Design tokens are the atomic values of your design system—colors, typography, spacing, and more. This guide shows how to implement and scale design tokens effectively.
What Are Design Tokens?#
Design tokens are named entities storing visual design attributes:
1{
2 "color": {
3 "primary": {
4 "value": "#2563eb",
5 "type": "color"
6 },
7 "text": {
8 "default": { "value": "#1f2937", "type": "color" },
9 "muted": { "value": "#6b7280", "type": "color" }
10 }
11 },
12 "spacing": {
13 "xs": { "value": "4px", "type": "dimension" },
14 "sm": { "value": "8px", "type": "dimension" },
15 "md": { "value": "16px", "type": "dimension" },
16 "lg": { "value": "24px", "type": "dimension" },
17 "xl": { "value": "32px", "type": "dimension" }
18 }
19}Token Architecture#
Three-Tier Token System#
┌─────────────────────────────────────────────────────────┐
│ SEMANTIC TOKENS (Component-level) │
│ button-primary-background → color-brand-500 │
│ card-shadow → shadow-md │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ ALIAS TOKENS (Theme-level) │
│ color-brand-500 → color-blue-500 │
│ color-text-primary → color-gray-900 │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ PRIMITIVE TOKENS (Raw values) │
│ color-blue-500 → #3b82f6 │
│ spacing-4 → 16px │
└─────────────────────────────────────────────────────────┘
Token File Structure#
tokens/
├── primitives/
│ ├── colors.json
│ ├── typography.json
│ ├── spacing.json
│ └── shadows.json
├── semantic/
│ ├── light.json
│ └── dark.json
├── components/
│ ├── button.json
│ ├── input.json
│ └── card.json
└── index.js
Defining Tokens#
Color Tokens#
1// tokens/primitives/colors.json
2{
3 "color": {
4 "blue": {
5 "50": { "value": "#eff6ff" },
6 "100": { "value": "#dbeafe" },
7 "200": { "value": "#bfdbfe" },
8 "300": { "value": "#93c5fd" },
9 "400": { "value": "#60a5fa" },
10 "500": { "value": "#3b82f6" },
11 "600": { "value": "#2563eb" },
12 "700": { "value": "#1d4ed8" },
13 "800": { "value": "#1e40af" },
14 "900": { "value": "#1e3a8a" }
15 },
16 "gray": {
17 "50": { "value": "#f9fafb" },
18 "100": { "value": "#f3f4f6" },
19 "500": { "value": "#6b7280" },
20 "900": { "value": "#111827" }
21 }
22 }
23}Semantic Tokens with References#
1// tokens/semantic/light.json
2{
3 "color": {
4 "background": {
5 "default": { "value": "{color.white}" },
6 "subtle": { "value": "{color.gray.50}" },
7 "muted": { "value": "{color.gray.100}" }
8 },
9 "text": {
10 "default": { "value": "{color.gray.900}" },
11 "muted": { "value": "{color.gray.500}" },
12 "inverse": { "value": "{color.white}" }
13 },
14 "brand": {
15 "default": { "value": "{color.blue.600}" },
16 "hover": { "value": "{color.blue.700}" },
17 "active": { "value": "{color.blue.800}" }
18 }
19 }
20}1// tokens/semantic/dark.json
2{
3 "color": {
4 "background": {
5 "default": { "value": "{color.gray.900}" },
6 "subtle": { "value": "{color.gray.800}" },
7 "muted": { "value": "{color.gray.700}" }
8 },
9 "text": {
10 "default": { "value": "{color.gray.50}" },
11 "muted": { "value": "{color.gray.400}" },
12 "inverse": { "value": "{color.gray.900}" }
13 },
14 "brand": {
15 "default": { "value": "{color.blue.500}" },
16 "hover": { "value": "{color.blue.400}" },
17 "active": { "value": "{color.blue.300}" }
18 }
19 }
20}Typography Tokens#
1// tokens/primitives/typography.json
2{
3 "font": {
4 "family": {
5 "sans": { "value": "Inter, system-ui, sans-serif" },
6 "mono": { "value": "JetBrains Mono, monospace" }
7 },
8 "size": {
9 "xs": { "value": "0.75rem" },
10 "sm": { "value": "0.875rem" },
11 "base": { "value": "1rem" },
12 "lg": { "value": "1.125rem" },
13 "xl": { "value": "1.25rem" },
14 "2xl": { "value": "1.5rem" },
15 "3xl": { "value": "1.875rem" },
16 "4xl": { "value": "2.25rem" }
17 },
18 "weight": {
19 "normal": { "value": "400" },
20 "medium": { "value": "500" },
21 "semibold": { "value": "600" },
22 "bold": { "value": "700" }
23 },
24 "lineHeight": {
25 "tight": { "value": "1.25" },
26 "normal": { "value": "1.5" },
27 "relaxed": { "value": "1.75" }
28 }
29 }
30}Component Tokens#
1// tokens/components/button.json
2{
3 "button": {
4 "primary": {
5 "background": { "value": "{color.brand.default}" },
6 "backgroundHover": { "value": "{color.brand.hover}" },
7 "backgroundActive": { "value": "{color.brand.active}" },
8 "text": { "value": "{color.text.inverse}" },
9 "borderRadius": { "value": "{radius.md}" }
10 },
11 "secondary": {
12 "background": { "value": "transparent" },
13 "backgroundHover": { "value": "{color.background.subtle}" },
14 "text": { "value": "{color.text.default}" },
15 "border": { "value": "{color.border.default}" }
16 },
17 "padding": {
18 "sm": { "value": "{spacing.2} {spacing.3}" },
19 "md": { "value": "{spacing.2} {spacing.4}" },
20 "lg": { "value": "{spacing.3} {spacing.6}" }
21 }
22 }
23}Transforming Tokens#
Using Style Dictionary#
1// style-dictionary.config.js
2const StyleDictionary = require('style-dictionary');
3
4module.exports = {
5 source: ['tokens/**/*.json'],
6 platforms: {
7 css: {
8 transformGroup: 'css',
9 buildPath: 'dist/css/',
10 files: [
11 {
12 destination: 'variables.css',
13 format: 'css/variables',
14 options: {
15 outputReferences: true,
16 },
17 },
18 ],
19 },
20 js: {
21 transformGroup: 'js',
22 buildPath: 'dist/js/',
23 files: [
24 {
25 destination: 'tokens.js',
26 format: 'javascript/es6',
27 },
28 ],
29 },
30 scss: {
31 transformGroup: 'scss',
32 buildPath: 'dist/scss/',
33 files: [
34 {
35 destination: '_variables.scss',
36 format: 'scss/variables',
37 },
38 ],
39 },
40 },
41};Generated CSS Output#
1/* dist/css/variables.css */
2:root {
3 /* Primitives */
4 --color-blue-500: #3b82f6;
5 --color-blue-600: #2563eb;
6 --color-gray-900: #111827;
7
8 /* Semantic */
9 --color-brand-default: var(--color-blue-600);
10 --color-text-default: var(--color-gray-900);
11
12 /* Components */
13 --button-primary-background: var(--color-brand-default);
14 --button-primary-text: var(--color-text-inverse);
15}
16
17[data-theme="dark"] {
18 --color-brand-default: var(--color-blue-500);
19 --color-text-default: var(--color-gray-50);
20}Integration with Tailwind#
Extending Tailwind Config#
1// tailwind.config.js
2const tokens = require('./dist/js/tokens');
3
4module.exports = {
5 theme: {
6 extend: {
7 colors: {
8 brand: {
9 DEFAULT: 'var(--color-brand-default)',
10 hover: 'var(--color-brand-hover)',
11 active: 'var(--color-brand-active)',
12 },
13 background: {
14 DEFAULT: 'var(--color-background-default)',
15 subtle: 'var(--color-background-subtle)',
16 muted: 'var(--color-background-muted)',
17 },
18 },
19 spacing: tokens.spacing,
20 fontSize: tokens.fontSize,
21 fontFamily: {
22 sans: tokens.fontFamily.sans,
23 mono: tokens.fontFamily.mono,
24 },
25 },
26 },
27};Using in Components#
1// Button.jsx
2function Button({ variant = 'primary', size = 'md', children }) {
3 const baseClasses = 'inline-flex items-center justify-center font-medium rounded-md transition-colors';
4
5 const variants = {
6 primary: 'bg-brand text-white hover:bg-brand-hover active:bg-brand-active',
7 secondary: 'bg-transparent border border-border-default text-text-default hover:bg-background-subtle',
8 };
9
10 const sizes = {
11 sm: 'px-3 py-1.5 text-sm',
12 md: 'px-4 py-2 text-base',
13 lg: 'px-6 py-3 text-lg',
14 };
15
16 return (
17 <button className={`${baseClasses} ${variants[variant]} ${sizes[size]}`}>
18 {children}
19 </button>
20 );
21}Theme Switching#
CSS Variables Approach#
1// ThemeProvider.jsx
2import { createContext, useContext, useEffect, useState } from 'react';
3
4const ThemeContext = createContext();
5
6export function ThemeProvider({ children }) {
7 const [theme, setTheme] = useState('light');
8
9 useEffect(() => {
10 document.documentElement.setAttribute('data-theme', theme);
11 }, [theme]);
12
13 return (
14 <ThemeContext.Provider value={{ theme, setTheme }}>
15 {children}
16 </ThemeContext.Provider>
17 );
18}
19
20export const useTheme = () => useContext(ThemeContext);System Preference Detection#
1// hooks/useSystemTheme.js
2import { useEffect, useState } from 'react';
3
4export function useSystemTheme() {
5 const [systemTheme, setSystemTheme] = useState('light');
6
7 useEffect(() => {
8 const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
9 setSystemTheme(mediaQuery.matches ? 'dark' : 'light');
10
11 const handler = (e) => setSystemTheme(e.matches ? 'dark' : 'light');
12 mediaQuery.addEventListener('change', handler);
13
14 return () => mediaQuery.removeEventListener('change', handler);
15 }, []);
16
17 return systemTheme;
18}Syncing with Figma#
Tokens Studio Plugin#
1// tokens.json (Figma Tokens Studio format)
2{
3 "global": {
4 "colors": {
5 "blue": {
6 "500": {
7 "value": "#3b82f6",
8 "type": "color"
9 }
10 }
11 }
12 },
13 "light": {
14 "brand": {
15 "primary": {
16 "value": "{global.colors.blue.500}",
17 "type": "color"
18 }
19 }
20 }
21}Automated Sync Workflow#
1# .github/workflows/sync-tokens.yml
2name: Sync Design Tokens
3
4on:
5 push:
6 paths:
7 - 'tokens/**'
8
9jobs:
10 build:
11 runs-on: ubuntu-latest
12 steps:
13 - uses: actions/checkout@v4
14
15 - name: Setup Node
16 uses: actions/setup-node@v4
17 with:
18 node-version: '20'
19
20 - name: Install dependencies
21 run: npm ci
22
23 - name: Build tokens
24 run: npm run build:tokens
25
26 - name: Commit changes
27 run: |
28 git config --local user.email "action@github.com"
29 git config --local user.name "GitHub Action"
30 git add dist/
31 git diff --staged --quiet || git commit -m "chore: update compiled tokens"
32 git pushBest Practices#
1. Naming Conventions#
1{
2 // Good: Semantic naming
3 "color-text-primary": "#111827",
4 "color-background-elevated": "#ffffff",
5
6 // Avoid: Value-based naming
7 "color-dark-gray": "#111827",
8 "color-white": "#ffffff"
9}2. Documentation#
1{
2 "color": {
3 "brand": {
4 "primary": {
5 "value": "#2563eb",
6 "description": "Primary brand color for buttons and links",
7 "type": "color"
8 }
9 }
10 }
11}3. Versioning#
1// package.json
2{
3 "name": "@company/design-tokens",
4 "version": "2.1.0",
5 "main": "dist/js/tokens.js",
6 "files": ["dist"]
7}Testing Tokens#
1// tokens.test.js
2import tokens from './dist/js/tokens';
3
4describe('Design Tokens', () => {
5 test('all colors are valid hex values', () => {
6 const hexRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
7
8 Object.values(tokens.color).forEach(color => {
9 expect(color).toMatch(hexRegex);
10 });
11 });
12
13 test('spacing values are consistent', () => {
14 const spacingValues = Object.values(tokens.spacing);
15 spacingValues.forEach((value, index) => {
16 if (index > 0) {
17 expect(parseInt(value)).toBeGreaterThan(parseInt(spacingValues[index - 1]));
18 }
19 });
20 });
21});Conclusion#
Design tokens bridge the gap between design and development. Start with primitives, build semantic layers, and use tools like Style Dictionary to transform tokens for each platform. Consistent tokens enable scalable design systems that maintain visual coherence across products.