Pilot, imagine that every ship in the galactic fleet uses exactly the same status colors, the same spacing between panels, and the same typography on monitors. This is no coincidence -- it's a design system. In the frontend world, the equivalent is design tokens -- a central source of truth for all visual values in the application.
Design tokens are named values representing design decisions: colors, spacing, font sizes, shadows, border radii. Instead of scattering magic numbers throughout the code, you define them in one place:
1// tokens.js -- central source of truth
2const tokens = {
3 colors: {
4 primary: '#7c4dff',
5 primaryLight: '#b388ff',
6 primaryDark: '#651fff',
7 success: '#00e676',
8 warning: '#ffa726',
9 danger: '#f44336',
10 bgDark: '#0a0a2e',
11 bgSurface: '#1a1a4e',
12 textPrimary: '#e0e0e0',
13 textMuted: '#9e9e9e',
14 border: '#3949ab',
15 },
16 spacing: {
17 xs: '4px',
18 sm: '8px',
19 md: '16px',
20 lg: '24px',
21 xl: '32px',
22 xxl: '48px',
23 },
24 fontSize: {
25 xs: '12px',
26 sm: '14px',
27 md: '16px',
28 lg: '20px',
29 xl: '28px',
30 xxl: '36px',
31 },
32 borderRadius: {
33 sm: '4px',
34 md: '8px',
35 lg: '12px',
36 xl: '20px',
37 full: '9999px',
38 },
39 shadows: {
40 sm: '0 2px 4px rgba(0,0,0,0.1)',
41 md: '0 4px 12px rgba(0,0,0,0.2)',
42 lg: '0 8px 24px rgba(0,0,0,0.3)',
43 glow: '0 0 20px rgba(124,77,255,0.3)',
44 },
45};Once you have tokens defined, you use them instead of direct values:
1import { tokens } from './tokens';
2
3// In inline styles
4function Card({ children }) {
5 return (
6 <div style={{
7 background: tokens.colors.bgSurface,
8 padding: tokens.spacing.lg,
9 borderRadius: tokens.borderRadius.lg,
10 border: `1px solid ${tokens.colors.border}`,
11 }}>
12 {children}
13 </div>
14 );
15}1// In styled-components
2const Card = styled.div`
3 background: ${tokens.colors.bgSurface};
4 padding: ${tokens.spacing.lg};
5 border-radius: ${tokens.borderRadius.lg};
6 border: 1px solid ${tokens.colors.border};
7`;The best approach is to combine JavaScript tokens with CSS Variables -- you define tokens in JS (easy to manage) and pass them to CSS as variables (efficient rendering):
1function applyTokens(tokens) {
2 const vars = {};
3 vars['--color-primary'] = tokens.colors.primary;
4 vars['--color-bg'] = tokens.colors.bgDark;
5 vars['--color-surface'] = tokens.colors.bgSurface;
6 vars['--color-text'] = tokens.colors.textPrimary;
7 vars['--spacing-md'] = tokens.spacing.md;
8 vars['--spacing-lg'] = tokens.spacing.lg;
9 vars['--radius-lg'] = tokens.borderRadius.lg;
10 return vars;
11}
12
13function App() {
14 return (
15 <div style={applyTokens(tokens)} className="app">
16 {/* CSS uses var(--color-primary) etc. */}
17 </div>
18 );
19}A good color system has structure:
1const colorSystem = {
2 // Base colors
3 blue: { 50: '#e3f2fd', 100: '#bbdefb', 500: '#2196f3', 900: '#0d47a1' },
4 purple: { 50: '#f3e5f5', 100: '#e1bee7', 500: '#9c27b0', 900: '#4a148c' },
5
6 // Semantic colors (used in code)
7 primary: 'var(--blue-500)',
8 success: 'var(--green-500)',
9 warning: 'var(--orange-500)',
10 danger: 'var(--red-500)',
11
12 // Surface colors
13 background: '#0a0a2e',
14 surface: '#1a1a4e',
15 surfaceHover: '#2a2a5e',
16};Semantic colors (primary, success, danger) are separated from base colors (blue-500, red-400). This way, changing the theme is just a mapping change, not searching through the entire codebase.
Spacing based on a scale -- each next value is a multiple of the base:
1// 4px scale (Tailwind-style)
2const spacing = {
3 0: '0px',
4 1: '4px',
5 2: '8px',
6 3: '12px',
7 4: '16px',
8 5: '20px',
9 6: '24px',
10 8: '32px',
11 10: '40px',
12 12: '48px',
13 16: '64px',
14};Using a scale instead of arbitrary values guarantees visual consistency -- elements on the screen are evenly spaced, giving a professional look.
1const typography = {
2 fontFamily: {
3 sans: "'Inter', 'Segoe UI', sans-serif",
4 mono: "'Fira Code', monospace",
5 },
6 fontSize: {
7 xs: ['12px', { lineHeight: '16px' }],
8 sm: ['14px', { lineHeight: '20px' }],
9 base: ['16px', { lineHeight: '24px' }],
10 lg: ['20px', { lineHeight: '28px' }],
11 xl: ['28px', { lineHeight: '36px' }],
12 '2xl': ['36px', { lineHeight: '44px' }],
13 },
14 fontWeight: {
15 normal: 400,
16 medium: 500,
17 semibold: 600,
18 bold: 700,
19 },
20};Each font size has an assigned line height -- this eliminates typographic chaos.
An advanced pattern is token composition -- creating more specific tokens from base ones:
1// Base tokens (primitive)
2const primitives = {
3 blue500: '#7c4dff',
4 blue300: '#b388ff',
5 green500: '#00e676',
6 red500: '#f44336',
7 gray900: '#0a0a2e',
8 gray800: '#1a1a4e',
9 gray100: '#e0e0e0',
10 gray400: '#9e9e9e',
11};
12
13// Semantic tokens (composed from base)
14const semantic = {
15 colorPrimary: primitives.blue500,
16 colorPrimaryLight: primitives.blue300,
17 colorSuccess: primitives.green500,
18 colorDanger: primitives.red500,
19 colorBackground: primitives.gray900,
20 colorSurface: primitives.gray800,
21 colorText: primitives.gray100,
22 colorTextMuted: primitives.gray400,
23};
24
25// Component tokens (composed from semantic)
26const components = {
27 buttonPrimaryBg: semantic.colorPrimary,
28 buttonPrimaryText: '#ffffff',
29 buttonDangerBg: semantic.colorDanger,
30 cardBg: semantic.colorSurface,
31 cardBorder: primitives.blue500 + '40',
32};Three layers: base (values) -> semantic (meaning) -> component (usage). Changing a single base token cascades updates everywhere.
In large projects, tokens are stored in JSON format and automatically generated for different platforms using the Style Dictionary tool:
1{
2 "color": {
3 "primary": { "value": "#7c4dff" },
4 "success": { "value": "#00e676" },
5 "background": { "value": "#0a0a2e" }
6 },
7 "spacing": {
8 "sm": { "value": "8px" },
9 "md": { "value": "16px" },
10 "lg": { "value": "24px" }
11 }
12}Style Dictionary generates from this:
One source of truth, multiple output formats.
Creating a
TokenPreview component helps control the entire system:1function TokenPreview({ tokens }) {
2 return (
3 <div>
4 <h2>Colors</h2>
5 {Object.entries(tokens.colors).map(([name, value]) => (
6 <div key={name} style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
7 <div style={{ width: 40, height: 40, background: value, borderRadius: 8 }} />
8 <span>{name}: {value}</span>
9 </div>
10 ))}
11
12 <h2>Spacing</h2>
13 {Object.entries(tokens.spacing).map(([name, value]) => (
14 <div key={name} style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
15 <div style={{ width: value, height: 16, background: '#7c4dff' }} />
16 <span>{name}: {value}</span>
17 </div>
18 ))}
19 </div>
20 );
21}