Skip to main content
A type config is a shared archetype: a root-level YAML config under src/types/ that defines reusable role, state, mode, and semantic-map patterns. Components inherit one with extends: and then override or delete what differs — badge extends the action archetype and deletes its interaction states; button extends the same archetype and keeps them. Types are portable source, not platform adapters: the same archetype feeds every output target. Five archetypes exist:

Anatomy of a type config

Every type config shares the same top-level shape — type:, an effect: channel (backdrop-filter for all archetypes except text, which uses filter), a meta: block, and then roles:, modes:, and semantic-map:. The meta: block states a system-wide invariant, annotated identically in all five files:
Intents are universal — a type config cannot invent color semantics, only restrict which of the brand’s intents its components may consume, and all five shipped archetypes restrict nothing. The action archetype, abridged to one role per concept:
The semantic-map is the named-to-numeric lookup: a component (or author) writing border: thin gets 1 through this table rather than a magic number. The other archetypes vary the same vocabulary — feedback’s auxiliary role authors border-x-start-width: 3, the logical-property spelling that produces the classic left-accent alert bar and mirrors correctly in right-to-left scripts; input adds an indeterminate mode alongside selected. Chart is the structural outlier: it has no roles: or modes: at all, defining instead series: (named palettes composed of intent + contrast stops — solo, comparison, percentile, categorical), overlays: (threshold bands and target lines), states: (default/selected/dimmed for series elements), and axis: styling. Everything still references intents by name, so chart colors go through the same solver as everything else.

The text archetype: where typography meets contrast

types/text/config.yaml defines the six text roles. Each role carries a font family variable, a font-scale step exponent, weight, line height, letter spacing, and a fluid: envelope: The fluid: envelope per role (floor, ceiling, min, max, reference: container) feeds a second generated rule gated by [data-mode~="fluid"] — fluid typography is opt-in per element via that mode token, exactly as the markup contract describes.

How text roles drive per-role APCA foregrounds

This file is also the source of the role-specific solved foregrounds you see in the color token reference — the chain runs from YAML to solver to CSS:
  1. Baseline election. The generator elects the baseline role as the one whose font-scale is closest to zero (ties broken alphabetically) — with the shipped config, body. This is derived, not hard-coded: reshape the scale and the baseline can move.
  2. Per-role solve. For every other role, the role’s step exponent and the brand’s ratio yield a rendered pixel size; size and weight index into Substrate’s size/weight-aware Lc floor table (its own construction — see the APCA Solver reference); and that target is solved against the actual surface. Larger, bolder text legitimately needs less contrast, so each role earns its own solved lightness instead of sharing one global foreground.
  3. Emission. The solver writes --ucs-{intent}-fg-l-{role} per intent × non-baseline role; the generated CSS narrows it to --intent-fg-l-{role} inside each intent’s scope; and a text element carrying data-mode~="heading" picks it up with a fallback to the base foreground. The baseline role uses the plain --intent-fg-l directly, and where a parent already declared contrast: auto, the spatial foreground is suppressed with an explanatory comment in the output rather than silently fighting it.
The engine also seals a mirror of these six roles as bootstrap defaults in the kernel — the compiler cannot depend on its own generated output to start — and installs a client’s generated text-role manifest over them at runtime.

Authoring with archetypes

Reach for a type config when a pattern recurs across components — roles, states, or semantic maps that would otherwise be copy-pasted. The inheritance mechanics are the same cascade as everywhere else (see the resolution cascade): objects deep-merge, null deletes, and a brand can override at the type level so the change reaches every component that extends it. Parts can tap an archetype directly with uses: — a part named after a text role inherits that role’s tokens without the component extending the whole type.

Component Config Overview

The full config.yaml schema — channels, parts, validation, and the cascade.

Type Tokens

The generated typography variables the text roles resolve into.