Skip to content

Promote Primitives

A run choice in config/settings.yaml. When enabled, a text, glyph or container layer in composed example content becomes an instance of the design system component it stands for, using the primitives table.

Designers build examples out of raw layers because composing with real instances in a design file is laborious. A text layer wearing the design system’s typography token is standing in for that system’s text component; promotion records the component rather than the approximation.

Composed content only. slotContentExamples and instanceExamples are affected. A component’s own variants.yaml is never promoted — someone who put a text layer in a component’s anatomy chose a text layer, and that is what the component contains.

Configuration

spec:
promotePrimitives: true

Off by default. Promotion restructures composed content, so a workspace elects it rather than receiving it on upgrade. A primitives table is inert until this is turned on.

Result

Without promotion (false), a heading in an alert’s slot content is a text layer with styles:

anatomy:
heading: { type: text }
elements:
heading:
styles:
layoutSizingHorizontal: FILL
textColor: { $token: Color/Inverse on surface, $type: color }
typography: { $token: Typography/font__400__medium, $type: typography }
content: Heading

With promotion (true), it is an instance, in the same shape as a component the designer placed:

anatomy:
heading: { type: instance, instanceOf: dsTypography }
elements:
heading:
instanceOf: dsTypography
propConfigurations:
color: Inverse on surface
size: 400
weight: Medium
text: Heading
styles:
layoutSizingHorizontal: FILL # unmapped — still styling
$extensions:
com.figma:
promotedPrimitive: true
content: Heading
styles:
textColor: { $token: Color/Inverse on surface, $type: color }
typography: { $token: Typography/font__400__medium, $type: typography }

The styles are partitioned, not copied. What the promotion consumed moves to $extensions.com.figma.styles; what nothing claimed stays in styles and reaches output as before. No value is stored twice.

Provenance

promotedPrimitive marks the element as a promoted layer rather than an instance the designer placed. It is stated rather than inferred, because a promotion may consume no styles at all — a glyph whose name maps to a prop while no style rule resolves.

multipleMatches marks an element where more than one entry resolved and the highest scorer was chosen. Two components claiming one layer may be a true description of the design system, or a conventions file that has drifted — either way the ambiguity is recorded on the element rather than announced once in a run log.

content holds the text string or glyph name the promotion consumed. Its value also sits in propConfigurations, but only under whichever prop the table named — recording it here is what lets a promoted layer be restored without consulting the table.

styles holds what the promotion consumed, verbatim. A prop value cannot be turned back into the style that produced it, since two sources may map to one value: size: XS cannot say whether the layer carried a sizing token or a raw 16. Recording the original is what lets a promoted layer be rendered back to Figma as a layer.

Unmatched sources

A source with no matching row in the table does not resolve. Its value stays in styles and reaches output as passed styling, so an incomplete table produces more verbose output rather than missing design intent.

If no entry matches at all, the element is left exactly as it is — the same output as false.

Tokens

A values key is matched literally against what the style carries. What a style carries depends on spec.tokens — so a primitives.yaml is written against one profile, and goes inert under another.

Under TOKEN, a colour arrives as { $token: "Color/On surface", $type: "color" } and the key is that path:

- source: textColor
values:
"Color/On surface": { color: On surface }

Under FIGMA_SYNTAX_WEB the same colour arrives as the web code-syntax name a designer set in Figma — --ds-color-on-surface — and the row above matches nothing.

ProfileWhat a key must be
TOKENThe token path
TOKEN_FIGMA_EXTENSIONSThe token path — the extensions block is ignored
TOKEN_NAMEThe token path
FIGMA_NAMEThe Figma-native name
CUSTOMWhatever your mapping puts in $token
FIGMA_SYNTAX_WEB / _IOS / _ANDROIDThe platform’s code-syntax name — or the token path, per token

FIGMA_SYNTAX_WEB / FIGMA_SYNTAX_IOS / FIGMA_SYNTAX_ANDROID

The code-syntax profiles fall back to the token path per token, whenever a token has no code syntax defined for that platform. So a correct table under FIGMA_SYNTAX_WEB is part code-syntax names and part token paths, decided token by token — and the conventions file gives no clue which is which. Check each token in Figma rather than assuming a rule.

CUSTOM

CUSTOM replaces the reference with your $custom object verbatim. Promotion keys on $token, so a mapping that renames the token while keeping the shape works normally:

{ "$token": "color-on-surface", "$type": "color" }

A mapping that emits some other shape has no member promotion knows to key on, so nothing resolves and nothing promotes.

Comparison

A spec captured with promotion and one captured without differ throughout their composed content. Any comparison between them — a parity check between two producers, a diff against a stored baseline — reports the whole difference unless both sides ran with the same value.

The value is recorded in metadata.settings.spec, so a spec states how it was produced.

Path

spec.promotePrimitives in config/settings.yaml

See Also