Skip to main content
Running many brands from one design system usually decays the same way: parallel token files drift, accessibility rules get enforced inconsistently, and every new brand is a fresh integration. Substrate avoids that by keeping the mathematics in one engine and reducing a brand to a config. The solver, the preference vector, the component descriptors, and the accessibility gate are shared; a brand supplies intent and policy.

What a brand actually owns

More than color. A brand config carries its intents, but also its elevation steps, shape.radius-base, typography families and ratio, motion.duration-base and easing, space.unit, its flexibility policy bounding every user preference axis, its presets (including which mode and density levels exist at all), plus optional gradients, ramps, materials, and system overrides. See the brand config overview for the full schema. What a brand does not own is the solving. APCA lightness resolution, the scheme axis, CVD and warmth compensation, and the build-failing accessibility gate run identically for every brand. That is why a new brand inherits correct contrast behavior from its first generation rather than earning it.

Project structure

Brands live in directories under src/brands/. There are two shapes, and the directory contents decide which one applies. Here is how a workspace with two fictional brands — the Aurora Airways family and the standalone Acme brand — would be laid out:
A directory containing config.global.yaml is a family: each subdirectory holding a config.yaml becomes its own brand, with the merged slug {parent}-{sub}aurora-rewards, aurora-booking. A directory containing only config.yaml is a flat brand, and its slug is the directory name. The engine bundles several demo brand families in this layout for testing and demonstration.
Demo brands only. All brand configurations bundled with the Substrate engine, and all brand names used in examples throughout these docs, are fictional demonstrations created to exercise the engine. They do not represent clients or customers of Substrate or its authors, and no affiliation with, sponsorship by, or endorsement from any real company is implied.
There is no workspace file, no brand registry, and no registration step. Discovery is a filesystem scan of the brands directory — a brand exists because its directory does. Adding one is covered in Adding a Brand.

Generating

Generation runs from the engine checkout and covers every discovered brand in one pass:
Narrower runs are available for iteration: npm run generate:tokens, npm run generate:ramps, npm run generate:descriptors, and npm run generate:doc-views each handle one artifact family, and most accept a target suffix such as npm run generate:tokens:css or npm run generate:tokens:swift. Every one has a :check variant that verifies the committed output matches what the current configs would produce, which is what CI runs.
Substrate has no build verb and no per-brand build flag — you regenerate the corpus, not a single brand. The substrate CLI is a separate consumer-side onboarding tool whose verbs are init, add, upgrade, adopt, setup, and artifact — see the CLI reference.

Per-brand output

Output lands under generated/brands/<family>/<sub>/, one subdirectory per target. For the fictional aurora-rewards:
Globally shared artifacts — the continuous scalar expressions, text roles, component descriptors — live under generated/global/ and are not duplicated per brand. Every generated file carries a .gen. marker in its name and a DO NOT EDIT header. Changes belong in the brand YAML.

Why families are worth using

A family exists so that shared decisions live in one file. Updating src/brands/aurora/config.global.yaml propagates to all five Aurora sub-brands on the next generation; adding an intent there gives every sub-brand that intent automatically. A sub-brand’s own config stays small — a well-factored sub-brand is often around thirty lines of genuine divergence over a shared foundation — and the validator actively flags any line in it that merely restates what it already inherits.

Inheritance

The deep-merge contract, null-delete semantics, and why sub-brand configs contain only deltas.

Adding a Brand

Create a directory, write a config, generate — with no registration step in between.