Appearance
Token Engine
Located in src/lib/token-engine.ts. Generation runs entirely in the UI thread (not the sandbox) and produces a flat array of GeneratedToken objects.
Generation Phases
The current pipeline has six main phases (0–5) plus a 2b inv-frame sub-phase, mirrored in src/lib/token-engine.ts (search "Phase N:" inline). Collections use the flat layout under color/... (the collection path is not prefixed with the theme name, under the single-theme-in-Figma policy).
| Phase | What | Collection pattern |
|---|---|---|
| 0: Meta | theme-name / description (only when set) / color/contrast-model / color/collection-structure / color/skip-duplicate-tokens / counts (STRING + FLOAT, theme-level meta), per-entity stats, per-mode stats, and seed-color (per-mode COLOR, color-category). The engine always generates these; the Push meta to Figma toggle (theme.settings.pushIncludeMeta) filters them out at push time. | meta, color/meta |
| 1: Surfaces | Level entities from seed color. Condition + interaction segments skipped — surfaces don't iterate either axis. | color/{entity} |
| 2: Containers | Frame entities per surface level. Iterates conditions × interactions when the active conditions are interactive. | color/{entity}/{surface}-{level} |
| 2b: Inv-containers | Same frames computed with flipped direction. Merged into the same collection as Phase 2 with an inv/ prefix on the variable path (no separate inv- collection). | color/{entity}/{surface}-{level} (paths gain inv/ prefix) |
| 3: Content | Top-layer entities on surfaces, containers, and inv-containers. Both directions; inverse merged with inv/ prefix. | color/{entity}-on-{parent}/{surface}-{level}[/{containers}-{level}] |
| 4: Scrims | Overlay entities from seed color. Condition + interaction segments skipped. | color/{entity} |
| 5: Fixed colours | Mode-invariant flavour for frame / top-layer entities when Enable Fixed colors is on. Synthetic single-mode collection. | color/fixed/{entity} |
Token Path Structure
text
color/{collectionName}/{conditionName?}/{interactionName?}/c-{contrastIndex}/{colorId}- Static condition (e.g.
Disabled,interactive: false) → no interaction segment:color/{entity}/disabled/c-N/color-name. - Interactive condition (e.g.
Enabled) → both segments:color/{entity}/enabled/{interactionName}/c-N/color-name. - Inverse tokens add an
inv/prefix anywhere along the path. - Surfaces / scrims skip only the interaction segment — they still emit a condition segment (always present, even for the lone seeded
Enabled): surfacecolor/surfaces :: enabled/surfaces-0/color-name; scrimcolor/scrims :: scrims-0/enabled/c-N/color-name.
Deduplication
Two levels of deduplication:
- Generation-time -- within each (condition, interaction) group, if a token produces identical RGBA values across all modes as a previously generated token with the same dedup key, it is skipped. The count is tracked in
skippedDuplicates. The Skip duplicate tokens switch in Color Settings (default on) controls this. - Push-time (FNV-1a hash) -- each collection's token set is hashed (paths + per-mode values + each token's
aliasOfTokenId). If the hash matches the stored hash from the previous push (systema_hashplugin data on the collection), the entire collection is skipped with no Figma writes. Including the alias state means toggling Alias duplicate tokens still triggers a re-push.
A separate, opt-in aliasing pass (assignAliases, default off — settings.aliasDuplicateTokens) runs after generation: tokens with an identical per-mode hex signature inside the same collection collapse onto the first canonical token via aliasOfTokenId, which becomes a Figma VARIABLE_ALIAS on push. STRING/meta tokens and tokens without a tokenId are never aliased.
The Skip low-contrast tokens switch (default on) is a third filter — independent from dedup — that prunes tokens whose actual contrast against parent falls below the entity's smallest contrast step in EVERY mode (direct AND inverse axis). See Skip low-contrast tokens above.
surfaceScope Filtering
For frame and top-layer entities, the surfaceScope property (all, first, last) determines which surface level indices are included in generation, reducing output for entities that only need to appear on specific levels.
Disabled Filtering
The following are excluded from generation:
- Colors with
enabled === false - Conditions with
enabled === false(note:Enabledcondition is undeletable, but can be disabled to suppress its tokens) - Interactions with
enabled === false - Modes with
enabled === false
Collection structure
settings.collectionStructure (default balanced) decides how the flat token array is bucketed into Figma variable collections:
- compact — everything in a single
colorcollection. The per-modecolor/meta(seed-color) and theme-level single-modemetamerge so the user seescolor+metaonly. - balanced (default) — grouped by phase:
color/surfaces(scrims merge in here),color/containers,color/content, pluscolor/metaand theme-levelmeta. - granular — per-surface-level collections (e.g.
color/{entity},color/{entity}/{surface}-{level}) to stay under the 5,000-variable-per-collection Figma limit on large themes.
All three produce genuinely different layouts, so switching between any pair after a push triggers the cross-collection migration flow (create new variables → rebind node refs via systema_tid → legacy-alias the old ones). The Fixed flavour always lives in its own single-mode color/fixed/{entity} collections regardless of structure.
Variable-count estimation
estimateVariableCount(theme, preGeneratedTokens?, preSkipped?) tallies tokens per collectionName and returns { collections, totalVariables, totalModes, skippedDuplicates }. totalVariables is tokens.length; totalModes uses effectiveModes (so the synthetic Value placeholder counts as 1); each collection is flagged withinLimit against FIGMA_LIMITS.variablesPerCollection (5,000). The header pill and collection grid read this estimate.