jrpease/throughline/token-sync-layer

Claude Code and agent skill by jrpease/throughline

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Sync Figma design variables into code-ready token files via DTCG JSON and Style Dictionary, emitting framework-specific outputs (shadcn/Tailwind, MUI, vanilla CSS, iOS Swift, Android Kotlin, or custom), and set up the reusable "sync figma tokens" command. Use this when the user wants to turn Figma tokens into code, sync design tokens, generate tokens.css or a Tailwind config or tokens.swift, set up a token pipeline, or run "sync figma tokens". Also trigger when the user mentions Style Dictionar…

Install

npx vskill@latest install jrpease/throughline/token-sync-layer

SKILL.md

Token sync layer

Compiles Figma variables into code. The pipeline is one-directional — Figma is the source of truth for token values; code consumes generated artifacts that are never hand-edited:

Figma variables → DTCG JSON → Style Dictionary → adapter output (per platform)

This skill both performs the sync and installs the reusable /sync-figma-tokens command so the user can re-run it anytime tokens change.

Calibrate

Read user.codingLevel (${CLAUDE_PLUGIN_ROOT}/references/coding-level.md) and scale explanation accordingly. The pipeline involves several developer concepts (JSON, build tools, PRs) — for new users, explain each plainly the first time; for comfortable users, be terse. Actions are identical across levels.

Prerequisites (offer to run them, don't bail)

Read the manifest. This skill needs two things:

  1. Tokens in Figma — tokens.semanticBuilt should be true. If no tokens

exist, offer to run token-builder first.

  1. At least a local git repo — workspace.stage must be local-git or

github (so the sync can land changes as a reviewable diff/PR). If it's still folder, this is the soft-nudge seam: offer to run repository-builder to add version history first. Explain plainly why ("the sync writes code files, and git lets you review exactly what changed before keeping it"). If the user is at local-git but not github, strongly recommend connecting a remote (GitHub or similar) so the sync can land as a real reviewable PR — the cleanest review model — but don't force it; a reviewed branch diff works at local-git.

A live Figma connection is also required — do a cheap liveness read; if it fails, offer figma-environment-setup.

Step 1 — Choose target platform(s)

Ask which platform(s) the user is building for. Read ${CLAUDE_PLUGIN_ROOT}/references/sync-adapters.md for the two-tier adapter model.

  • Curated (Tier 1): shadcn, tailwind, mui, vanilla-css, ios-swift.

Vetted presets — high confidence.

  • Generated (Tier 2): any other framework (Ant Design, Chakra, HeroUI,

Android/Kotlin, Flutter, etc.). The skill generates an adapter and verifies it against a real component before trusting it.

Always tell the user which tier they're on. If they name a curated one, say it'll be solid. If they name anything else, be honest: "That's not one I have a vetted preset for — I'll generate an adapter from its conventions and we'll check it against a real component before relying on it." Follow the Tier 2 protocol in the reference, and offer to save a validated generated adapter to packages/tokens/adapters/ (record it in sync.customAdapters) so future syncs reuse it.

A user can target several platforms at once. Record the choices in sync.platforms. The adapter is what absorbs all framework-specific shaping — which is why the Figma variables stayed framework-neutral.

Also set project.uiFramework if it's still null (this or component-builder sets it at first relevance, whichever runs first; the other reads it). If component-builder already set it, the chosen adapter(s) should be consistent with it.

Step 2 — Extract Figma variables to DTCG JSON

The plugin talks to Figma through Console MCP. If figma.mechanism holds anything other than console-mcp, say that value is no longer supported and offer figma-environment-setup to reset it. Using Console MCP's full-design-system extraction (which works on any Figma plan), read every variable collection (there are now several per tier, e.g. _Color/Primitive, Spacing/Primitive, Color/Semantic, …) and normalize them into DTCG-format JSON ($value, $type, with semantic tokens expressed as {group.token} references to primitives, not flattened literals).

Three rules for the multi-collection structure:

  • Iterate N collections, not a fixed two. Don't assume one Primitives +

one Semantic.

  • Single-mode collections are non-themed — a collection with one mode (e.g.

Spacing/Semantic) emits flat values, never a phantom default theme alongside :root/.dark.

  • Primitives can be multi-mode — _Color/Primitive carries a Brand axis, so

emit brand themes from the primitive tier, not just light/dark from semantics. Preserve modes (light/dark/brand/device) as the DTCG structure the adapters expect.

  • Name mapping: collapse the tier/category prefix into clean token names —

_Color/Primitive + gray/500 → color.gray.500 (not color.primitive.gray.500); Color/Semantic + text/primary → color.text.primary. The category drives the top-level group; the tier (and the _) is dropped from the emitted name.

  • Opacity: normalize 0–100 → 0–1 on extraction. Opacity tokens are stored in

Figma on the 0–100 scale (a quirk of how Figma binds variables to a node's opacity field — see the opacity scale rule in token-builder). CSS opacity, Tailwind opacity, and native alpha are all 0–1, so when normalizing any opacity token (the Opacity/* collections, or any FLOAT bound to opacity) divide its value by 100 before writing DTCG (40 → 0.4). Do this once at extraction so every adapter emits a correct 0–1 value; an un-normalized 40 lands as opacity: 40 in CSS and renders the element fully opaque. token-builder and token-sync-layer are a matched pair on this — neither is correct alone.

This DTCG JSON is the canonical intermediate. Write it to a known location in packages/tokens/, one file per mode, because every gate downstream reads a file as a mode. A single-mode system writes packages/tokens/dtcg/tokens.json; a light/dark system writes packages/tokens/dtcg/primitives.json, packages/tokens/dtcg/semantic.light.json and packages/tokens/dtcg/semantic.dark.json — the ramps once, then each mode's semantic aliases in its own file. Nesting the modes as groups inside a single file (color.light.* / color.dark.*) is the layout color-contrast abstains on, and writing a multi-mode system to one tokens.json is what leaves the registered verify:check checking a single mode and reporting that it checked one. Do not trust the MCP's built-in CSS/Tailwind exporters for final output — route through Style Dictionary so the adapter system governs the result.

Brownfield transforms (learned the hard way)

On a retrofit (tokens.intakeMode: "retrofit"), apply these transforms in addition to the opacity normalization above. Each cost real debugging time on a live retrofit — see the guardrails in ${CLAUDE_PLUGIN_ROOT}/references/brownfield-retrofit.md.

  • Alpha as channels, not baked CSS vars (so Tailwind `/opacity` survives). Emit

colors as space-separated channels with a slash-alpha slot — --color-x: 239 68 68; consumed as rgb(var(--color-x) / <alpha-value>) — rather than a finished rgba(...). A baked rgba() can't accept Tailwind's /opacity modifier; the channel form keeps bg-x/50 working after the retrofit.

  • `/opacity` on var-based tokens → `color-mix` or channel alpha (guardrail 6).

Where existing code applies a /opacity modifier to a token that is now a CSS var, you can't fold the alpha into the var. Convert to color-mix(in srgb, var(--color-x) NN%, transparent) (or the channel-alpha form above). Never carry a raw /opacity onto a var-based token.

  • Round float32 noise at the export boundary (guardrail 2). Figma stores values as

float32 and re-quantizes on write, so normalizing inside Figma is a no-op. Round at the export boundary instead — Math.round(v * 100) / 100 as values leave the pipeline — so 0.30000001192092896 lands as 0.3 in the generated files. Do this in the extraction/transform step, never by hand-editing the generated output (guardrail 7).

These are transforms on the values the adapters emit; the adapter presets themselves are unchanged. See ${CLAUDE_PLUGIN_ROOT}/references/sync-adapters.md for where they fit in the adapter output.

Step 3 — Set up Style Dictionary + adapters

Install Style Dictionary pinned to an exact version with pnpm add -D style-dictionary@4.4.0 (or the project's package manager) in packages/tokens/; the generated config is verified against 4.4.0, and a newer release can rename or add stock transforms. Then configure it. For each chosen platform, apply its adapter preset (per ${CLAUDE_PLUGIN_ROOT}/references/sync-adapters.md): register the platform, transform group, format, and outputReferences (true for web → preserves the semantic→primitive cascade; false for native → flattens). Web adapters emit :root/.dark (or [data-theme]); the shadcn adapter also emits a Tailwind preset.

Native targets import the shipped configuration; they do not transcribe it. Copy ${CLAUDE_PLUGIN_ROOT}/scripts/lib/sd-native.mjs into packages/tokens/scripts/lib/ (see Step 4) and call it. The stock ios-swift and compose transform groups emit every px-authored dimension at ×16 its value — valid, compiling, silently wrong — and mishandle color-mix() and dual-node DTCG the same way. Never build a native platform from a stock transformGroup.

js
import StyleDictionary from 'style-dictionary';
import { registerNativeTransforms, nativePlatform, nativeSources }
from './scripts/lib/sd-native.mjs';
registerNativeTransforms(StyleDictionary);
for (const mode of MODES) { // e.g. ['light', 'dark']
const sd = new StyleDictionary({
source: nativeSources(sourcesFor(mode)), // guards against a mode collapse
preprocessors: ['dtcg/resolve-dual-node'],
platforms: {
ios: nativePlatform({ platform: 'ios-swift', buildPath: `ios/${mode}/` }),
// android also requires packageName:
// android: nativePlatform({ platform: 'android-kotlin',
// buildPath: `android/${mode}/`, packageName: 'com.example.tokens' }),
},
});
await sd.buildAllPlatforms();
}

One build per mode combination, and never a glob. Style Dictionary dedupes by dot-path, so a single build over the whole token directory collapses light and dark into whichever file sorted last, silently dropping a mode. Passing each mode's sources through nativeSources turns that into a thrown error naming the colliding paths. The dtcg/resolve-dual-node preprocessor throws too, on its own collision: a dual node's hoisted child renamed to a camel-joined name that an existing sibling or an earlier hoist in the same pass already has. Then run tokens:validate-output against each generated file with that same source list. See ${CLAUDE_PLUGIN_ROOT}/references/native-adapter-config.md.

Execution model — subagent dispatch with model routing. Generating each platform's output is independent and verifiable. If your host supports subagent dispatch, dispatch one `code-executor` per adapter — each produces its platform's files and verifies them (for web and native alike: tokens:validate-output passes — a clean build is not verification, it is the condition under which every known failure mode, web or native, ships silently) — then a `reviewer` to check each before combining. Choose each subagent's model from its role tier per ${CLAUDE_PLUGIN_ROOT}/references/agent-routing.md (code-executor → fast, reviewer → balanced), and only dispatch once each adapter's spec is complete enough to transcribe. If your host has no subagent dispatch, generate and verify each adapter inline instead. For a single platform, run inline either way. This is a code-gen stage, so these subagents may run in parallel — unlike Figma authoring, which is always sequential.

Step 4 — Build and place outputs

Run the Style Dictionary build. Outputs land in packages/tokens/<platform>/ as build artifacts — regenerated every sync, never hand-edited. Wire packages/tokens/package.json to export them so the UI package, Storybook, and any future app consume them.

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
79
Repository forks
11
7-day trend score
0

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/jrpease/throughline/token-sync-layer/badge)](https://verified-skill.com/skills/jrpease/throughline/token-sync-layer)

Something wrong with this skill or its page? Tell us.