A design system is not a component library. It is the structured data layer underneath every visual decision — colours, spacing, typography, elevation — encoded as tokens so they can be consumed by any platform, any framework, any theme, without copying hex values into Slack.
A design system is an agreement between design and engineering, expressed as structured data. Design tokens are the smallest unit: a named value for colour, spacing, font size, border radius, shadow, or any other visual property. Instead of #2563EB scattered across fifty files, you have --color-primary defined once and referenced everywhere.
Tokens come in layers. Global tokens define the raw palette (blue-500). Alias tokens assign semantic meaning (color-action-primary). Component tokens scope to a surface (button-bg). This layering is what makes multi-theme and multi-brand possible without rewriting components.
CSS custom properties are the runtime format — every browser understands them, they cascade, they respond to media queries and selectors. Style Dictionary (by Amazon) is the build-time format — you define tokens as JSON or YAML and it generates CSS, SCSS, Swift, Kotlin, whatever you need. Figma Variables are the design-time format — they map directly to tokens and support modes (light/dark, brand A/brand B).
The workflow: define tokens in a canonical format, transform them for each target platform, consume them in components. Three stages, one source of truth.
/* tokens/color.json — Style Dictionary source */ { "color": { "base": { "blue": { "50": { "value": "#EFF6FF" }, "500": { "value": "#3B82F6" }, "900": { "value": "#1E3A5F" } } }, "action": { "primary": { "value": "{color.base.blue.500}" }, "primary-hover": { "value": "{color.base.blue.600}" } }, "surface": { "default": { "value": "#0B1120" }, "raised": { "value": "#121D33" } } } }
/* style-dictionary.config.json */ { "source": ["tokens/**/*.json"], "platforms": { "css": { "transformGroup": "css", "buildPath": "dist/css/", "files": [{ "destination": "tokens.css", "format": "css/variables" }] }, "ts": { "transformGroup": "js", "buildPath": "dist/ts/", "files": [{ "destination": "tokens.ts", "format": "javascript/es6" }] } } }
/* Generated output: dist/css/tokens.css */ :root { --color-base-blue-50: #EFF6FF; --color-base-blue-500: #3B82F6; --color-base-blue-900: #1E3A5F; --color-action-primary: var(--color-base-blue-500); --color-action-primary-hover: var(--color-base-blue-600); --color-surface-default: #0B1120; --color-surface-raised: #121D33; } /* Theme override — dark/light via data attribute */ [data-theme="light"] { --color-surface-default: #F1F5F9; --color-surface-raised: #FFFFFF; --color-action-primary: #2563EB; }
Component consumption is the easy part. Once tokens exist as CSS custom properties, components just reference them. No imports, no build step, no framework dependency. A button's background is var(--color-action-primary) and it doesn't know or care which theme is active.
/* Component consuming tokens — zero coupling to values */ .btn-primary { background: var(--color-action-primary); color: var(--color-text-on-action); padding: var(--space-3) var(--space-5); border-radius: var(--radius-md); font-size: var(--text-sm); font-weight: var(--font-weight-medium); transition: background 0.2s ease; } .btn-primary:hover { background: var(--color-action-primary-hover); }
Calling a token --blue-500 is fine for the palette. Calling a semantic token --header-blue locks you into a colour that might change next quarter. Use role-based names: --color-action-primary, not --button-blue.
If anyone can add a token to the file, you'll have 400 tokens in six months and nobody will know which ones are canonical. Assign ownership. Review token PRs the same way you review API changes.
Designers update Figma Variables; engineers update the JSON. Neither tells the other. Automate the sync — Figma REST API to extract variables, CI to generate tokens — or accept that they will diverge within weeks.
Not everything needs a token. A one-off margin-top: 2px nudge on a specific icon is not a spacing decision — it's a fix. Tokenise decisions that repeat. Leave tweaks as values.