Design Systems & Component Libraries
A design system is the difference between a product that feels cohesive and one that feels like it was built by 15 different people on 15 different days. At scale, without a design system, every new feature introduces visual inconsistencies, accessibility gaps, and duplicated work.
What Is a Design System?
A design system is the single source of truth for your product's interface. It's not just a component library. It's an interconnected set of:
| Layer | What It Contains | Purpose |
|---|---|---|
| Design tokens | Colors, spacing, typography, shadows, radii | Shared visual language between design and code |
| Components | Buttons, inputs, cards, modals, tables | Reusable UI building blocks |
| Patterns | Auth flows, search, settings, navigation | Reusable solutions to common UX problems |
| Guidelines | Voice/tone, accessibility standards, writing rules | Consistent decisions across teams |
| Documentation | Usage guidelines, code examples, dos/don'ts | Enables adoption without tribal knowledge |
What a Design System Is NOT
| Common Misconception | Reality |
|---|---|
| "It's a Figma file with components" | That's a UI kit. A design system includes code, documentation, and governance. |
| "It's a CSS framework" | A framework provides generic styles. A design system is specific to your product. |
| "We build it once, then we're done" | A design system is a living product that evolves with your product. |
| "Only big companies need one" | Even a 3-person team benefits from shared tokens and documented components. |
Atomic Design Methodology
Brad Frost's Atomic Design is the most practical mental model for structuring components:
Atoms → Molecules → Organisms → Templates → Pages
Atoms: The smallest building blocks (can't be broken down further)
Button, Input, Label, Icon, Badge, Avatar, Checkbox
Molecules: Groups of atoms working together
Search field (input + button)
Form group (label + input + help text)
Nav item (icon + label)
Organisms: Complex, distinct sections of the interface
Header (logo + nav items + search field + user menu)
Card (image + title + description + actions)
Data table (header row + data rows + pagination)
Templates: Page-level layout structures (no real content)
Dashboard layout, Settings layout, List-detail layout
Pages: Templates filled with real content
The actual screens users see
Practical Component Hierarchy
| Level | Examples | How Many? | Design Effort |
|---|---|---|---|
| Atoms | Button, Input, Badge, Avatar, Icon, Checkbox, Radio, Toggle | 15-25 | Low per component, high total |
| Molecules | Form group, Search bar, Dropdown menu, Card header, Stat card | 10-20 | Medium |
| Organisms | Header, Sidebar, Data table, Form, Card, Modal, Navigation | 8-15 | High |
| Templates | Dashboard, Settings, List view, Detail view, Auth pages | 5-10 | High |
Start here: Define your atoms and core molecules first. Organisms and templates emerge naturally from composing them.
Design Tokens
Design tokens are the atomic values that define your visual language. They're the bridge between design (Figma) and code (CSS/JS).
Token Categories
{
"color": {
"primary": {
"50": "#EBF5FF",
"100": "#CCE5FF",
"500": "#0066CC",
"600": "#0052A3",
"700": "#003D7A"
},
"neutral": {
"0": "#FFFFFF",
"50": "#F8F9FA",
"100": "#F1F3F5",
"500": "#868E96",
"900": "#212529"
},
"semantic": {
"success": "#2F9E44",
"warning": "#F08C00",
"error": "#E03131",
"info": "#1C7ED6"
}
},
"spacing": {
"1": "4px",
"2": "8px",
"3": "12px",
"4": "16px",
"6": "24px",
"8": "32px",
"12": "48px",
"16": "64px"
},
"typography": {
"fontFamily": {
"sans": "Inter, system-ui, sans-serif",
"mono": "JetBrains Mono, monospace"
},
"fontSize": {
"xs": "12px",
"sm": "14px",
"base": "16px",
"lg": "18px",
"xl": "20px",
"2xl": "24px",
"3xl": "32px",
"4xl": "40px"
},
"fontWeight": {
"regular": 400,
"medium": 500,
"semibold": 600,
"bold": 700
},
"lineHeight": {
"tight": 1.2,
"normal": 1.5,
"relaxed": 1.6
}
},
"borderRadius": {
"sm": "4px",
"md": "8px",
"lg": "12px",
"xl": "16px",
"full": "9999px"
},
"shadow": {
"sm": "0 1px 2px rgba(0,0,0,0.05)",
"md": "0 4px 6px rgba(0,0,0,0.07)",
"lg": "0 10px 15px rgba(0,0,0,0.1)",
"xl": "0 20px 25px rgba(0,0,0,0.15)"
}
}
Semantic Tokens (Aliases)
Raw tokens (like primary-500) are implementation details. Semantic tokens describe purpose, making theming and dark mode easier:
:root {
/* Semantic aliases — these are what components reference */
--color-bg-primary: var(--neutral-0); /* Main background */
--color-bg-secondary: var(--neutral-50); /* Card/section backgrounds */
--color-bg-inverse: var(--neutral-900); /* Dark sections */
--color-text-primary: var(--neutral-900); /* Headings, important text */
--color-text-secondary: var(--neutral-500); /* Body text */
--color-text-on-accent: var(--neutral-0); /* Text on colored buttons */
--color-border: var(--neutral-200); /* Default borders */
--color-border-focus: var(--primary-500); /* Focus rings */
--color-action-primary: var(--primary-500); /* Primary buttons, links */
--color-action-hover: var(--primary-600); /* Primary hover state */
}
/* Dark mode — just reassign the aliases */
[data-theme="dark"] {
--color-bg-primary: var(--neutral-900);
--color-bg-secondary: #1E1E1E;
--color-text-primary: var(--neutral-100);
--color-text-secondary: var(--neutral-400);
--color-border: var(--neutral-700);
--color-action-primary: var(--primary-400);
}
Component States
Every interactive component needs these states designed and documented:
Required States
| State | Visual Treatment | When Active |
|---|---|---|
| Default | Base appearance | No interaction |
| Hover | Slight background change, cursor pointer | Mouse over (desktop) |
| Focus | Visible focus ring (2px+ outline) | Keyboard navigation or click |
| Active/Pressed | Slightly darker, pressed-in feel | Mouse down / touch |
| Disabled | Reduced opacity (50-60%), no pointer cursor | Action unavailable |
| Loading | Spinner or skeleton, disabled interaction | Async operation in progress |
| Error | Red border, error icon, error message | Validation failure |
| Success | Green indicator, checkmark | Operation completed |
| Selected | Highlighted background, checkmark | Item chosen from a list |
| Empty | Illustration + helpful text + CTA | No data to display |
State Example: Button
.btn-primary {
/* Default */
background: var(--primary-500);
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
cursor: pointer;
transition: all 150ms ease;
}
.btn-primary:hover {
background: var(--primary-600); /* Slightly darker */
}
.btn-primary:focus-visible {
outline: 2px solid var(--primary-500);
outline-offset: 2px; /* Ring visible around button */
}
.btn-primary:active {
background: var(--primary-700);
transform: scale(0.98); /* Subtle press effect */
}
.btn-primary:disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none; /* Prevents click AND hover */
}
.btn-primary.is-loading {
color: transparent; /* Hide text */
pointer-events: none;
position: relative;
}
.btn-primary.is-loading::after {
content: '';
/* Spinner styles */
}
Designing Empty States
Empty states are often overlooked but they're the first thing new users see. A good empty state includes:
┌─────────────────────────────────────────┐
│ │
│ [Illustration/Icon] │
│ │
│ No projects yet │ ← What state they're in
│ │
│ Create your first project to get │ ← Why it's empty
│ started tracking your work. │
│ │
│ [+ Create Project] │ ← Clear action to fix it
│ │
└─────────────────────────────────────────┘
Component Documentation Template
Every component in your design system should have:
## Button
### Description
A clickable element that triggers an action.
### Variants
- Primary: Main actions (submit, save, confirm)
- Secondary: Supporting actions (cancel, back)
- Destructive: Dangerous actions (delete, remove)
- Ghost: Minimal-emphasis actions (tertiary actions, icon buttons)
### Sizes
- Small: 32px height, 14px text — dense UIs, inline actions
- Medium: 40px height, 16px text — default
- Large: 48px height, 16px text — CTAs, mobile primary actions
### States
Default, Hover, Focus, Active, Disabled, Loading
### Props/API
- variant: primary | secondary | destructive | ghost
- size: sm | md | lg
- disabled: boolean
- loading: boolean
- icon: ReactNode (optional, leading icon)
- fullWidth: boolean
### Accessibility
- Uses native <button> element
- Disabled buttons use aria-disabled, not removed from tab order
- Loading state announces to screen readers via aria-live
### Do / Don't
✓ Use primary for the single most important action per screen
✗ Don't use multiple primary buttons in the same view
✓ Use verb-based labels: "Save Changes", "Delete Account"
✗ Don't use vague labels: "OK", "Submit", "Click Here"
Building a Design System: Practical Steps
Phase 1: Audit (Week 1)
- Screenshot every unique component in your current product
- Group similar components (you'll find 5 different button styles)
- Document inconsistencies (different padding, colors, border-radius)
- Identify the 10-15 most-used components
Phase 2: Define Tokens (Week 2)
- Lock down your color palette (primary, neutrals, semantic)
- Define your type scale (7-8 sizes)
- Define spacing tokens (8px grid)
- Define border-radius, shadow, and transition tokens
- Implement as CSS custom properties or a tokens file
Phase 3: Build Core Components (Weeks 3-6)
Start with the highest-impact, most-reused components:
| Priority | Components | Why First |
|---|---|---|
| 1 | Button, Input, Select, Checkbox | Used everywhere |
| 2 | Card, Modal, Dropdown, Badge | Core containers and indicators |
| 3 | Table, Form, Navigation, Tabs | Complex, high-value |
| 4 | Toast, Tooltip, Popover, Avatar | Supporting elements |
Phase 4: Document and Launch (Week 7-8)
- Write usage guidelines for each component
- Create a documentation site (Storybook, Docusaurus, or custom)
- Provide copy-paste code examples
- Show do/don't examples with screenshots
Design Systems to Study
| System | Owner | Strength | URL |
|---|---|---|---|
| Material Design | Deepest documentation | material.io | |
| Human Interface Guidelines | Apple | Platform-specific excellence | developer.apple.com/design |
| Carbon | IBM | Enterprise-focused, accessible | carbondesignsystem.com |
| Polaris | Shopify | E-commerce patterns, excellent docs | polaris.shopify.com |
| Atlassian Design System | Atlassian | Team collaboration patterns | atlassian.design |
| GOV.UK | UK Government | Accessibility-first, clear patterns | design-system.service.gov.uk |
| Radix | WorkOS | Unstyled, accessible primitives | radix-ui.com |
| Shadcn/ui | Community | Copy-paste components, Tailwind-based | ui.shadcn.com |
Governance
A design system without governance will drift into inconsistency within 6 months.
Versioning
Use semantic versioning (MAJOR.MINOR.PATCH):
| Change Type | Version Bump | Example |
|---|---|---|
| Breaking change (API, removed component) | MAJOR (2.0.0) | Button prop type renamed to variant |
| New component or feature | MINOR (1.1.0) | Added DatePicker component |
| Bug fix, style tweak | PATCH (1.0.1) | Fixed focus ring on Checkbox |
Contribution Process
1. Proposal → Open an issue describing the need
2. Discussion → Team reviews, asks questions, suggests alternatives
3. Design → Create designs following existing token system
4. Review → Design review by system maintainers
5. Build → Implement with tests and documentation
6. Code Review → Review by system maintainers
7. Release → Merge, version bump, changelog update
Maintenance Practices
- Regular audits: Quarterly review of all components for consistency
- Usage tracking: Monitor which components are actually used (and which aren't)
- Deprecation process: Announce deprecation, then provide a migration path, then remove after 2 major versions
- Changelog: Every change documented with before/after when applicable
- Office hours: Regular time slot where teams can ask questions or propose changes
Common Mistakes
| Mistake | Impact | Fix |
|---|---|---|
| Building the system before auditing | Creates components nobody uses | Audit existing UI first, then build what's needed |
| Over-engineering from the start | System never ships | Start with tokens + 5 core components, grow from there |
| No documentation | Developers guess how to use components | Document every component with examples and guidelines |
| Design and code diverge | Figma shows one thing, code renders another | Sync regularly, or use design-to-code tools |
| No governance process | Inconsistencies creep back in | Assign maintainers, establish a contribution process |
| Building for every edge case | Components become bloated and hard to use | Build for 80% of cases, allow composition for the rest |
Key Takeaways
- A design system is a product, not a project. It needs ongoing maintenance and governance.
- Start small: tokens + 5 core components. Grow based on actual needs, not speculation.
- Tokens are the foundation. Define colors, spacing, typography, and shadows before building components.
- Document every component with variants, states, accessibility notes, and do/don't guidelines.
- Study established design systems (Material, Polaris, GOV.UK) for patterns and documentation structure.
- Every component needs at minimum: default, hover, focus, active, disabled, error, and empty states.
- Use semantic tokens (aliases) instead of raw values so dark mode and theming are simple.