Design Tokens
Design tokens are the single source of truth for colors, spacing, typography, radii, shadows. Define them once in Sass (or JSON via Style Dictionary), emit as CSS custom properties for runtime theming, and reference everywhere — design and engineering speak the same language.
Token shapes, CSS vars, themes, sync
EXAMPLE
// 1) Token categories
// • Color — brand, semantic (success, danger), surfaces
// • Spacing — scale (0, 1, 2, 4, 8, …)
// • Typography — family, size, weight, line-height
// • Radius — corner sizes
// • Shadow — elevation
// • Border — width, style, color
// • Z-index — stacking layers
// • Motion — duration, easing
// • Breakpoint — screen sizes
// 2) Define tokens — Sass maps
@use 'sass:map';
$colors: (
'brand': (
'50': #eef2ff,
'500': #4f46e5,
'900': #312e81,
),
'gray': (
'50': #f8fafc,
'500': #64748b,
'900': #0f172a,
),
'semantic': (
'success': #22c55e,
'warning': #f59e0b,
'danger': #ef4444,
),
);
$space: (
'0': 0,
'1': 0.25rem,
'2': 0.5rem,
'3': 0.75rem,
'4': 1rem,
'6': 1.5rem,
'8': 2rem,
'12': 3rem,
'16': 4rem,
);
$radius: (
'none': 0,
'sm': 0.125rem,
'md': 0.375rem,
'lg': 0.5rem,
'xl': 1rem,
'full': 9999px,
);
$shadow: (
'sm': '0 1px 2px rgba(0,0,0,0.05)',
'md': '0 4px 6px rgba(0,0,0,0.07), 0 2px 4px rgba(0,0,0,0.06)',
'lg': '0 10px 15px rgba(0,0,0,0.08), 0 4px 6px rgba(0,0,0,0.05)',
);
$font-size: (
'xs': 0.75rem,
'sm': 0.875rem,
'base': 1rem,
'lg': 1.125rem,
'xl': 1.25rem,
'2xl': 1.5rem,
);
// 3) Emit as CSS custom properties
@mixin emit-color-scale($name, $scale) {
@each $shade, $value in $scale {
--color-#{$name}-#{$shade}: #{$value};
}
}
:root {
@include emit-color-scale('brand', map.get($colors, 'brand'));
@include emit-color-scale('gray', map.get($colors, 'gray'));
@each $name, $value in map.get($colors, 'semantic') {
--color-#{$name}: #{$value};
}
@each $name, $value in $space {
--space-#{$name}: #{$value};
}
@each $name, $value in $radius {
--radius-#{$name}: #{$value};
}
@each $name, $value in $shadow {
--shadow-#{$name}: #{$value};
}
@each $name, $value in $font-size {
--text-#{$name}: #{$value};
}
}
// 4) Two-layer tokens — primitive + semantic
// Primitive tokens describe color (brand-500, gray-100)
// Semantic tokens describe USE (text-default, bg-surface, border-strong)
// Components reference SEMANTIC tokens; theming swaps semantic only.
:root {
/* primitive */
--color-gray-50: #f8fafc;
--color-gray-900: #0f172a;
--color-brand-500: #4f46e5;
/* semantic */
--color-text: var(--color-gray-900);
--color-text-muted: var(--color-gray-500);
--color-bg: white;
--color-bg-surface: var(--color-gray-50);
--color-border: var(--color-gray-200);
--color-accent: var(--color-brand-500);
}
.button {
background: var(--color-accent);
color: white;
}
.card {
background: var(--color-bg-surface);
border: 1px solid var(--color-border);
color: var(--color-text);
}
// 5) Dark theme — override semantic only
[data-theme='dark'] {
--color-bg: #0f172a;
--color-bg-surface: #1e293b;
--color-text: #f1f5f9;
--color-text-muted: #94a3b8;
--color-border: #334155;
--color-accent: #818cf8;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme='light']) {
--color-bg: #0f172a;
/* ... */
}
}
// 6) Spacing scale + utility classes
@each $name, $value in $space {
.p-#{$name} { padding: var(--space-#{$name}); }
.px-#{$name} { padding-inline: var(--space-#{$name}); }
.py-#{$name} { padding-block: var(--space-#{$name}); }
.m-#{$name} { margin: var(--space-#{$name}); }
.gap-#{$name} { gap: var(--space-#{$name}); }
}
// 7) Sync to other platforms — Style Dictionary
// JSON tokens.json:
{
"color": {
"brand": {
"500": { "value": "#4f46e5" }
}
},
"space": {
"4": { "value": "1rem" }
}
}
// Run Style Dictionary → produces CSS, Sass, JS, iOS, Android variants from one source.
// One source of truth across web, iOS, Android, Figma.
// 8) Token naming conventions
// • Lowercase, hyphens, scoped: --color-bg-surface
// • Avoid generic names: '--blue' bad; '--color-brand-500' good
// • Semantic tokens describe USE not color: --color-text NOT --color-dark
// • Keep depth shallow: 2-3 levels max
// 9) Documentation
// • Storybook with token gallery
// • Figma library mirrors token names exactly
// • README with rationale per token category
// 10) Versioning + breaking changes
// • SemVer the token package
// • Renaming a token = MAJOR (consumers break)
// • Adding a token = MINOR
// • Changing a value = MINOR (most cases; MAJOR if semantic meaning changes)
// 11) Common bugs
// • Hard-coded hex in components → can't theme; refactor to tokens
// • Token aliasing too deep (5+ vars) → debugging headache; flatten
// • Spacing scale not respected → utility classes drift; lint enforces scale
// • Dark mode missing some surfaces → audit semantic tokens, ensure every visual surface has a token
// • Token name doesn't match Figma → designers + devs out of sync; share Style Dictionary output
// • Color contrast not verified per shade → some shades fail WCAG; calculate luminance
// • Removing tokens without migration path → consumer apps break
// • Tokens shipped as JSON but consumers expect CSS vars only — pick a delivery format and stick with it
Why it matters
Design tokens belong in Sass maps + CSS custom properties: primitive tokens describe colors and sizes, semantic tokens describe usage (text, surface, accent). Components reference semantics; theming swaps semantic values per [data-theme]. Sync to Figma + iOS + Android via Style Dictionary so designers and engineers share one vocabulary.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// Tokens as a Sass map = single source of truth
$spacing: (xs: 4px, sm: 8px, md: 16px);
.card { padding: map-get($spacing, md); }
Try it Yourself »
Discussion
Loading…