> ## 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.

# Adding a New Brand to a Substrate Engine

> Create a directory, write a YAML config, generate. Discovery is a filesystem scan, so there is no registration step — and accessibility behavior is correct from the first run.

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

<Steps>
  <Step title="Create the sub-brand directory">
    Inside the family directory, next to `config.global.yaml` and the existing sub-brands.

    ```bash theme={null}
    mkdir -p src/brands/aurora/vacations
    ```

    The directory name becomes the child slug, and the merged brand slug will be `aurora-vacations`.
  </Step>

  <Step title="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.

    ```yaml theme={null}
    name: Aurora Airways - Vacations

    intents:
      brand:
        chroma: 0.220
      vacations-accent:
        hue: 51
        chroma: 0.116

    typography:
      scale-ratio: 1.25

    motion:
      duration-base: 250
    ```

    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](/multi-brand/inheritance). And `typography` overrides one field while keeping the family's font stacks and fluid settings.
  </Step>

  <Step title="Generate">
    From the engine checkout:

    ```bash theme={null}
    npm run generate
    ```

    Every brand regenerates, the new one included.
  </Step>
</Steps>

## 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.

<Steps>
  <Step title="Create the brand directory">
    ```bash theme={null}
    mkdir -p src/brands/nova
    ```

    The directory name is the slug: `nova`.
  </Step>

  <Step title="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.

    ```yaml theme={null}
    name: Nova

    intents:
      brand:
        hue: 284
        chroma: 0.272
      neutral:
        hue: 284
        chroma: 0.005
      danger:
        hue: 25
        chroma: 0.22
      warning:
        hue: 75
        chroma: 0.17
      success:
        hue: 150
        chroma: 0.19
      info:
        hue: 267
        chroma: 0.191

    elevation:
      step-light: 0.015
      step-dark: 0.04
      sunken-step: 0.025

    typography:
      heading-family: "Inter, system-ui, sans-serif"
      body-family: "Inter, system-ui, sans-serif"
      base-font-size: 1
      scale-ratio: 1.25
      fluid:
        rate: 0.5

    shape:
      radius-base: 12

    motion:
      duration-base: 200
      easing: "cubic-bezier(0.25, 0.1, 0.25, 1)"

    space:
      unit: 4

    flexibility:
      scheme: true
      contrast: { min: 0.8, max: 1.5 }
      density: { min: 0.75, max: 1.3 }
      type-scale: { min: 0.9, max: 1.4 }
      motion: { min: 0, max: 1 }

    presets:
      mode:
        light:
          scheme: 0
          contrast-factor: 1.0
        dark:
          scheme: 1
          contrast-factor: 1.0
        dimmed:
          scheme: 0.65
          contrast-factor: 0.95
        high-contrast:
          scheme: 0
          contrast-factor: 1.3
        dark-high-contrast:
          scheme: 1
          contrast-factor: 1.3
      density:
        compact:
          density-factor: 0.85
        default:
          density-factor: 1.0
        comfortable:
          density-factor: 1.15
    ```

    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](/brand-config/overview).
  </Step>

  <Step title="Generate">
    ```bash theme={null}
    npm run generate
    ```
  </Step>

  <Step title="Verify the output">
    A flat brand writes to `generated/brands/nova/`, with the same per-target subdirectories every brand gets:

    ```
    generated/brands/nova/
    ├── css/            # brand.gen.css + tokens.<mode>.gen.css
    ├── swift/          # brand.gen.swift, index.gen.swift, system.<mode>.gen.swift
    ├── compose/        # Kotlin equivalents
    ├── dtcg/
    ├── json/
    ├── react-native/
    ├── xcassets/
    └── docs/
    ```

    Every file carries a `.gen.` marker and a `DO NOT EDIT` header. Sub-brands nest one level deeper — `generated/brands/aurora/vacations/`.
  </Step>
</Steps>

<Warning>
  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`.
</Warning>

## 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.

<CardGroup cols={2}>
  <Card title="Inheritance" icon="arrow-down-to-bracket" href="/multi-brand/inheritance">
    The deep-merge rules, null-delete, and the validator that enforces deltas-only authoring.
  </Card>

  <Card title="Color" icon="palette" href="/brand-config/color">
    How to choose hue and chroma for an intent, and what the solver does with them.
  </Card>
</CardGroup>
