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
{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: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:
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, andnull 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.
What inheritance does not include
There is no workspace layer above the family, nodefaults 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.
Adding a Brand
Walk through creating a sub-brand and a flat brand end to end.
Brand Config Overview
The schema every layer is authored against, and the key normalization rule.