substrate.config.json and no registration file:
A separate
substrate.config.yaml exists in client projects, but it is the org-wide system overlay scaffolded by substrate init — not a brand config. Keys are authored kebab-case and normalized to camelCase at load (heading-family → headingFamily); child keys of named-resource maps (intents, gradients, materials, ramps) keep their spelling. Inheritance is a recursive deep merge — see Inheritance.
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.
Complete example
An example brand config (fictional brand), trimmed —src/brands/acme/config.global.yaml:
Sections
name and slug
name is the display name (name: Aurora Airways - Rewards). The slug is not authored — it is derived from the directory name by the loader (aurora/rewards → aurora-rewards) and becomes the runtime’s data-brand value.
intents — required
An open map of intent colors. Only brand and neutral are hard-required (the loader throws missing-required-intent otherwise). The conventional names — brand, neutral, danger, warning, success, info, beta — get no special treatment over custom intents; the brand’s own map is the source of truth. Each intent authors hue (0–360) and chroma (0–0.4) only — lightness is solver-owned.
An intent’s color may vary across the scheme axis with a scheme track: scheme-end: { hue, chroma } (shorthand for the dark end), or a full scheme-track list of stops { at, hue, chroma } / { at, from-intent } with optional blend: oklch | oklab. Schema-valid, though none of the bundled demo brands currently exercises it.
elevation — required
step-light, step-dark, and sunken-step — the per-level lightness offsets for elevated and sunken surfaces in each scheme direction. A typical shape keeps light-mode elevation subtle (0.015) and lets dark mode carry more (0.04).
typography — required
heading-family, body-family, optional mono-family; base-font-size (rem, not px); scale-ratio (the modular ratio — there is no engine default; the demo brands range from 1.2 to 1.25); optional fluid (rate, floor, ceiling, quantum) and density coefficients (font-size-k, font-weight-k). See Type Tokens for how these compose.
shape, motion, space — required
shape.radius-base (px) is the base for the runtime-scaled --radius, so corner rounding tracks the density slider; the bundled demo brands range from 4 to 12. motion.duration-base (ms) and motion.easing are both required — values vary by brand character, from 75 with linear for a transactional surface to 200 with a custom cubic-bezier for a more polished one. space.unit (px, a bare number) is the single spatial unit everything composes from; the bundled demo brands all use 4.
flexibility — required
The brand’s policy for how far users may push each preference axis: scheme (boolean), and { min, max } bounds for contrast, density, type-scale, and motion (optional effects). These bound the runtime preference vector, whose engine defaults are:
presets — optional
Named positions on the preference continuum: mode, density, and contrast are each an open map of brand-named levels. Mode levels carry scheme and contrast-factor — note brands choose positions, not endpoints (a light level may sit at scheme: 0.02; a dark level at 0.74, a deep navy rather than black). The engine also ships brand-independent presets:
A fuller example for the fictional Acme brand — five mode levels and a density map:
presets.mode does not get those five modes — they are authored, not automatic.
ramps and color — optional
ramps is an open map of ramp recipes — the only place a --color-* style output exists; a brand that opts in gets static stepped scales generated from an intent. Each recipe: intent (source intent name), steps (number list, lightest → darkest; pure labels — lightness is assigned by position), lightest-l (default 0.97), darkest-l (default 0.18), gamut (srgb | p3, default srgb), outputs (adapter list, default [tailwind]), sample.
color key is ramp orchestration, not brand color: color.ramps holds defaults and per-intent settings (stops, lightest-l, darkest-l, alpha-equivalent, surfaces, sample), and color.ramp-outputs maps each platform (web, ios, android, react-native) to formats, materialization (dynamic | static | both), and an include list.
gradients — optional
An open map of named gradient recipes — the one place lightness is authored. Each: type (linear | radial | conic), geometry (angle for linear/conic, shape and origin for radial, optional repeat), and two or more stops. A stop references an intent by name (or transparent) and carries at (position) plus either an authored lightness or a channel (surface | fg | border) to use the APCA-solved value, with optional chroma and alpha:
materials and effects — optional
materials is an open map of named surface treatments applied via the data-mode token list. Each material declares channel blocks — background, foreground, border — and each channel takes a filter composition (blur, saturate, brightness, contrast, grayscale, hue-rotate, invert) plus channel properties (alpha, noise, lightness, chroma):
effects holds blur-unit (px) — filter blur values are emitted as multiples of it (blur(calc(var(--blur-unit) * 2))). Backdrop filters are wrapped in an @supports guard, and a non-zero noise adds an SVG-turbulence grain overlay.
system — optional
A free-form map deep-merged over the engine’s system.config.yaml defaults for this brand only (cascade: defaults → user override → brand block). Overridable keys include optical typography rates and clamps, CVD safe arcs, and layout structure thresholds: