Building Design Systems: Complete 2026 Guide

Master design tokens, atomic components, and Storybook. Build scalable design systems with real-world examples from Google Material, IBM Carbon, and Shopify.

Inzimam Ul Haq
Inzimam Ul Haq
· 8 min read · Updated
Design system components and tokens visualization
Photo by Balázs Kétyi on Unsplash

A design system is a collection of reusable components, design tokens, and guidelines that ensure consistency across products. Start with tokens before components to build a foundation that scales.

TL;DR: Start with design tokens (colors, spacing, typography), build atomic components (buttons, inputs), document usage patterns in Storybook, and version your system like software. For AI-assisted development, create an llms.txt file so AI generates consistent, on-brand UI.

Building a design system from scratch is the ultimate test for a design engineer. You are architecting the bridge between Figma Variables, design tokens, and production code.

This guide covers the complete process: W3C Design Token formats, CVA for component variants, Storybook documentation, and AI-friendly patterns.

What Is a Design System?

A component library is just reusable UI components. A design system adds design tokens, documentation, accessibility standards, and governance.

Three essential layers:

  1. Design Tokens: Foundational values (colors, spacing, typography, shadows)
  2. Components: UI building blocks built on those tokens
  3. Documentation: Guidelines for when and how to use everything

Lessons from Building Design Systems

My first attempt involved manually syncing JSON token files to CSS-in-JS. Disaster. Every Figma color update broke the build because I hadn’t set up Style Dictionary or a strict component API with CVA.

The fix: treat the design system as a product. Figma Variables export to W3C-compliant JSON, transform via Style Dictionary to CSS Custom Properties, and strictly type in React. Adoption increased because developers stopped guessing tokens.

What works:

  • Start with pain, not perfection: Solve one real problem well before expanding
  • Ship early, iterate often: A basic button in production beats a perfect one in development
  • Make adoption effortless: If using your system requires more effort than not using it, people won’t
  • Document the “why”: Developers can figure out “how” from code
  • Celebrate contributions: Every external PR is a win for adoption

Why Design Systems Matter

Per the Sparkbox Design Systems Survey 2025, teams using design systems ship 34% faster and reduce design-related bugs by 47%.

Benefits:

  • Consistency: Unified look and feel across products
  • Efficiency: Stop rebuilding the same components
  • Quality: Accessibility built in once
  • Scalability: New teams ship faster with established patterns
  • Communication: Shared vocabulary between designers and developers

For component rendering optimization, see our React performance guide.

Core Components of a Design System

Foundation Layer

Raw design decisions:

├── tokens/
│   ├── colors.json
│   ├── typography.json
│   ├── spacing.json
│   ├── shadows.json
│   ├── borders.json
│   └── motion.json

Component Layer

Components follow atomic design principles:

LevelExamplesDescription
AtomsButton, Input, Icon, BadgeSmallest functional units
MoleculesSearch Field, Form Group, CardCombinations of atoms
OrganismsNavigation, Hero Section, FooterComplex, distinct sections
TemplatesPage layouts, Grid systemsPage-level structures

Documentation Layer

What transforms a component library into a design system:

  • Usage guidelines: When to use each component
  • Accessibility requirements: WCAG compliance details
  • Code examples: Copy-paste ready implementations
  • Do’s and Don’ts: Visual examples of correct usage
  • Migration guides: How to upgrade between versions

Token Architecture: The Foundation

Design tokens are atomic variables storing design decisions (colors, typography, spacing) in a platform-agnostic format. Tools like Style Dictionary compile them to CSS variables, iOS Swift, or Android XML.

Token Hierarchy

Structure tokens in three tiers:

// 1. Primitive Tokens (raw values)
const primitives = {
  blue: {
    50: "#eff6ff",
    100: "#dbeafe",
    500: "#3b82f6",
    600: "#2563eb",
    900: "#1e3a8a",
  },
  spacing: {
    1: "0.25rem",
    2: "0.5rem",
    4: "1rem",
    8: "2rem",
  },
};

// 2. Semantic Tokens (purpose-driven)
const semantic = {
  color: {
    primary: "{blue.600}",
    primaryHover: "{blue.700}",
    background: "{gray.50}",
    text: "{gray.900}",
    textMuted: "{gray.600}",
  },
  spacing: {
    componentGap: "{spacing.4}",
    sectionGap: "{spacing.8}",
  },
};

// 3. Component Tokens (component-specific)
const component = {
  button: {
    primary: {
      background: "{color.primary}",
      backgroundHover: "{color.primaryHover}",
      text: "{color.white}",
      paddingX: "{spacing.4}",
      paddingY: "{spacing.2}",
    },
  },
};

Token Naming Conventions

Use a consistent pattern:

[category]-[property]-[variant]-[state]

Examples:

  • color-background-primary
  • color-text-secondary-hover
  • spacing-component-gap-lg
  • typography-heading-1-font-size

Implementing Tokens with Style Dictionary

Style Dictionary transforms tokens into platform-specific outputs:

// config.js
export default {
  source: ["tokens/**/*.json"],
  platforms: {
    css: {
      transformGroup: "css",
      buildPath: "dist/css/",
      files: [
        {
          destination: "tokens.css",
          format: "css/variables",
        },
      ],
    },
    js: {
      transformGroup: "js",
      buildPath: "dist/js/",
      files: [
        {
          destination: "tokens.js",
          format: "javascript/es6",
        },
      ],
    },
    typescript: {
      transformGroup: "js",
      buildPath: "dist/ts/",
      files: [
        {
          destination: "tokens.ts",
          format: "typescript/es6-declarations",
        },
      ],
    },
  },
};

Output CSS:

:root {
  --color-primary: #2563eb;
  --color-primary-hover: #1d4ed8;
  --color-background: #f9fafb;
  --color-text: #111827;
  --spacing-1: 0.25rem;
  --spacing-2: 0.5rem;
  --spacing-4: 1rem;
  --font-size-base: 1rem;
  --font-size-lg: 1.125rem;
}

Design Tokens in Practice

Building Atomic Components

With tokens established, build components using atomic design. Start small and compose upward.

Button Component Example

A production-ready button using design tokens:

// Button.tsx
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";

const buttonVariants = cva(
  // Base styles using design tokens
  "inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
  {
    variants: {
      variant: {
        primary:
          "bg-[var(--color-primary)] text-white hover:bg-[var(--color-primary-hover)]",
        secondary:
          "bg-[var(--color-secondary)] text-[var(--color-text)] hover:bg-[var(--color-secondary-hover)]",
        outline:
          "border border-[var(--color-border)] bg-transparent hover:bg-[var(--color-background-subtle)]",
        ghost: "hover:bg-[var(--color-background-subtle)]",
        destructive:
          "bg-[var(--color-destructive)] text-white hover:bg-[var(--color-destructive-hover)]",
      },
      size: {
        sm: "h-8 px-3 text-sm",
        md: "h-10 px-4 text-base",
        lg: "h-12 px-6 text-lg",
      },
    },
    defaultVariants: {
      variant: "primary",
      size: "md",
    },
  },
);

export interface ButtonProps
  extends
    React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  isLoading?: boolean;
  ref?: React.Ref<HTMLButtonElement>;
}

export function Button({
  className,
  variant,
  size,
  isLoading,
  children,
  disabled,
  ref,
  ...props
}: ButtonProps) {
  return (
    <button
      className={cn(buttonVariants({ variant, size, className }))}
      ref={ref}
      disabled={disabled || isLoading}
      {...props}
    >
      {isLoading ? (
        <span className="mr-2 h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
      ) : null}
      {children}
    </button>
  );
}

React 19 update: ref is now a regular prop—no need for forwardRef. This simplifies component APIs and eliminates the need for displayName. See our React Hooks guide for more on React 19 changes.

Component API Design Principles

  1. Composition over configuration: Prefer composable parts over prop explosion
  2. Sensible defaults: Components work with zero props
  3. Consistent naming: Same prop names across components (size, variant, disabled)
  4. TypeScript first: Full type safety with autocomplete
  5. Accessibility built-in: ARIA attributes, keyboard navigation, focus management

See our TypeScript for React developers guide.

// Good: Composable API
<Card>
  <CardHeader>
    <CardTitle>Title</CardTitle>
    <CardDescription>Description</CardDescription>
  </CardHeader>
  <CardContent>Content here</CardContent>
  <CardFooter>
    <Button>Action</Button>
  </CardFooter>
</Card>

// Avoid: Prop explosion
<Card
  title="Title"
  description="Description"
  content="Content here"
  footerContent={<Button>Action</Button>}
/>

For state management guidance, see Redux vs Zustand vs Jotai.

Documentation That Gets Used

Documentation is where design systems succeed or fail.

Storybook Setup

Storybook is the industry standard:

npx storybook@latest init

Configure for design systems:

// .storybook/main.ts
import type { StorybookConfig } from "@storybook/react-vite";

const config: StorybookConfig = {
  stories: ["../src/**/*.stories.@(js|jsx|ts|tsx|mdx)"],
  addons: [
    "@storybook/addon-essentials",
    "@storybook/addon-a11y", // Accessibility testing
    "@storybook/addon-designs", // Figma embeds
    "@chromatic-com/storybook", // Visual regression
  ],
  framework: "@storybook/react-vite",
  docs: {
    autodocs: "tag",
  },
};

export default config;

Writing Effective Stories

// Button.stories.tsx
import type { Meta, StoryObj } from "@storybook/react";
import { Button } from "./Button";

const meta: Meta<typeof Button> = {
  title: "Components/Button",
  component: Button,
  tags: ["autodocs"],
  argTypes: {
    variant: {
      control: "select",
      options: ["primary", "secondary", "outline", "ghost", "destructive"],
      description: "Visual style variant",
    },
    size: {
      control: "select",
      options: ["sm", "md", "lg"],
      description: "Size of the button",
    },
    isLoading: {
      control: "boolean",
      description: "Shows loading spinner and disables interaction",
    },
  },
  parameters: {
    design: {
      type: "figma",
      url: "https://figma.com/file/xxx/Design-System?node-id=123",
    },
  },
};

export default meta;
type Story = StoryObj<typeof Button>;

export const Primary: Story = {
  args: {
    children: "Primary Button",
    variant: "primary",
  },
};

export const AllVariants: Story = {
  render: () => (
    <div className="flex gap-4">
      <Button variant="primary">Primary</Button>
      <Button variant="secondary">Secondary</Button>
      <Button variant="outline">Outline</Button>
      <Button variant="ghost">Ghost</Button>
      <Button variant="destructive">Destructive</Button>
    </div>
  ),
};

export const Sizes: Story = {
  render: () => (
    <div className="flex items-center gap-4">
      <Button size="sm">Small</Button>
      <Button size="md">Medium</Button>
      <Button size="lg">Large</Button>
    </div>
  ),
};

export const Loading: Story = {
  args: {
    children: "Loading...",
    isLoading: true,
  },
};

Documentation Beyond Code

{/* Button.mdx */}
import { Canvas, Meta, Story } from '@storybook/blocks';
import \* as ButtonStories from './Button.stories';

<Meta of={ButtonStories} />

# Button

Buttons trigger actions or navigation. Use them for the primary calls to action on a page.

## When to Use

- **Primary**: Main action on a page (one per view)
- **Secondary**: Supporting actions
- **Outline**: Lower emphasis actions
- **Ghost**: Tertiary actions, often in toolbars
- **Destructive**: Irreversible or dangerous actions

## Accessibility

- Always include descriptive text or `aria-label`
- Ensure 4.5:1 color contrast ratio
- Support keyboard activation (Enter and Space)
- Disabled buttons should use `aria-disabled` for screen readers

## Do's and Don'ts

**Do**

- Use action verbs: "Save", "Submit", "Delete"
- Keep labels concise (1-3 words)
- Use only one primary button per section

**Don't**

- Use vague labels like "Click here" or "OK"
- Disable buttons without explanation
- Use buttons for navigation (use links instead)

<Canvas of={ButtonStories.AllVariants} />

Scaling Across Teams

Versioning Strategy

Use Semantic Versioning:

MAJOR.MINOR.PATCH

1.0.0 → 1.0.1  (Patch: bug fixes, no API changes)
1.0.0 → 1.1.0  (Minor: new features, backward compatible)
1.0.0 → 2.0.0  (Major: breaking changes)

Maintain a changelog:

# Changelog

## [2.0.0] - 2026-01-10

### Breaking Changes

- Renamed `Button` prop `loading` to `isLoading`
- Removed deprecated `Card` `flat` variant

### Migration Guide

```jsx
// Before
<Button loading>Submit</Button>

// After
<Button isLoading>Submit</Button>
```

[1.5.0] - 2025-12-15

Added

  • New Tooltip component
  • Badge now supports dot variant

Fixed

  • Input focus ring color in dark mode

### Package Structure

@company/design-system/ ├── packages/ │ ├── tokens/ # @company/tokens │ ├── core/ # @company/core (React components) │ ├── icons/ # @company/icons │ └── themes/ # @company/themes ├── apps/ │ └── docs/ # Storybook documentation └── tools/ └── figma-plugin/ # Figma integration


Teams can install what they need:

```bash
# Full system
npm install @company/design-system

# Just tokens (for non-React projects)
npm install @company/tokens

# Specific packages
npm install @company/core @company/icons

Governance Model

RoleResponsibility
Core TeamMaintains packages, reviews PRs, releases
ContributorsProposes changes, submits PRs
ConsumersUses system, reports issues, requests features

Contribution workflow:

  1. RFC: Propose significant changes
  2. Design Review: Validate with design team
  3. Implementation: Build with tests and documentation
  4. Review: Core team approval
  5. Release: Versioned release with changelog

Automated Quality Gates

# .github/workflows/ci.yml
name: Design System CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install dependencies
        run: npm ci

      - name: Lint
        run: npm run lint

      - name: Type check
        run: npm run typecheck

      - name: Unit tests
        run: npm run test

      - name: Build
        run: npm run build

      - name: Visual regression (Chromatic)
        uses: chromaui/action@latest
        with:
          projectToken: ${{ secrets.CHROMATIC_TOKEN }}

      - name: Accessibility audit
        run: npm run test:a11y

Building Scalable Design Systems

Key Takeaways

  1. Start with design tokens (colors, spacing, typography) - they’re the foundation

  2. Build atomic components first (buttons, inputs, cards) - master basics before complex patterns

  3. Document usage patterns, not just props - teams need to know when and why

  4. Use Storybook - interactive docs beat static documentation

  5. Version like software - semantic versioning and changelogs build trust

Related: Learn to implement with Figma to Code workflows and build accessible components.

Design Systems in the Age of AI

With AI coding assistants and “vibe coding” becoming mainstream, design systems need to evolve. AI makes design systems more important, not less.

Why AI Needs Design Systems

Without a design system, asking AI to “create a button” five times yields five different buttons with arbitrary styles. With a documented system, AI becomes a force multiplier for consistency.

For shipping AI-generated code safely, see why 73% of vibe-coded apps fail.

Structuring Your System for AI

1. Create an AI-readable overview

Add DESIGN_SYSTEM.md or llms.txt at your project root:

# Design System Overview

## Quick Start

Import components from `@company/ui`. All components use our design tokens.

## Core Principles

- Use semantic color tokens (primary, secondary) not raw colors
- Spacing uses 4px base unit: 4, 8, 12, 16, 24, 32, 48, 64
- All interactive elements must be keyboard accessible

## Component Usage

### Button

\`\`\`tsx
import { Button } from '@company/ui';

<Button variant="primary" size="md">Label</Button>
\`\`\`
Variants: primary, secondary, outline, ghost, destructive
Sizes: sm, md, lg

### Card

\`\`\`tsx
import { Card, CardHeader, CardContent } from '@company/ui';

<Card>
  <CardHeader>Title</CardHeader>
  <CardContent>Content</CardContent>
</Card>
\`\`\`

## Token Reference

- Colors: --color-primary, --color-secondary, --color-background
- Spacing: --spacing-1 (4px) through --spacing-16 (64px)
- Typography: --font-size-sm, --font-size-base, --font-size-lg

2. Use semantic naming

AI understands clear names better than cryptic abbreviations:

// ✅ AI-friendly: Clear, semantic names
<Button variant="destructive" size="large" isLoading>
  Delete Account
</Button>

// ❌ AI-unfriendly: Cryptic abbreviations
<Btn v="dng" sz="lg" ld>
  Delete Account
</Btn>

3. Include JSDoc with examples

AI reads your comments:

/**
 * Primary button component for user actions.
 *
 * @example
 * // Primary action
 * <Button variant="primary">Save Changes</Button>
 *
 * @example
 * // Destructive action with confirmation
 * <Button variant="destructive" onClick={handleDelete}>
 *   Delete Project
 * </Button>
 *
 * @example
 * // Loading state
 * <Button isLoading disabled>
 *   Saving...
 * </Button>
 */
export const Button = ({ variant, size, isLoading, children, ...props }) => {
  // implementation
};

4. Provide copy-paste patterns

// patterns/form-with-validation.tsx
/**
 * Standard form pattern with validation feedback.
 * Use this pattern for all user input forms.
 */
export function FormPattern() {
  return (
    <Form onSubmit={handleSubmit}>
      <FormField>
        <Label htmlFor="email">Email</Label>
        <Input
          id="email"
          type="email"
          placeholder="you@example.com"
          aria-describedby="email-error"
        />
        <FormError id="email-error">Please enter a valid email</FormError>
      </FormField>
      <Button type="submit" variant="primary">
        Submit
      </Button>
    </Form>
  );
}

Prompting for Consistency

Reference your system:

"Using our design system components from @company/ui, create a
user profile card with avatar, name, email, and an edit button."

Specify constraints:

"Create a pricing table using only our existing Card, Badge, and
Button components. Use semantic color tokens, not hardcoded colors.
Follow our 8px spacing grid."

Ask for system compliance:

"Review this component and ensure it:
1. Uses design tokens instead of hardcoded values
2. Follows our Button API conventions
3. Includes proper accessibility attributes
4. Matches our existing component patterns"

Request token-based styling:

"Style this component using CSS custom properties from our token
system. Reference --color-*, --spacing-*, and --font-* variables.
Don't use arbitrary values like #3b82f6 or 16px."

Building Components with AI

Step 1: Define the spec

"I need a Tooltip component for our design system. Requirements:
- Appears on hover/focus with 200ms delay
- Positions automatically (top, bottom, left, right)
- Uses --color-tooltip-bg and --color-tooltip-text tokens
- Max width of 200px with text wrapping
- Accessible: role=tooltip, aria-describedby connection
- Keyboard dismissible with Escape
- Follows our existing component API patterns (see Button, Popover)"

Step 2: Review for

  • Token usage (no hardcoded values)
  • API consistency
  • Accessibility compliance
  • TypeScript types

Step 3: Iterate

"The Tooltip looks good, but:
1. Change padding to use --spacing-2 and --spacing-3
2. Add a 'variant' prop matching our Badge variants
3. Export types for consumers
4. Add Storybook stories following our Button.stories.tsx pattern"

Human-AI Workflow

TaskHuman RoleAI Role
Token definitionDefine values, semantic meaningGenerate token files, CSS output
Component API designDecide props, variants, behaviorSuggest patterns from similar systems
ImplementationReview, refine, ensure qualityGenerate initial code, handle boilerplate
DocumentationWrite “why” and guidelinesGenerate API docs, prop tables
TestingDefine test cases, edge casesGenerate test implementations
AccessibilityAudit, define requirementsImplement ARIA, keyboard handling

AI accelerates execution, but humans own the decisions. Your system’s value comes from intentional choices: the “why” behind tokens, the principles guiding APIs, the accessibility standards. AI implements faster; it doesn’t decide for you.

Common AI Pitfalls

1. Accepting arbitrary values

// ❌ AI often generates hardcoded values
<div style={{ padding: '14px', color: '#6366f1' }}>

// ✅ Always convert to tokens
<div style={{ padding: 'var(--spacing-3)', color: 'var(--color-primary)' }}>

2. Inconsistent APIs

// ❌ AI might generate inconsistent prop names
<Button loading={true} />
<Spinner isLoading={true} />
<Card isLoading={true} />

// ✅ Enforce consistency in your prompts
// "Use 'isLoading' for all loading states, matching our Button component"

3. Skipping accessibility

// ❌ Vague prompt
"Create a dropdown menu"

// ✅ Accessibility-aware prompt
"Create an accessible dropdown menu with:
- Keyboard navigation (arrow keys, Enter, Escape)
- ARIA: role=menu, role=menuitem, aria-expanded
- Focus management on open/close
- Screen reader announcements"

4. Not validating AI output against your system

Always run AI-generated components through your quality gates:

  • Does it use only design tokens?
  • Does it follow your component API conventions?
  • Does it pass accessibility audits?
  • Does it match your TypeScript patterns?

The Future: AI-Native Design Systems

Looking ahead, I see design systems evolving to be AI-native, built from the ground up to be consumed by both humans and AI assistants. This means:

  • Structured metadata: Components with machine-readable descriptions of their purpose, constraints, and relationships
  • Semantic tokens everywhere: No arbitrary values that AI might misinterpret
  • Pattern libraries: Documented compositions that AI can reference and adapt
  • Validation layers: Automated checks that catch AI-generated inconsistencies before they ship

The teams that figure this out first will have a massive advantage. Their AI assistants will generate consistent, on-brand UI while competitors struggle with chaos.

My advice? Start now. Document your system for AI consumption. Create that llms.txt file. Write prompts that reference your patterns. The investment pays off immediately in consistency and compounds over time as AI tools improve.

Learning from the Best: How Top Design Systems Started

The most successful design systems didn’t emerge fully formed. They evolved from real problems and grew through iteration. Here’s how some of the industry’s most influential systems got their start, and what you can learn from their journeys.

Material Design (Google)

Origin: Material Design launched in 2014, but its roots trace back to Google’s struggle with inconsistency across products. Gmail looked nothing like Google Maps, which looked nothing like Android. The design team, led by Matías Duarte, set out to create a unified visual language.

Key insight: Material Design started with principles, not components. The team defined a “material” metaphor: digital surfaces that behave like physical paper with realistic shadows and motion. Components came later, built on these foundational principles. For implementing motion in React, Framer Motion provides a declarative API that aligns well with design system principles.

What you can learn: Define your design philosophy before building components. Material’s success came from having a clear, opinionated point of view that guided every decision.

Where they are now: Material Design 3 (2021) introduced dynamic color, making the system more flexible while maintaining consistency. It powers Android, Google Workspace, and countless third-party apps.

Carbon Design System (IBM)

Origin: IBM had over 30 different design systems across the company in 2015. Products looked completely different, and teams were duplicating effort constantly. Carbon emerged from a grassroots effort to unify the experience.

Key insight: Carbon succeeded because it was built by practitioners, not mandated from above. The team started by auditing existing patterns across IBM products, identifying commonalities, and standardizing the best solutions.

What you can learn: Don’t start from scratch. Audit what already exists. The best patterns in your organization are already being used; your job is to find, formalize, and scale them.

Where they are now: Carbon is now open source with implementations for React, Angular, Vue, and Web Components. It includes specialized variants for AI interfaces and data visualization.

Polaris (Shopify)

Origin: Shopify’s design system started in 2017 when the company realized their merchant admin was becoming inconsistent as teams scaled. Different squads were building similar components with subtle differences that confused merchants.

Key insight: Polaris focused heavily on content guidelines alongside components. They recognized that consistent UI means nothing if the words and tone vary wildly. Their content strategy became as important as their component library.

What you can learn: Design systems aren’t just visual. They include voice, tone, and content patterns. Consider how your system addresses the words users see, not just the pixels.

Where they are now: Polaris has evolved to include extensive accessibility guidelines, internationalization support, and a Figma UI kit that stays in sync with code.

Atlassian Design System

Origin: Atlassian’s design system grew from the chaos of acquisitions. As the company bought Trello, Jira, Confluence, and others, each product had its own design language. The design system team formed to create coherence without forcing uniformity.

Key insight: Atlassian embraced “unity, not uniformity.” They created shared foundations (tokens, primitives) while allowing products to maintain distinct personalities. This balance between consistency and flexibility was key to adoption.

What you can learn: Not everything needs to be identical. Define what must be consistent (accessibility, spacing, core interactions) and what can flex (color accents, illustrations, product-specific patterns).

Where they are now: Atlassian Design System includes comprehensive token documentation, accessibility guidelines, and a unique approach to design system governance that balances central control with product autonomy.

Primer (GitHub)

Origin: GitHub’s design system started as a CSS framework in 2016, born from the need to maintain consistency across a rapidly growing codebase. What began as shared styles evolved into a full component system.

Key insight: Primer grew incrementally. Instead of a big-bang rewrite, the team extracted patterns from production code, documented them, and gradually replaced ad-hoc implementations with system components.

What you can learn: You don’t need to stop the world to build a design system. Extract, document, and replace incrementally. This approach reduces risk and builds trust through small wins.

Where they are now: Primer includes React components, CSS utilities, and extensive documentation. It’s open source and powers all of GitHub’s interfaces, including the new GitHub Copilot features.

Lessons from the Giants

Studying these systems reveals common patterns:

SystemStarted WithKey Differentiator
Material DesignDesign principlesStrong metaphor and motion language
CarbonPattern auditPractitioner-led, bottom-up adoption
PolarisComponent inconsistencyContent guidelines as first-class citizens
AtlassianAcquisition chaosUnity without uniformity philosophy
PrimerCSS frameworkIncremental extraction from production

The common thread? None of these systems started with “let’s build a design system.” They started with real problems (inconsistency, duplication, scaling challenges) and built solutions that evolved into systems over time.

My Recommendation for Getting Started

Based on my experience and studying these successful systems, here’s the approach I recommend:

  1. Week 1-2: Audit your existing products. Screenshot every button, form, card, and modal. You’ll be shocked at the inconsistency.

  2. Week 3-4: Interview 5-10 developers and designers. Ask: “What’s the most frustrating thing about building UI here?” Their answers will prioritize your roadmap.

  3. Week 5-8: Build your token foundation and 3-5 core components that solve the biggest pain points. Ship them to one team.

  4. Month 3+: Iterate based on feedback, expand component coverage, and gradually onboard more teams.

This isn’t the only path, but it’s one that balances speed with sustainability. The goal is to create value quickly while building toward something comprehensive.

The best design systems evolve with their organizations. Start small, ship early, and iterate based on real usage. Your future self and every team that builds with your system will thank you.

Design System Testing Strategy

A comprehensive testing strategy ensures your design system remains reliable as it evolves. Cover these layers:

Visual Regression Testing

Tools like Chromatic capture screenshots of every component state and diff them against baselines. This catches unintended visual changes before they ship.

// Visual regression via Storybook + Chromatic
// Runs automatically on every PR
npx chromatic --project-token=your-token

Unit Testing Components

Test component behavior, not implementation details:

import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Button } from "./Button";

test("calls onClick when clicked", async () => {
  const handleClick = jest.fn();
  render(<Button onClick={handleClick}>Click me</Button>);

  await userEvent.click(screen.getByRole("button"));
  expect(handleClick).toHaveBeenCalledTimes(1);
});

test("shows loading spinner when isLoading", () => {
  render(<Button isLoading>Submit</Button>);
  expect(screen.getByRole("button")).toBeDisabled();
});

Accessibility Testing

Automate accessibility checks in your pipeline:

# Run axe-core against Storybook stories
npm run test:a11y

Combine automated tools with manual testing. Automated tools catch about 30-40% of accessibility issues—keyboard navigation, screen reader behavior, and cognitive accessibility require human review. For comprehensive accessibility patterns, see our accessibility guide for design engineers.

Token Validation

Ensure tokens remain consistent across platforms:

// validate-tokens.test.ts
import tokens from "../dist/tokens.json";

test("all color tokens have valid hex values", () => {
  Object.entries(tokens.color).forEach(([name, value]) => {
    expect(value).toMatch(/^#[0-9a-fA-F]{6}$/);
  });
});

test("spacing tokens follow 4px grid", () => {
  Object.entries(tokens.spacing).forEach(([name, value]) => {
    const px = parseFloat(value) * 16; // rem to px
    expect(px % 4).toBe(0);
  });
});

Ready to dive deeper? Explore how React Server Components can optimize your design system’s performance, or learn about the modern design engineer role that makes this work possible.

Frequently Asked Questions

What is a design system?
A design system is a collection of reusable components, design tokens, and guidelines that ensure consistency across products. It includes foundational elements like color palettes, typography scales, and spacing systems, along with UI components and documentation that teams use to build cohesive user experiences.
What are design tokens?
Design tokens are the smallest, most atomic pieces of a design system. They store design decisions as named variables (colors, spacing, typography, shadows, and more) in a platform-agnostic format that can be transformed into CSS, iOS, Android, or any other platform's native styling language.
How do I start building a design system?
Start with design tokens (colors, spacing, typography) before building components. Define your foundational tokens first, then create atomic components like buttons and inputs. Document usage patterns, not just props, and use Storybook for interactive documentation. Version your design system like software.
What is the difference between a component library and a design system?
A component library is a collection of reusable UI components, while a design system is broader. It includes the component library plus design tokens, documentation, usage guidelines, accessibility standards, and governance processes. A design system is the complete ecosystem for building consistent products.
How do I scale a design system across multiple teams?
Scale through clear governance, semantic versioning, and comprehensive documentation. Establish a core team for maintenance, create contribution guidelines, use automated testing and visual regression tools, and publish your system as versioned packages that teams can depend on reliably.
Should I use CSS variables or CSS-in-JS for design tokens?
CSS custom properties (variables) offer runtime theming and broad compatibility, while CSS-in-JS provides type safety and co-location with components. Many modern design systems use both: CSS variables for runtime theming and CSS-in-JS for component-scoped styles with TypeScript integration.
What are the best design systems to learn from?
The most influential open-source design systems include Material Design (Google), Carbon (IBM), Polaris (Shopify), Atlassian Design System, and Primer (GitHub). Each offers unique insights: Material for design principles, Carbon for enterprise patterns, Polaris for content guidelines, and Primer for incremental adoption strategies.
How long does it take to build a design system?
A minimal viable design system with tokens and 5-10 core components can be built in 4-8 weeks. However, a comprehensive system with full documentation, governance, and broad adoption typically takes 6-12 months to mature. The key is shipping incrementally rather than waiting for perfection.
How do I use a design system with AI coding assistants?
Create an AI-readable overview file (like llms.txt or DESIGN_SYSTEM.md) documenting your components, tokens, and patterns. Use semantic naming, include JSDoc comments with examples, and explicitly reference your design system in prompts. AI assistants work best when given clear constraints and examples to follow.
Does vibe coding make design systems obsolete?
No. AI makes design systems more important. Without a design system, AI generates inconsistent UI with arbitrary values. With a well-documented system, AI becomes a force multiplier for consistency. The key is structuring your system for AI consumption with clear documentation, semantic tokens, and copy-paste patterns.

Sources & References