agentsclimarketplace

Token builder

Skill jrpease/throughline/skills/token-builder

Build a two-tier (primitive + semantic) design token system as Figma variables — color ramps, spacing, type scale, radius, shadows — with light/dark or brand modes, using one collection per category per tier. Use this when the user wants to create design tokens, design variables, a color system, a type scale, a spacing system, or 'build my tokens' in Figma. Also trigger when the user mentions primitives and semantic tokens, variable collections, design system foundations, or modes/themes in Figma.From its SKILL.md

Install
npx -y skills add jrpease/throughline --skill token-builder

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

SKILL.md

23.8 KB, as published. Nobody here has run it

Token builder

Critical collection rule: this system uses one collection per category per tier — never one giant Primitives + one giant Semantic. Color, spacing, typography, radius, and border each get their own primitive collection and their own semantic collection. This is non-negotiable: modes (Light/Dark, Desktop/Mobile) belong to a collection, so mixing categories in one collection forces unrelated variables to share the same mode axis.

Model tip (#3): building a coherent token system (ramps, type scale, modes, primitive→semantic aliasing) is reasoning-heavy. It runs on your session model — Sonnet is a solid default; Opus helps for large or multi-mode systems. See the model guide in the plugin README.

Creates a two-tier design token system as Figma variables. Each tier is split into one collection per category (color, spacing, type, radius, border) so every category owns its own modes — see Step 1 for why. Within a collection, Figma variables are grouped with / and omit the category prefix (the collection already carries it); the sync layer later derives the dotted logical name (color.gray.50) by prefixing the collection's category.

  • Primitives — raw values with no meaning attached: gray/50, space/4 in their category's primitive collection. The full palette of possible values.
  • Semantic — meaning-bearing tokens that alias onto primitives: bg/default → {gray/50}, text/primary → {gray/900}. This is the layer the rest of the system consumes.

The power of two tiers: change a primitive once and every semantic token referencing it updates automatically, which cascades through sheets, components, and synced code. Preserving these aliases (not flattening them) is what makes runtime theming work later, so always create semantic tokens as references to primitives, never as copied literal values.

Prerequisites

This skill needs a live Figma connection. Read the manifest (${CLAUDE_PLUGIN_ROOT}/references/manifest-schema.md for the schema) and check figma.connected. Then do a cheap liveness read to confirm the connection is actually live right now. If it isn't, say so plainly and offer to run the figma-environment-setup skill first: "Figma isn't connected yet — want me to set that up? It's a one-time thing." Don't proceed until Figma is reachable.

Use the write mechanism recorded in figma.mechanism (default console-mcp). With Console MCP, prefer figma_execute to create variables in scripted loops rather than one tool call per variable — this is dramatically more token-efficient for the potentially hundreds of primitives across modes.

Scripting via figma_execute: see ${CLAUDE_PLUGIN_ROOT}/references/figma-scripting.md for the shared gotchas — the single-bridge-instance preflight (concurrent writes corrupt the file), the dynamic-page async APIs (synchronous document-wide getters like getLocalVariableCollections and setters like figma.currentPage = / node.textStyleId = throw — use getLocalVariableCollectionsAsync, setCurrentPageAsync, setTextStyleIdAsync, …), the resize() axis-lock trap, and why large WRAP grids time out. For a simple verification read, prefer the dedicated figma_get_variables tool (it handles dynamic-page correctly and resolves aliases with resolveAliases: true) over a hand-written script.

Execution model — sequential architect → figma-executor with model routing. If your host supports subagent dispatch, plan the token architecture once with one architect dispatch (deep tier) — it reads existing Figma state and emits a transcription-grade spec in stable identifiers (collection/variable names, never nodeIds) — then build the variables with figma-executor dispatches (balanced tier), strictly sequentially: preflight figma_get_status and never run two Figma-touching subagents at once (the single bridge is concurrency-1). Each executor finalizes by build-verify-then-replace with a programmatic read-back (figma_get_variables, not a screenshot); gate the result with a reviewer pass. Keep the human checkpoint between tiers (the PAUSE in Steps 2–4) — subagents run continuously within a tier, but you pause for the human between them. Route per ${CLAUDE_PLUGIN_ROOT}/references/agent-routing.md; never parallelize Figma work. If your host has no subagent dispatch, build and verify each tier inline, sequentially, as the steps below describe.

Step 1 — Brainstorm the structure (before building anything)

Run the protocol in ${CLAUDE_PLUGIN_ROOT}/references/brainstorm-before-build.md. First establish the intake mode (generative / descriptive / import) per that reference — it changes how much you generate versus preserve. In import mode, do not stop at a 1:1 transcription of what the user fed you (a brand guide or marketing site is almost always a partial system): after preserving and organizing their values, run the completeness pass from that reference — diff against the full-system model and proactively propose the missing pieces (tonal ramps, neutral ramp, state colors, dark mode, semantic roles, elevation/radius scales) as a preserve-first, opt-in menu derived from their existing values. Then lock the structure with the user, in readable chunks:

  • Seeds and direction — capture what the user actually has: brand color(s), font(s), aesthetic words (modern, rounded, dense, comfortable), reference images/URLs, or an existing token set to import. In generative mode, this is the seed you expand from; in import mode, this is the set you organize.
  • Color ramps — which hues (e.g. gray, brand primary, success, warning, danger), and how many steps each (a 50–900 ramp of ~10 steps is a sensible default). In generative mode, derive a full tonal ramp and harmonized supporting families from the user's seed color(s).
  • Modes — does the system need light + dark? Brand variants? Density? This is high-impact and shapes everything downstream (it determines how many values each variable holds and how the sync layer emits themes), so brainstorm it carefully. Default: light + dark.
  • Spacing scale — base unit and steps (e.g. 4px base: 0, 1, 2, 3, 4, 6, 8, 12, 16, 24...). Default to a 4px-based scale; "dense" pulls it tighter, "comfortable" looser.
  • Type scale — font families, the size ramp, weights, line-heights. In generative mode, propose complementary font pairings from the user's seed font. Default to a modular scale.
  • Radius / border / shadow scales as needed. "Rounded" → larger radius scale.
  • Primitive naming convention — the single most important decision, because the semantic tier aliases onto these names and renaming later breaks every alias. Lock it explicitly. In Figma, name variables without the category prefix (the collection supplies it) and group with /: e.g. gray/50, space/4, radius/md inside their category's primitive collection. The sync layer derives the dotted logical identity (e.g. color.gray.50) by prefixing the collection's category. Keep names neutral and semantic — never framework-specific (don't name a token --background to match shadcn; the adapter renames per framework later). Figma is framework-agnostic; the adapter absorbs all framework-specific shaping.
  • Tiers — default to two-tier (primitive + semantic). Only raise the option of a third component tier if the user signals multi-brand, white-labeling, or a very large/robust component library — for a single-brand project it adds complexity without payoff, so don't even surface it. If the user opts in, component tokens alias onto semantic tokens (e.g. bg/primary → {bg/emphasis}).
  • Collection structure — two tiers, but one collection per category per tier, never one giant Primitives + one giant Semantic. In Figma a mode axis (Light/Dark, Desktop/Mobile, Brand) belongs to the collection, so every variable in a collection is forced to share its modes. Putting space in the same collection as color drags spacing into Light/Dark, which is meaningless. Default layout (single-brand): private primitive collections _Color/Primitive, _Typography/Primitive, _Radius/Primitive, _Border/Primitive, and a public Spacing/Primitive; published semantic collections Color/Semantic (Light/Dark), Spacing/Semantic, Typography/Semantic, Radius/Semantic, Border/Semantic. Privacy is the leading-_ prefix. size/icon/* lives in Spacing/Primitive; don't create a Sizing collection unless control heights/avatars become real tokens.
  • Multi-brand — keep two axes in two collections so they don't multiply. Brand lives on _Color/Primitive (modes = Brand A, Brand B = raw palettes); Theme lives on Color/Semantic (modes = Light, Dark). A frame sets both independently and Figma resolves bg/default(Light) → {gray/50} → Brand A's gray. This keeps each collection ≤2 modes — under the Figma Professional cap of 4 modes/collection. (Brand-on-primitive assumes brands differ in raw palette, same role mapping; if a brand needs a different mapping, it also becomes a mode on Color/Semantic.)

Show the proposed full structure back to the user and get sign-off before creating anything.

The structural-consistency rule (prevents the "different every time" problem)

Every token concern gets both a primitive and a semantic collection, even when the semantic tier is a 1:1 passthrough today. A dimensional semantic tier (Spacing/Semantic, Radius/Semantic, Border/Semantic) is justified by a plausible future mode axis — e.g. adding Desktop/Mobile to spacing later — which only works if the semantic collection already exists to carry that axis. Building every concern the same way is also what makes the output consistent run-to-run instead of an arbitrary guess about which categories to duplicate.

The one guardrail: consistency does not license invented roles. Semantic names must be real usage roles (inset/md, width/focus, text/primary), never just renamed primitive steps (space/12, width/1). A passthrough role with a meaningful name is fine; a fake role nobody applies is not. If you can't name a genuine role for a category, give it semantic roles that map to actual usage rather than mirroring the primitive scale step-for-step.

Step 1.5 — Brownfield: refine existing variables in place (don't rebuild)

If this is a retrofit (tokens.intakeMode: "retrofit", or the file already has variables), do not create a fresh set on top of the old one. Refine what exists, in place. Read ${CLAUDE_PLUGIN_ROOT}/references/brownfield-retrofit.md first — guardrail 3 is the whole point of this branch.

The hard rule (guardrail 3): rename and realign variables in place to preserve their Figma IDs. Never delete-and-recreate — every binding (a populated file can have thousands) references the variable id, so recreating under the same name still unbinds everything. A rename keeps the id; a delete+create does not.

The refine loop:

  1. Read what exists (read discipline). Use figma_get_variables after await figma.loadAllPagesAsync(). List the existing collections, variables, and values. Treat a 0/empty first read as suspect — re-read before concluding the file is empty.
  2. Snapshot bindings before. Run the binding-survival audit in ${CLAUDE_PLUGIN_ROOT}/references/figma-scripting.md to record the total consuming binding count before any change. This is your guardrail-3 tripwire.
  3. Rename / realign in place. Use the rename operation that keeps the id (e.g. figma_rename_variable / setting variable.name), and update values in place. Map old names to the new two-tier scheme; record each old→new mapping (the crosswalk consumes it later via token-crosswalk-builder).
  4. Snapshot bindings after, and assert equality. Re-run the binding-survival audit. The total must be unchanged. A drop means a binding was severed — STOP, find the delete-and-recreate that slipped in, and restore from the baseline.
  5. Add genuinely-new variables (the added rows) as normal creates — only the existing ones must be renamed rather than recreated.

After the refine loop, continue with the normal tiers below for any net-new structure, but skip recreating anything that already exists. The primitive/semantic checkpoint (Step 2's PAUSE) still applies: lock names before building dependent semantics.

Step 2 — Build the PRIMITIVE tier, then PAUSE

Create the per-category primitive collections and their variables via a scripted loop on the active write mechanism. Default set:

  • _Color/Primitive — color ramps (gray/50…900, brand/50…900, success/warning/danger, white, black).
  • Spacing/Primitive — the spacing scale (space/0,2,4,8,12,16,24,32,48,64) plus size/icon/{sm,md,lg}.
  • _Typography/Primitive — family/*, size/*, weight/*, lineHeight/*, letterSpacing/*.
  • _Radius/Primitive — radius/{none,sm,md,lg,xl,full}.
  • _Border/Primitive — width/{0,1,2,4}. Do not skip border width — borders need a width primitive, not only a color.

Privacy: the leading-_ prefix hides a collection from the published library. Keep color/type/radius/border primitives private (they're always consumed through semantics or styles). Make Spacing/Primitive public (no underscore) — spacing semantics are intentionally minimal, so designers will grab raw space/* for one-off gaps. Note the trade-off in your checkpoint summary: a value applied directly from Spacing/Primitive is frozen across device modes; only Spacing/Semantic carries future Desktop/Mobile responsiveness.

Modes at the primitive tier: primitives are usually mode-free (a single Value mode). The exception is multi-brand: give _Color/Primitive a Brand mode axis (Brand A, Brand B) holding each brand's raw palette. All other primitive collections stay single-mode.

Then stop and checkpoint. This is the critical seam: semantic tokens are about to alias onto these primitives, so the primitive names and values must be right before you build on them. Show the user the created collections — summarize the ramps and scales, note which are public vs private, and if helpful mention the token-sheet-builder skill can render them visually later. Ask for explicit confirmation: "Here are your primitives. Once you're happy, I'll build the semantic layer on top — and after that, renaming primitives gets disruptive, so this is the moment to adjust names or values."

Update the manifest: tokens.primitivesBuilt = true, add every created collection name to tokens.collections.

Do not proceed to the semantic tier until the user confirms.

Step 3 — Build the SEMANTIC tier as aliases

Create the per-category semantic collections, where every variable references a primitive, not a literal value. Bind each semantic variable to its primitive so the alias is live. Default set and modes:

  • Color/Semantic — modes Light, Dark (+ a Brand mode only if a brand needs a different mapping, not just a different palette). Roles: bg/{default, subtle,muted,emphasis,inverse}, text/{primary,secondary,disabled,inverse, link,onEmphasis}, border/{default,subtle,focus,emphasis}, status/{success,warning,danger}/{bg,text,border}, shadow/{ambient,key}. The shadow/* roles are the colors the elevation effect styles consume (Step 4) — they carry alpha and are mode-aware: a low-alpha near-black in Light, and a darker/higher-alpha value in Dark, so toggling modes recolors every elevation. They live here in Color/Semantic (rather than as part of the effect style) precisely so the Light/Dark switch reaches them; alias a near-black primitive where the alpha allows, otherwise set the rgba per mode directly. text/onEmphasis is the label/icon color that sits on bg/emphasis (a primary button's text, a filled badge) — it must contrast with the emphasis fill in every mode, so it's a distinct role, not text/inverse (which flips with the theme and won't reliably contrast a brand-colored emphasis fill). Component-builder binds primary/ filled control labels to this role; without it, builds fall back to literals and break the "everything bound" audit.
  • Spacing/Semantic — single Default mode now, structured so Desktop/Mobile can be added later. Roles: inset/{xs,sm,md,lg,xl}, stack/*, inline/*.
  • Typography/Semantic — single Default mode (room for Desktop/Mobile). Roles: size/{body,bodyLg,heading/sm…xl,caption} and role line-heights; these feed the text styles built in Step 4.
  • Radius/Semantic — single mode. Roles: control, card, pill, field.
  • Border/Semantic — single mode. Roles: width/{default,focus,emphasis} aliasing _Border/Primitive widths, plus offset/focus (the gap between a control's edge and its focus ring, the outline-offset equivalent) aliasing the 2 width primitive. It's used by the outline-style focus recipe (e.g. vanilla-css) to keep the ring clear of the edge for accessibility; library idioms that render focus as a spread-shadow ring don't need it — see "State handling" in the component standards for the per-library recipes.
  • Opacity/Semantic (when the system needs disabled/overlay states) — single mode. Roles: disabled, muted, overlay/scrim, hover, aliasing _Opacity/Primitive. See the opacity scale rule below — this is the one category where the Figma value scale is a trap.

⚠️ Opacity is stored 0–100, NOT 0–1 (critical). When a variable is bound to a node's opacity field, Figma treats the value as a 0–100 percentage and divides by 100. So an opacity primitive must be created on the 0–100 scale: _Opacity/Primitive 40 has the value 40 (which renders as 0.4), never 0.4 (which would render as 0.004 — effectively invisible). Name and value must match (40 = 40), and a bound disabled/overlay element renders correctly. The sync layer is responsible for the inverse: CSS opacity is 0–1, so it ÷100 on emit — token-builder and token-sync-layer are a matched pair here.

These dimensional semantic tiers are often passthroughs today — that's expected under the structural-consistency rule; keep the role names real (inset/md, not space/16).

The color semantic tier is where the Light/Dark switch lives: bg/default → {gray/50} in Light and {gray/900} in Dark. Primitives stay fixed; the semantic aliases differ per mode. This is exactly what lets the sync layer emit :root/.dark for web later.

Mode-application reality (state this to the user): a frame can now carry up to three independent modes — Brand (_Color/Primitive), Theme (Color/Semantic), and later Device (Spacing/Typography). That's the cost of independent axes.

Verify the aliases resolve — use figma_get_variables (filtered to the new semantic collection, resolveAliases: true) to confirm semantic tokens point at primitives, not literals; if you read via a figma_execute script instead, use the async APIs (see the dynamic-page note in Prerequisites). Then checkpoint: show the semantic layer and demonstrate the cascade if useful ("change gray/50 and bg/default follows").

Update the manifest: tokens.semanticBuilt = true, add every semantic collection to tokens.collections.

Step 4 — Build Figma STYLES (the third phase)

Figma variables can't express everything a design system needs. Composed styles must be created as a distinct phase after the variables exist, so they can bind to the tokens you just built rather than duplicating values:

  • Text styles — a full type scale needs one text style per role/size (e.g. Heading/XL, Body/Default, Caption), each composing family + size + weight + line-height + letter-spacing. Bind size and family to the corresponding font.* variables where Figma supports it, so changing a font primitive updates the text styles. A type scale is NOT just variables — these composed text styles are what designers actually apply to text layers.
  • Effect styles — drop shadows and elevation levels (e.g. Elevation/1 through Elevation/5), driven by the shadow scale from the brainstorm. The composition (offset, blur, spread) is what makes this a style rather than a variable — but the shadow color must be bound to the shadow/* Color/Semantic variables, never hardcoded. Figma supports binding the color of a drop shadow inside an effect style to a color variable; do that for every shadow in every elevation level. This is what makes elevations mode-aware: because the bound shadow/* variable carries different values per mode, toggling Light/Dark recolors all elevations automatically. A style with a literal shadow color is the bug — it stays the same color in dark mode. (Heavier elevations may layer two shadows, e.g. an ambient + a key shadow; bind each to shadow/ambient and shadow/key respectively.)
  • Grid styles — layout grids if the user wants them (column/row grids for their breakpoints). Optional; only if requested.

Generate styles via the active write mechanism (scripted where possible). Bind to variables wherever Figma allows so styles consume tokens rather than duplicating them. Checkpoint with the user: show the created styles.

Update the manifest: set tokens.stylesBuilt = true and record which style groups were created in tokens.styleGroups. Append token-builder to completedSkills.

Step 5 — Hand off

Tell the user what's unlocked and offer natural next steps without forcing them: generate a visual stylesheet of all tokens (token-sheet-builder), build an icon system (icon-system-builder), or start on components (component-builder). If they're heading toward code, mention that the token-sync skill will later turn these exact variables into code files — but only when they have a repo, which comes later.

Notes that matter

  • Never flatten semantic into literals. Aliases are the whole point.
  • One collection per category per tier. Modes belong to the collection, so splitting by category is what keeps color's Light/Dark from infecting spacing.
  • Privacy is the _ prefix. Only Spacing/Primitive is public by default; all other primitives are private.
  • Primitives can be multi-mode. Brand lives on _Color/Primitive; the sync layer must emit brand themes from the primitive tier, not just light/dark from semantics.
  • Lock primitive names before building semantics. The checkpoint between tiers exists precisely to prevent rename cascades.
  • Think in DTCG terms even though you're writing Figma variables. Each token has a value and a type (color, dimension, fontFamily, etc.). The sync layer will later normalize these Figma variables into DTCG-format JSON, so keep types clean and consistent — it makes the downstream sync trivial.
  • Token-efficiency: scripted loops via figma_execute, batched per tier — not per-variable tool calls.
  • Brownfield: never delete-and-recreate to rename (guardrail 3). On a retrofit, rename variables in place to preserve their Figma IDs and every binding. Snapshot the binding count before and after each rename and assert it's unchanged — see Step 1.5 and ${CLAUDE_PLUGIN_ROOT}/references/figma-scripting.md.

Keep looking

Skills are one crate of 627,801. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.