Token Pipeline (Figma → DTCG → dist)
How design tokens flow from Figma to compiled CSS/SCSS/Tailwind output.
Fetch
handoff-app fetchPulls the raw Figma export, local styles (color, typography, effects) and component
instance/variant data, and writes it to the project's tokens.json snapshot
({exportsOutputDirectory}/{figma_project_id}/tokens.json, exported/<project-id>/tokens.json
by default). This snapshot is the input the rest of the pipeline builds on.
`fetch` alone doesn't produce what `push` sends
The tokens step of push/push:all reads public/api/tokens.json, a different file, written
by handoff-app start / dev / build:app when they persist the client config. fetch never
writes it. So a fresh fetch followed straight by push:tokens either pushes the previous
snapshot or fails with "No tokens snapshot found" — run a build (or start) in between.
Build (tokens:build)
handoff-app tokens:buildRuns in two steps:
-
Auto-migration: if
design-system/tokens/is empty or missing but a Figma snapshot exists, Handoff migrates the legacy raw export into DTCG 2025.10 format underdesign-system/tokens/{primitive,semantic}/. This runs automatically on first use; pass--skip-migrateto opt out. If abrands.sharedCss+brands.entriesconfig is set (for hand-authored brand CSS custom properties instead of Figma-exported color styles), those files are parsed and merged into the manifest at this step too. -
Style Dictionary transform: every
*.tokens.jsonfile underdesign-system/tokens/(including any hand-authored files, e.g. a hand-writtenspacing.tokens.json) is run through Style Dictionary v4 and written todesign-system/dist/:Output Format css/tokens.cssCSS custom properties scss/_tokens.scssSass variables tailwind/theme.cssTailwind 4 @theme {}blockdtcg/tokens.resolved.jsonAlias-resolved DTCG passthrough If no brand is configured, Handoff synthesizes a
defaultbrand from the resolved color tokens so the Colors foundation page always has something to render.
Hand-authored tokens survive migration
Some token categories, spacing is the common example, are hand-authored rather than
Figma-extracted. They're safe because the migration step is all-or-nothing, not a merge:
needsMigration() returns false as soon as it finds any *.tokens.json anywhere under
design-system/tokens/, so the migration is skipped entirely and nothing is overwritten. The
Style Dictionary step then picks up every file it finds, hand-authored ones included.
Push
handoff-app push:tokens # or push:all, which includes this stepSends the compiled DTCG manifest + dist output to POST /api/registry/dtcg, stored in the
registry's singleton handoff_registry_dtcg row, plus the raw Figma snapshot to
POST /api/registry/tokens. See API → /api/registry for both
endpoints.
Consumption
Foundation pages (/foundations/colors, /typography, etc.) and the TokenOutputTabs component
read compiled token output through getDataProvider(), StaticDataProvider reads
design-system/dist/ off the filesystem in workspace dev mode; DynamicDataProvider reads the
handoff_registry_dtcg row in registry mode. Same rendering code, same data shape, either mode,
see Concepts → Tokens for the page-level detail and
Architecture → The DATABASE_URL switch for the
provider pattern itself.