> ## Documentation Index
> Fetch the complete documentation index at: https://substrate.docs.unknowncreatives.studio/llms.txt
> Use this file to discover all available pages before exploring further.

# Brand Family Inheritance and the Deep-Merge Cascade

> A sub-brand config holds only its deltas. The two layers combine by recursive deep merge, so partial objects merge field by field — and the validator flags anything a sub-brand restates.

A brand family exists so that shared decisions live in exactly one file. The family's `config.global.yaml` is the foundation; each sub-brand's `config.yaml` holds **only what differs**. The two layers combine by a recursive deep merge, which is what makes deltas-only authoring possible: a sub-brand can override one field inside a nested object and inherit the rest of it.

## The two layers

```
src/brands/aurora/config.global.yaml     →  parent (shared foundation)
src/brands/aurora/rewards/config.yaml    →  child (deltas only)
```

Later layers win, per leaf. The merged result is stamped with the family slug `{parent}-{sub}` — `aurora-rewards` — which is what appears in generated CSS as `[data-brand="aurora-rewards"]`.

## Partial objects merge

This is the central rule, and the one most likely to surprise you if you have used systems where an override replaces the whole object. **An object in the child merges recursively into the object in the parent.** It does not replace it.

The fictional Aurora Airways family demonstrates it. The parent declares the brand intent in full:

```yaml theme={null}
# src/brands/aurora/config.global.yaml
intents:
  brand:
    hue: 15
    chroma: 0.216
```

And the child overrides one field of it:

```yaml theme={null}
# src/brands/aurora/rewards/config.yaml
name: Aurora Airways - Rewards

intents:
  brand:
    chroma: 0.220        # hue: 15 inherited from the parent
  tier-gold:
    hue: 51
    chroma: 0.116
  aurora-navy:
    hue: 216
    chroma: 0.141

typography:
  heading-family: "'Aurora Display', Verdana, system-ui, sans-serif"
```

The merged `intents.brand` is `{ hue: 15, chroma: 0.220 }`. The Rewards sub-brand never restates the hue. Likewise, its `typography` block overrides only `heading-family` — `body-family`, `base-font-size`, `scale-ratio`, `fluid`, and `density` all come from the parent untouched, and `tier-gold` is simply added to the inherited intent roster.

A sibling sub-brand like Aurora Cargo can show the same merge at three different depths at once: overriding `intents.brand` entirely (both fields), `intents.neutral.chroma` alone, `typography.scale-ratio` alone, `motion.duration-base` alone (keeping the family's `linear` easing), and inside `presets` adjusting individual mode levels' `scheme` values while leaving each level's `contrast-factor` inherited.

## The full merge table

Applied per key of the child layer:

| Child value                                         | Result                                                                   |
| --------------------------------------------------- | ------------------------------------------------------------------------ |
| An object, where the parent value is also an object | Deep-merged recursively                                                  |
| `null`                                              | **Deletes** the key from the merged result                               |
| An array                                            | Replaces the parent array wholesale — never concatenated or index-merged |
| A scalar, or any type change                        | Replaces the parent value                                                |

Two consequences are worth stating plainly. `null` is the **only** delete signal — there is no separate unset or remove keyword, so a sub-brand that needs to drop an inherited intent or gradient sets that key to `null`. And arrays replace, which matters most for `scheme-track` stop lists: a child supplying a track supplies the whole track.

Key normalization runs before the merge, so a parent authoring `heading-family` and a child authoring `headingFamily` merge as the same key. (Authoring both spellings side by side inside one object is an error, not a silent win for one of them.)

## Deltas-only is enforced, not just encouraged

The cascade validator walks every family and flags each leaf in a sub-brand config whose value structurally equals what the parent already provides. Comparison is deep and by value, so an object or array that merely matches the parent's content is caught even though it is a different reference. Genuine overrides are never flagged, and `null` deletes are not leaves so they are never flagged either.

Each finding names the brand, the config path, and the redundant key path, so a config that has drifted into duplication tells you exactly which lines to delete.

<Warning>
  Redundant leaves are worse than noise. A value copied into a sub-brand no longer tracks the family — the next time the parent's value changes, that one sub-brand silently keeps the old one. That is the drift the family structure exists to prevent, and it is why the validator treats duplication as a finding rather than a style preference.
</Warning>

## What inheritance does not include

There is no workspace layer above the family, no `defaults` block, and no system-wide brand defaults file. The cascade is exactly two layers deep: family and sub-brand. A flat brand has no cascade at all — its single `config.yaml` is the whole configuration, and it must therefore be complete, including the required `intents.brand` and `intents.neutral`.

<Tip>
  When starting a new sub-brand, write nothing and generate. Then add fields one at a time until it looks right. Starting from a copy of the parent guarantees a config full of redundant leaves you will have to delete.
</Tip>

<CardGroup cols={2}>
  <Card title="Adding a Brand" icon="circle-plus" href="/multi-brand/adding-a-brand">
    Walk through creating a sub-brand and a flat brand end to end.
  </Card>

  <Card title="Brand Config Overview" icon="sliders" href="/brand-config/overview">
    The schema every layer is authored against, and the key normalization rule.
  </Card>
</CardGroup>
