Spec reference
Token bindings
Styling is a set of bindings to design tokens — generated onto both surfaces, gated for integrity at build time, never handwritten.
Token bindings & substitutionGeneratedCurated#
Every visual decision is a binding from a CSS property to a DTCG token reference — Record<cssProperty, TokenRef> where a TokenRef is a brace-wrapped token path. The CSS Module and the canvas variable bindings are generated from these; there is no handwritten style layer to drift.
Substitution: a {propName} placeholder inside a token path expands over that enum prop’s values — {color.action.{variant}.background} produces one CSS rule per variant and one bound variable per canvas variant. Root parts support multi-axis substitution; nested parts one placeholder per reference.
TokenRef = string matching /^\{[a-z0-9.{}-]+\}$/i
// e.g. "{color.action.primary.background}"
// "{color.action.{variant}.background}" ← substituted over an enum propThe integrity gate: at generation time, every reference — after expansion — must resolve to a real token in tokens/. A binding to a nonexistent token fails the build with the exact contract path and missing token named. The contract and the token set cannot silently disagree (eval: refuse-unknown-token-reference).
Refusals: Each of these fails the build by name — the generator refuses, it never papers over (source: core/emit-react.ts validateContract, exercised by the C2 eval family).
- malformed references — anything not brace-wrapped token-path shaped
- a substituted reference naming a prop that is not a declared enum
- more than one placeholder on a nested part’s reference
{
"anatomy": {
"root": {
"tokens": {
"background-color": "{color.action.{variant}.background}",
"color": "{color.action.{variant}.foreground}",
"padding-inline": "{space.inset-x.{size}}",
"padding-block": "{space.inset-y.{size}}",
"border-radius": "{radius.control}",
"gap": "{space.gap.control}",
"font-family": "{font.control.family}",
"font-weight": "{font.control.weight}",
"font-size": "{font.control.size.{size}}"
}
}
}
}Tokens by propGeneratedCurated#
tokensByProp: { prop, map } — the value-level sibling of the substituted reference. A substituted ref can only carry bindings whose token names spell the axis value; real foreign vocabularies name tokens by scale step ({spacing.200} on large, {spacing.150} on small), so a binding that is a plain function of one enum axis needs a per-value map. Map values are overrides merged over the part’s base tokens; only the values that deviate appear, and refs are plain — no placeholders.
Multiple entries (v14): a part may carry an ordered array of entries — one per driving axis (Button: variant colors and size paddings; Text: the variant scale and the fontWeight map). The single-object spelling stays valid. When two entries on different props override the same channel, the later entry wins — mirroring the CSS source-order cascade the values were extracted from. Two entries may not claim the same channel for the same prop: a conflicting channel+prop pair is refused by name.
The driving enum prop, by canonical name.
enum value → (CSS property → plain TokenRef), merged over the base tokens.
Refusals: Each of these fails the build by name — the generator refuses, it never papers over (source: core/emit-react.ts validateContract, exercised by the C2 eval family).
- the driving prop must be a declared enum; every map key one of its values
- a placeholder inside a mapped ref — per-value maps are the substitution; a placeholder would be double substitution
- a component-instance part — the child contract owns its styling
- two entries claiming the same channel for the same prop (a conflicting channel+prop pair) — within tokensByProp or across tokensByProp/literalsByProp
{
"anatomy": {
"root": {
"tokensByProp": {
"prop": "size",
"map": {
"sm": {
"padding-inline": "{space.inset-y.sm}"
},
"lg": {
"font-size": "{font.control.size.sm}",
"padding-inline": "{space.inset-x.sm}",
"padding-block": "{space.inset-y.sm}"
}
}
}
}
}
}Literal channelsGeneratedCurated#
literals and literalsByProp (v14) carry the styling facts a foreign system keeps as component-private literals — Polaris’s --pc-* pixel geometry (ProgressBar’s per-size track heights, Avatar’s per-size widths, Button’s transparent base background). A literal is only carried when it was resolved deterministically through the source’s own var() chain at promotion time, with provenance; it is never minted into a token — the value is honestly literal, and renaming it against a real token is the adopter’s call.
The grammar is bounded: px/rem/em/unitless numbers, hex and rgb()/rgba() colors, and the CSS keywords transparent/inherit/currentColor. The channel set is bounded too (geometry and paint channels — see the refusal list). literalsByProp is the per-enum-value form, an ordered array with exactly the tokensByProp entry semantics.
The driving enum prop, by canonical name.
enum value → (CSS property → bounded literal), merged over the base literals.
Refusals: Each of these fails the build by name — the generator refuses, it never papers over (source: core/emit-react.ts validateContract, exercised by the C2 eval family).
- a channel outside the bounded literal-channel set (background/background-color, color, height/width/min-*, padding-block/-inline, gap, border-radius, border-width, font-size, line-height, letter-spacing)
- a value outside the bounded grammar (no gradients, no calc(), no keywords beyond transparent/inherit/currentColor)
- a channel carried as BOTH a token binding and a literal on the same part — ambiguous
- a component-instance part — the child contract owns its styling
{
"element": "div",
"tokens": {
"background-color": "{color.progress.track}"
},
"literalsByProp": [
{
"prop": "size",
"map": {
"small": {
"height": "8px"
},
"medium": {
"height": "16px"
},
"large": {
"height": "32px"
}
}
}
]
}Declared factsGeneratedCurated#
declared and declaredStates (v15, the S4 channel lifts) carry the keyword/literal styling channels that have no token vocabulary — cursor, user-select, appearance, text-rendering, font-feature-settings, transitions, touch-action, the position: relative class, the A22 text channels (text-transform, text-decoration-line, text-align, text-overflow), font-family stacks, and the background sub-channels. Before v15 these were extension-block residue; now they are first-class facts: every code emitter renders them verbatim, and the canvas either draws them natively (text case, decoration, alignment, truncation, first font-family stack entry — the capability matrix’s draw verdicts) or declares them without drawing (the matrix §b annotation copy lands in the component description — declared-not-drawn, never silently dropped).
declared is Record<cssProperty, value> on any non-ref part; declaredStates is the per-state form (Record<state, Record<cssProperty, value>>) — cursor staying pointer on :disabled, text-decoration-line: underline on :hover. A declared cursor or position fact is authoritative: the emitters’ own button chrome (cursor: pointer, the :disabled not-allowed rule) yields to it. The computed-capture floor promotes uniform observed values (and full-coverage uniform state deltas) into these fields automatically — see extract/computed/.
Refusals: Each of these fails the build by name — the generator refuses, it never papers over (source: core/emit-react.ts validateContract, exercised by the C2 eval family).
- a channel outside the
DECLARED_CHANNELSregistry — token/literal-vocabulary channels belong intokens/literals - a value outside the channel’s bounded grammar (e.g.
positionadmits onlyrelative/static— absolute placement belongs tooverlay/stylesWhen) - a channel carried as BOTH a token binding (or literal) and a declared fact on the same part — ambiguous
- a state outside the contract’s declared
states, or outside the state vocabulary - a component-instance or slot part — the child contract / consumer owns its styling
{
"element": "button",
"declared": {
"cursor": "pointer",
"user-select": "none",
"text-rendering": "optimizelegibility"
},
"declaredStates": {
"disabled": {
"cursor": "pointer"
}
}
}