Skip to main content
Adding a brand is three steps: make a directory, write a config, regenerate. There is no registry to update and no build flag to pass, because discovery is a scan of the brands directory. The solver, the mode presets, and the accessibility gate apply to the new brand the moment it is found. Two paths follow, depending on whether you are adding a sub-brand to an existing family or a standalone flat brand. The sub-brand path is much shorter, because inheritance does most of the work.

Adding a sub-brand to an existing family

1

Create the sub-brand directory

Inside the family directory, next to config.global.yaml and the existing sub-brands.
The directory name becomes the child slug, and the merged brand slug will be aurora-vacations.
2

Write only the deltas

Create src/brands/aurora/vacations/config.yaml containing nothing but what differs from the family foundation. Everything else — intents, elevation, shape, space, flexibility, presets — is inherited.
Note what is absent. There is no slug — it comes from the directory structure. The intents.brand override supplies only chroma, inheriting hue: 15 from the parent, because the cascade deep-merges partial objects. And typography overrides one field while keeping the family’s font stacks and fluid settings.
3

Generate

From the engine checkout:
Every brand regenerates, the new one included.

Adding a standalone flat brand

A brand with no sub-brands is a single directory holding a single config.yaml — but that config has no parent to inherit from, so it must be complete. All seven required sections must be present, and intents must include both brand and neutral or the load throws.
1

Create the brand directory

The directory name is the slug: nova.
2

Write a complete config

Create src/brands/nova/config.yaml. This shape follows the flat-brand pattern used by the engine’s bundled demo brands.
Values are in the units the schema expects: base-font-size in rem, space.unit and radius-base in pixels as bare numbers, duration-base in milliseconds as a bare number. Keys are kebab-case and normalize to camelCase at load — see the config overview.
3

Generate

4

Verify the output

A flat brand writes to generated/brands/nova/, with the same per-target subdirectories every brand gets:
Every file carries a .gen. marker and a DO NOT EDIT header. Sub-brands nest one level deeper — generated/brands/aurora/vacations/.
Modes are authored, not automatic. A brand gets exactly the mode levels its presets.mode map declares — the five in the example above are a convention the bundled demo brands share, not a built-in set. Omit presets.mode and the brand has no named modes. The same applies to density levels, whose names are entirely brand-chosen — a brand serving marketing and product surfaces might call its levels discovery, default, and workspace.

What happens at load

Two validations run before a brand is usable. The intent roster must contain brand and neutral, and any intent using scheme-end or scheme-track grammar is desugared and checked — including stop positions, reference cycles, and references to intents that do not exist. After that, the accessibility gate runs over the whole corpus during generation. If an intent cannot reach its APCA target within gamut, the shortfall is reported and the build fails rather than shipping a brand that quietly misses contrast. A new brand is held to exactly the same bar as the six that ship with the engine.

Inheritance

The deep-merge rules, null-delete, and the validator that enforces deltas-only authoring.

Color

How to choose hue and chroma for an intent, and what the solver does with them.