Back to Blog

Design System Engineering: Token-Based Theming, Component Library, Storybook, and Chromatic

Build a production design system: implement design token architecture with CSS custom properties, create a typed React component library, document with Storybook, automate visual regression testing with Chromatic, and publish with semantic versioning.

Viprasol Tech Team
October 13, 2026
13 min read

Design System Engineering: Building Component Libraries at Scale (2026)

A design system is more than a collection of components. It's the bridge between design intent and implementation reality. After building and maintaining design systems for clients ranging from startups to enterprises, I've learned that the difference between a successful system and one that gets ignored is architectural discipline.

The Problem with Ad-Hoc Component Growth

Most teams don't plan to have a mess. It happens naturally. Early in a project, you build a few buttons and cards. Weeks later, designers and developers have made slightly different versions of the same component. By month three, you have five button variants nobody can explain. By year two, new team members don't even know what's available in the "system," so they just build new components.

This fragmentation has real costs:

  • Design inconsistency damages user trust
  • Developers waste time rebuilding things that exist
  • Maintenance becomes a nightmare (fix a bug in one button, miss it in three others)
  • New feature development slows down as teams navigate component confusion
  • Testing and QA multiply unnecessarily

We've seen projects where the design system became so tangled that the team eventually gave up and migrated to a new framework entirelyβ€”at enormous cost. It doesn't have to be this way.

Architectural Foundations for Design Systems

Before writing a single component, you need a solid architecture. This is where most teams stumble.

Monorepo vs. Separate Repository

For web development projects, we've converged on monorepo architecture:

Code:

design-system/
β”œβ”€β”€ packages/
β”‚   β”œβ”€β”€ core/          # Base tokens, utilities
β”‚   β”œβ”€β”€ components/    # React components
β”‚   β”œβ”€β”€ tokens/        # Design tokens
β”‚   β”œβ”€β”€ docs/          # Storybook
β”‚   └── cli/           # Code generation tools
β”œβ”€β”€ pnpm-workspace.yaml
└── turbo.json

Monorepo advantages:

  • Atomic changes across packages
  • Shared utilities and types
  • Single source of truth for dependencies
  • Easier discoverability

Single repository approach:

  • Simpler for small teams
  • Easier to enforce consistency
  • Single CI/CD pipeline
  • But doesn't scale past a team of 5-10

We typically use Turbo for task orchestration and pnpm for package management.

Token Architecture

Design tokens are the foundation. They represent every decision: color, spacing, typography, shadows.

Code:

// tokens/src/color.ts
export const colors = {
  // Semantic tokens
  'color-text-primary': '#1a1a1a',
  'color-text-secondary': '#666666',
  'color-background-primary': '#ffffff',
  'color-background-secondary': '#f5f5f5',
  
  // Brand tokens
  'color-brand-primary': '#0066cc',
  'color-brand-secondary': '#ff6600',
  
  // State tokens
  'color-success': '#22c55e',
  'color-error': '#ef4444',
  'color-warning': '#f59e0b'
};

export const spacing = {
  'space-xs': '4px',
  'space-sm': '8px',
  'space-md': '16px',
  'space-lg': '32px',
  'space-xl': '64px'
};

export const typography = {
  'font-family-primary': "'Inter', sans-serif",
  'font-size-sm': '12px',
  'font-size-base': '14px',
  'font-size-lg': '16px',
  'font-weight-regular': 400,
  'font-weight-medium': 500,
  'font-weight-bold': 700
};

These tokens should be generated into:

  • CSS variables
  • Tailwind configuration
  • Design tool files (Figma tokens)
  • JavaScript objects
  • Documentation

A single source of truth prevents the inevitable drift between design and code.

🌐 Looking for a Dev Team That Actually Delivers?

Most agencies sell you a project manager and assign juniors. Viprasol is different β€” senior engineers only, direct Slack access, and a 5.0β˜… Upwork record across 1000+ projects.

  • React, Next.js, Node.js, TypeScript β€” production-grade stack
  • Fixed-price contracts β€” no surprise invoices
  • Full source code ownership from day one
  • 90-day post-launch support included

Component Structure and Documentation

Each component should have clear responsibilities:

Code:

components/button/
β”œβ”€β”€ Button.tsx          # Component implementation
β”œβ”€β”€ Button.test.tsx     # Unit tests
β”œβ”€β”€ Button.stories.tsx  # Storybook stories
β”œβ”€β”€ Button.module.css   # Styles
β”œβ”€β”€ types.ts            # TypeScript definitions
β”œβ”€β”€ index.ts            # Public API
└── README.md           # Usage documentation

Documentation is critical. For each component:

Code:

# Button Component

## Overview
Primary interactive element for user actions.

## API
- **variant**: 'primary' | 'secondary' | 'tertiary'
- **size**: 'small' | 'medium' | 'large'
- **disabled**: boolean
- **loading**: boolean

## Usage
\**\**\'tsx
<Button variant="primary" onClick={handleClick}>
  Click me
</Button>
\**\**\'

## Accessibility
- Supports keyboard navigation
- Screen reader friendly
- High contrast support
- Meets WCAG 2.1 AA standards

## Implementation Notes
- Uses CSS modules for styling
- Accepts standard HTML button attributes
- Loading state managed by parent

Component Composition Patterns

Base Components (Atoms)

Simple, single-responsibility components:

Code:

export interface ButtonProps {
  variant?: 'primary' | 'secondary' | 'tertiary';
  size?: 'small' | 'medium' | 'large';
  disabled?: boolean;
  onClick?: () => void;
  children: React.ReactNode;
}

export function Button({
  variant = 'primary',
  size = 'medium',
  disabled = false,
  onClick,
  children
}: ButtonProps) {
  return (
    <button
      className={classNames(
        styles.button,
        styles[**variant-${variant}**],
        styles[**size-${size}**],
        disabled && styles.disabled
      )}
      disabled={disabled}
      onClick={onClick}
    >
      {children}
    </button>
  );
}

Composed Components (Molecules)

Combinations of base components:

Code:

export interface CardProps {
  title: string;
  description?: string;
  action?: React.ReactNode;
}

export function Card({ title, description, action }: CardProps) {
  return (
    <div className={styles.card}>
      <div className={styles.header}>
        <Heading level="h3">{title}</Heading>
        {action && <div className={styles.action}>{action}</div>}
      </div>
      {description && <Text color="secondary">{description}</Text>}
    </div>
  );
}

Patterns and Templates (Organisms)

Complex patterns that guide page structures:

Code:

export interface PageLayoutProps {
  children: React.ReactNode;
  sidebar?: React.ReactNode;
  header?: React.ReactNode;
}

export function PageLayout({ children, sidebar, header }: PageLayoutProps) {
  return (
    <div className={styles.layout}>
      {header && <header className={styles.pageHeader}>{header}</header>}
      <div className={styles.content}>
        {sidebar && <aside className={styles.sidebar}>{sidebar}</aside>}
        <main className={styles.main}>{children}</main>
      </div>
    </div>
  );
}
design-system - Design System Engineering: Token-Based Theming, Component Library, Storybook, and Chromatic

πŸš€ Senior Engineers. No Junior Handoffs. Ever.

You get the senior developer, not a project manager who relays your requirements to someone you never meet. Every Viprasol project has a senior lead from kickoff to launch.

  • MVPs in 4–8 weeks, full platforms in 3–5 months
  • Lighthouse 90+ performance scores standard
  • Works across US, UK, AU timezones
  • Free 30-min architecture review, no commitment

Theming and Customization

Support themes without component explosion:

Code:

// contexts/theme.tsx
export interface Theme {
  colors: Record<string, string>;
  spacing: Record<string, string>;
  typography: Record<string, string>;
}

export const ThemeContext = createContext<Theme>(defaultTheme);

export function ThemeProvider({ theme, children }) {
  return (
    <ThemeContext.Provider value={theme}>
      {children}
    </ThemeContext.Provider>
  );
}

// components/Button.tsx
export function Button(props: ButtonProps) {
  const theme = useContext(ThemeContext);
  
  return (
    <button style={{
      color: theme.colors['color-text-primary'],
      padding: theme.spacing['space-md']
    }}>
      {props.children}
    </button>
  );
}

For SaaS development clients, theming lets us support white-label solutions without duplicating components.

Testing and Quality Assurance

Component testing should cover three levels:

Unit Tests

Code:

describe('Button', () => {
  it('renders with correct text', () => {
    render(<Button>Click me</Button>);
    expect(screen.getByText('Click me')).toBeInTheDocument();
  });

  it('calls onClick handler when clicked', () => {
    const handleClick = jest.fn();
    render(<Button onClick={handleClick}>Click me</Button>);
    fireEvent.click(screen.getByRole('button'));
    expect(handleClick).toHaveBeenCalled();
  });

  it('applies disabled state', () => {
    render(<Button disabled>Click me</Button>);
    expect(screen.getByRole('button')).toBeDisabled();
  });
});

Visual Regression Tests

Code:

describe('Button visual regression', () => {
  it('matches snapshot', () => {
    const { container } = render(
      <Button variant="primary">Primary Button</Button>
    );
    expect(container).toMatchSnapshot();
  });

  it('matches with different variants', () => {
    const variants = ['primary', 'secondary', 'tertiary'];
    variants.forEach(variant => {
      const { container } = render(
        <Button variant={variant}>{variant}</Button>
      );
      expect(container).toMatchImageSnapshot();
    });
  });
});

Accessibility Tests

Code:

describe('Button accessibility', () => {
  it('is keyboard accessible', () => {
    render(<Button>Click me</Button>);
    const button = screen.getByRole('button');
    button.focus();
    expect(button).toHaveFocus();
  });

  it('has accessible name', () => {
    render(<Button>Click me</Button>);
    expect(screen.getByRole('button', { name: /click me/i }))
      .toBeInTheDocument();
  });

  it('announces loading state', () => {
    render(<Button loading>Processing</Button>);
    expect(screen.getByRole('button'))
      .toHaveAttribute('aria-busy', 'true');
  });
});

Versioning and Deprecation

Design systems evolve. Plan for this:

Code:

// Deprecated component - still works but shows warning
export const OldButton = React.forwardRef<HTMLButtonElement, ButtonProps>(
  (props, ref) => {
    console.warn(
      'OldButton is deprecated. Use Button instead. ' +
      'OldButton will be removed in v2.0.0'
    );
    return <Button {...props} ref={ref} />;
  }
);

Use semantic versioning:

  • Major (v1.0.0 β†’ v2.0.0): Breaking changes
  • Minor (v1.0.0 β†’ v1.1.0): New components/features
  • Patch (v1.0.0 β†’ v1.0.1): Bug fixes

Documentation and Discovery

For cloud solutions teams with multiple services, a living style guide is essential:

Code:

// storybook/Button.stories.tsx
export default {
  title: 'Components/Button',
  component: Button,
  argTypes: {
    variant: {
      control: { type: 'select' },
      options: ['primary', 'secondary', 'tertiary']
    },
    size: {
      control: { type: 'select' },
      options: ['small', 'medium', 'large']
    },
    disabled: { control: 'boolean' }
  }
};

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

export const Secondary = {
  args: { variant: 'secondary', children: 'Secondary Button' }
};

export const Disabled = {
  args: { variant: 'primary', disabled: true, children: 'Disabled' }
};

export const AllVariants = {
  render: () => (
    <div style={{ display: 'flex', gap: '16px' }}>
      <Button variant="primary">Primary</Button>
      <Button variant="secondary">Secondary</Button>
      <Button variant="tertiary">Tertiary</Button>
    </div>
  )
};

Governance and Contribution

Without governance, design systems become dumping grounds:

Contribution Process

  1. Design review: Does this fit the system?
  2. Code review: Does it meet our quality standards?
  3. Documentation: Is it documented and discoverable?
  4. Testing: Is it tested adequately?
  5. Approval: Does it get the maintainer sign-off?

Maintenance Schedule

Dedicate time to:

  • Regular reviews (monthly)
  • Dependency updates (weekly)
  • Performance optimization (quarterly)
  • Breaking change planning (quarterly)

Common Pitfalls and Solutions

ProblemSolution
Too many variantsDocument variant strategy, limit combinations
Props explosionUse composition instead of props
Poor documentationEnforce README + stories for each component
Hard to updateDesign tokens should drive styling
Inconsistent namingCreate naming conventions document

For best practices in component library design, check out the Storybook documentation and the Design Tokens Community Group specifications, which provide comprehensive guidance on structuring and managing design systems. Additionally, explore Carbon Design System by IBM for real-world implementation examples.

FAQ

Q: How many components should I build upfront? A: Build what you need. Core components (Button, Card, Input). Add others as you identify duplicated code.

Q: Should we use CSS-in-JS or CSS modules? A: Either works. We prefer CSS modules for performance and simplicity. CSS-in-JS for dynamic theming.

Q: How do we handle different design requirements for different products? A: Use theming and layout components. Don't duplicate base components.

Q: What about mobile components? A: Build responsive components that work at all sizes. Avoid separate mobile/desktop versions.

Q: How do we encourage adoption? A: Make it easier to use the system than to build new components. Good documentation helps.

Q: Can we use component libraries like Material UI? A: Yes, but consider if you'll customize it. A fully customized library becomes hard to maintain.

Design System Adoption and Change Management

Getting Teams to Use It

The best design system fails if nobody uses it. Here's how we drive adoption:

Show ROI immediately: Demonstrate that using the system saves time. Benchmark: "This button took 3 hours to build from scratch, 5 minutes with our system."

Make discovery easy: Nothing discourages adoption like "I didn't know this component existed." Use searchable documentation, IDE plugins, and component previews.

Support migrations: Don't expect teams to refactor existing code overnight. Provide migration guides and support.

Celebrate adoption: Share metrics about which teams are using the system and what they've shipped faster.

Backward Compatibility and Deprecation

Design systems evolve. Plan migrations carefully:

Code:

// v1.0.0 - Original API
export function Button(props: ButtonPropsV1) { /* ... */ }

// v1.1.0 - Add new variant but keep old API
export function Button(props: ButtonPropsV1 | ButtonPropsV2) {
  if ('newProp' in props) {
    return <ButtonV2 {...props} />;
  }
  return <ButtonV1 {...props} />;
}

// v2.0.0 - Remove old API entirely
export function Button(props: ButtonPropsV2) { /* ... */ }

Provide migration tooling:

Code:

// Codemod to update component usage
export function transformButtonProps(props: any) {
  return {
    variant: props.type || 'primary', // Map 'type' to 'variant'
    size: props.size || 'medium',
    disabled: props.disabled ?? false
  };
}

Measuring Design System Success

Track metrics that matter:

Code:

// Time to implement feature
interface FeatureMetric {
  featureName: string;
  usedDesignSystem: boolean;
  developmentHours: number;
  testingHours: number;
}

// Component reuse rate
interface ReuseMetric {
  componentName: string;
  usageCount: number;
  projectsUsing: number;
}

// Quality metrics
interface QualityMetric {
  componentName: string;
  bugReports: number;
  a11yIssues: number;
  performanceScore: number;
}

Track these over time. You should see development time decreasing and reuse increasing.

Real-World Design System Examples

Documentation Structure

A well-organized design system has:

Code:

docs/
β”œβ”€β”€ Getting Started
β”‚   β”œβ”€β”€ Installation
β”‚   β”œβ”€β”€ Quick Start
β”‚   └── Design Principles
β”œβ”€β”€ Foundations
β”‚   β”œβ”€β”€ Typography
β”‚   β”œβ”€β”€ Color System
β”‚   β”œβ”€β”€ Spacing
β”‚   └── Icons
β”œβ”€β”€ Components
β”‚   β”œβ”€β”€ Atomic
β”‚   β”‚   β”œβ”€β”€ Button
β”‚   β”‚   β”œβ”€β”€ Input
β”‚   β”‚   └── Label
β”‚   β”œβ”€β”€ Composite
β”‚   β”‚   β”œβ”€β”€ Form
β”‚   β”‚   β”œβ”€β”€ Modal
β”‚   β”‚   └── Navigation
β”‚   └── Patterns
β”‚       β”œβ”€β”€ Authentication Flow
β”‚       β”œβ”€β”€ Data Tables
β”‚       └── Dashboard Layouts
β”œβ”€β”€ Accessibility
β”‚   β”œβ”€β”€ WCAG Checklist
β”‚   β”œβ”€β”€ Screen Reader Testing
β”‚   └── Keyboard Navigation
└── Contributing
    β”œβ”€β”€ Component Checklist
    β”œβ”€β”€ Code Review Guide
    └── Release Process

Component Maturity Levels

Not all components are equal. Define maturity levels:

Code:

interface ComponentMetadata {
  name: string;
  maturity: 'alpha' | 'beta' | 'stable' | 'legacy';
  a11yStatus: 'wcag-a' | 'wcag-aa' | 'wcag-aaa';
  lastUpdated: Date;
  maintainer: string;
  usageStats: {
    adoptionRate: number;
    bugReports: number;
  };
}

// Alpha: Experimental, breaking changes expected
// Beta: Mostly stable, minor breaking changes possible
// Stable: Production-ready, semantic versioning
// Legacy: Deprecated, plan migration

Auto-Generated Documentation

Use TypeScript to auto-generate parts of documentation:

Code:

/**
 * The Button component is the primary interactive element.
 * 
 * @example
 *

tsx

  • Click me

Code:

* 
 * @accessibility
 * - Supports keyboard navigation (Space/Enter to activate)
 * - Screen reader announces button role and label
 * - Respects prefers-reduced-motion
 */
export interface ButtonProps {
  /** Visual style of the button */
  variant?: 'primary' | 'secondary' | 'tertiary';
  
  /** Size of the button */
  size?: 'small' | 'medium' | 'large';
  
  /** Disabled state */
  disabled?: boolean;
  
  /** Click handler */
  onClick?: () => void;
}

export function Button(props: ButtonProps) { /* ... */ }

Conclusion

A design system is a long-term investment. The first 3-6 months feel slow because you're establishing foundations. But at month 12, you realize new features ship 40% faster because everyone uses the same components. By year two, the system pays for itself through reduced maintenance and faster development.

The teams we work with consistently tell us that investing in a proper design system was one of their best decisions. It doesn't just improve productsβ€”it improves how teams communicate and collaborate. Designers and developers use the same language. New team members get up to speed faster. Quality improves because patterns are proven.

design-systemreacttypescriptstorybooktesting
Share this article:

About the Author

V

Viprasol Tech Team

Custom Software Development Specialists

The Viprasol Tech team specialises in algorithmic trading software, AI agent systems, and SaaS development. With 1000+ projects delivered across MT4/MT5 EAs, fintech platforms, and production AI systems, the team brings deep technical experience to every engagement.

MT4/MT5 EA DevelopmentAI Agent SystemsSaaS DevelopmentAlgorithmic Trading

Need a Modern Web Application?

From landing pages to complex SaaS platforms β€” we build it all with Next.js and React.

Free consultation β€’ No commitment β€’ Response within 24 hours

Viprasol Β· Web Development

Need a custom web application built?

We build React and Next.js web applications with Lighthouse β‰₯90 scores, mobile-first design, and full source code ownership. Senior engineers only β€” from architecture through deployment.