| name | design-system |
| description | Design system architecture: design tokens (color, spacing, typography, radius), component library layers (Primitive → Composite → Pattern), theming with CSS Custom Properties and Tailwind, Storybook documentation, and dark mode. The foundation for consistent UI across an entire product. |
Design System Skill
When to Activate
- Starting a new product UI from scratch
- UI feels inconsistent across pages (colors, spacing, typography vary)
- Multiple developers building UI components independently
- Setting up dark mode or multiple themes
- Building a component library
- Documenting components for a team
- Migrating hardcoded color or spacing values to a token-based system that supports theming
- Structuring components across Primitive, Composite, and Pattern layers to avoid circular dependencies
Layer Architecture
Tokens → Raw values (colors, spacing scale, radius, shadows)
Semantic Tokens → Named by purpose (--color-surface, --color-brand-primary)
Primitives → Unstyled, accessible base components (Button, Input, Dialog)
Composites → Styled, opinionated components (SearchBar, UserCard)
Patterns → Full UI sections (EmptyState, DataTable, PageHeader)
Each layer only imports from the layer below it. Never skip layers.
Design Tokens
CSS Custom Properties (recommended — works with any framework)
:root {
--blue-50: #eff6ff;
--blue-100: #dbeafe;
--blue-500: #3b82f6;
--blue-600: #2563eb;
--blue-700: #1d4ed8;
--gray-50: #f9fafb;
--gray-100: #f3f4f6;
--gray-200: #e5e7eb;
--gray-500: #6b7280;
--gray-700: #374151;
--gray-900: #111827;
--red-500: #ef4444;
--green-500: #22c55e;
--yellow-500: #eab308;
--color-brand: var(--blue-600);
--color-brand-hover: var(--blue-700);
--color-brand-subtle: var(--blue-50);
--color-surface: #ffffff;
--color-surface-raised: var(--gray-50);
--color-surface-overlay: var(--gray-100);
--color-text-primary: var(--gray-900);
--color-text-secondary: var(--gray-500);
--color-text-disabled: var(--gray-200);
--color-text-inverse: #ffffff;
--color-text-brand: var(--blue-600);
--color-border: var(--gray-200);
--color-border-strong: var(--gray-500);
--color-border-brand: var(--blue-500);
--color-feedback-error: var(--red-500);
--color-feedback-success: var(--green-500);
--color-feedback-warning: var(--yellow-500);
--color-feedback-info: var(--blue-500);
--color-feedback-error-subtle: #fef2f2;
--color-feedback-success-subtle: #f0fdf4;
}
[data-theme="dark"] {
--color-surface: #0f172a;
--color-surface-raised: #1e293b;
--color-surface-overlay: #334155;
--color-text-primary: #f8fafc;
--color-text-secondary: #94a3b8;
--color-text-disabled: #475569;
--color-border: #334155;
--color-border-strong: #64748b;
}
:root {
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 0.75rem;
--space-4: 1rem;
--space-5: 1.25rem;
--space-6: 1.5rem;
--space-8: 2rem;
--space-10: 2.5rem;
--space-12: 3rem;
--space-16: 4rem;
--spacing-component-xs: var(--space-2);
--spacing-component-sm: var(--space-3);
--spacing-component-md: var(--space-4);
--spacing-component-lg: var(--space-6);
--spacing-section: var(--space-12);
--spacing-page: var(--space-16);
}
:root {
--font-sans: 'Inter', system-ui, -apple-system, sans-serif;
--font-mono: 'JetBrains Mono', 'Fira Code', monospace;
--text-xs: 0.75rem;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
--text-xl: 1.25rem;
--text-2xl: 1.5rem;
--text-3xl: 1.875rem;
--text-4xl: 2.25rem;
--leading-tight: 1.25;
--leading-snug: 1.375;
--leading-normal: 1.5;
--leading-relaxed: 1.625;
--font-normal: 400;
--font-medium: 500;
--font-semibold: 600;
--font-bold: 700;
}
:root {
--radius-sm: 0.25rem;
--radius-md: 0.375rem;
--radius-lg: 0.5rem;
--radius-xl: 0.75rem;
--radius-full: 9999px;
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1);
}
Tailwind Theme Integration
import type { Config } from 'tailwindcss';
export default {
content: ['./src/**/*.{ts,tsx}'],
darkMode: ['selector', '[data-theme="dark"]'],
theme: {
extend: {
colors: {
brand: {
DEFAULT: 'var(--color-brand)',
hover: 'var(--color-brand-hover)',
subtle: 'var(--color-brand-subtle)',
},
surface: {
DEFAULT: 'var(--color-surface)',
raised: 'var(--color-surface-raised)',
overlay: 'var(--color-surface-overlay)',
},
text: {
primary: 'var(--color-text-primary)',
secondary: 'var(--color-text-secondary)',
disabled: 'var(--color-text-disabled)',
inverse: 'var(--color-text-inverse)',
brand: 'var(--color-text-brand)',
},
border: {
DEFAULT: 'var(--color-border)',
strong: 'var(--color-border-strong)',
brand: 'var(--color-border-brand)',
},
: ,
: ,
: ,
},
: {
: ,
: ,
},
: {
: ,
: ,
: ,
: ,
},
: {
: ,
: ,
: ,
},
},
},
} ;
Primitive Components
Primitives are headless (no visual opinions) or minimally styled. They handle accessibility and behavior; Composites handle visual design.
import { forwardRef, ButtonHTMLAttributes } from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
const button = cva(
[
'inline-flex items-center justify-center gap-2',
'font-medium rounded-md',
'transition-colors duration-150',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand focus-visible:ring-offset-2',
'disabled:pointer-events-none disabled:opacity-50',
],
{
variants: {
variant: {
primary: 'bg-brand text-text-inverse hover:bg-brand-hover',
secondary: 'bg-surface-overlay text-text-primary hover:bg-border',
ghost: 'text-text-primary hover:bg-surface-overlay',
danger: 'bg-error text-white hover:bg-red-600',
outline: 'border border-border text-text-primary hover:bg-surface-raised',
},
size: {
sm: 'h-8 px-3 text-sm',
md: 'h-10 px-4 text-sm',
lg: 'h-12 px-6 text-base',
},
},
defaultVariants: { variant: 'primary', size: 'md' },
}
);
interface ButtonProps
<>,
<typeof button> {
?: ;
}
= forwardRef<, >(
(
)
);
. = ;
Dark Mode Toggle
'use client';
import { createContext, useContext, useEffect, useState } from 'react';
type Theme = 'light' | 'dark' | 'system';
const ThemeContext = createContext<{
theme: Theme;
setTheme: (t: Theme) => void;
}>({ theme: 'system', setTheme: () => {} });
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setThemeState] = useState<Theme>('system');
useEffect(() => {
const stored = localStorage.getItem('theme') as Theme | null;
if (stored) setThemeState(stored);
}, []);
useEffect(() => {
const root = document.documentElement;
const isDark =
theme === 'dark' ||
(theme === 'system' && .().);
root.(, isDark ? : );
.(, theme);
}, [theme]);
(
);
}
= () => ();
Storybook Documentation
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
const meta: Meta<typeof Button> = {
component: Button,
tags: ['autodocs'],
argTypes: {
variant: { control: 'select' },
size: { control: 'select' },
loading: { control: 'boolean' },
disabled: { control: 'boolean' },
},
};
export default meta;
type Story = StoryObj<typeof Button>;
export const Primary: Story = { args: { children: 'Button', variant: 'primary' } };
export const Secondary: = { : { : , : } };
: = { : { : , : } };
: = { : { : , : } };
: = {
: (
),
};
Checklist