Skip to main content

Building Component Libraries

A component library is a collection of reusable UI elements — buttons, inputs, cards, modals, and more — built with your design tokens and ready to drop into any page. A well-built library accelerates development, enforces consistency, and makes it easy for new team members to build interfaces that look and behave correctly.

Atomic Design

Brad Frost's Atomic Design methodology provides a mental model for organizing components by complexity:

  • Atoms — The smallest elements that cannot be broken down further. A button, an input field, a label, an icon.
  • Molecules — Groups of atoms that function together. A search bar (input + button), a form field (label + input + error message).
  • Organisms — Complex groups of molecules. A navigation header (logo + nav links + search bar + user menu), a product card (image + title + price + add-to-cart button).
  • Templates — Page-level layouts composed of organisms. They define where organisms go but use placeholder content.
  • Pages — Templates filled with real content. This is what users see.

You do not need to follow this taxonomy rigidly, but thinking in layers of complexity helps you decide what to extract as a shared component.

Building Composable Components

The best components are flexible without being complicated. Here is a React button component that uses variants and design tokens:

interface ButtonProps {
  variant?: 'primary' | 'secondary' | 'ghost';
  size?: 'sm' | 'md' | 'lg';
  children: React.ReactNode;
  onClick?: () => void;
  disabled?: boolean;
}

function Button({
  variant = 'primary',
  size = 'md',
  children,
  onClick,
  disabled = false,
}: ButtonProps) {
  const baseStyles = 'inline-flex items-center justify-center font-medium rounded-md transition-colors';

  const variants = {
    primary: 'bg-blue-600 text-white hover:bg-blue-700 disabled:bg-blue-300',
    secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200 disabled:bg-gray-50',
    ghost: 'bg-transparent text-gray-700 hover:bg-gray-100 disabled:text-gray-300',
  };

  const sizes = {
    sm: 'px-3 py-1.5 text-sm',
    md: 'px-4 py-2 text-base',
    lg: 'px-6 py-3 text-lg',
  };

  return (
    <button
      className={`${baseStyles} ${variants[variant]} ${sizes[size]}`}
      onClick={onClick}
      disabled={disabled}
    >
      {children}
    </button>
  );
}

Key principles in this example:

  • Variants over booleans — Using variant="primary" is clearer and more extensible than isPrimary, isSecondary as separate booleans.
  • Sensible defaults — The component works with zero props: <Button>Click</Button> renders a medium primary button.
  • Composition via children — The button renders whatever you pass as children, whether text, an icon, or both.

Composability Patterns

Compound Components

Some components need multiple coordinated parts. A compound component pattern keeps them flexible:

<Card>
  <Card.Image src="/photo.jpg" alt="Project" />
  <Card.Body>
    <Card.Title>Project Alpha</Card.Title>
    <Card.Description>A new dashboard for analytics.</Card.Description>
  </Card.Body>
  <Card.Footer>
    <Button variant="primary" size="sm">View</Button>
  </Card.Footer>
</Card>

Each sub-component is optional. You can render a card without a footer or without an image, and the layout adjusts.

Slot Pattern

For components that need to accept custom content in specific positions, use slots:

function PageHeader({ title, action, breadcrumb }) {
  return (
    <header className="flex items-center justify-between py-4">
      <div>
        {breadcrumb && <nav className="text-sm text-gray-500 mb-1">{breadcrumb}</nav>}
        <h1 className="text-2xl font-bold">{title}</h1>
      </div>
      {action && <div>{action}</div>}
    </header>
  );
}

// Usage
<PageHeader
  title="Dashboard"
  breadcrumb={<span>Home / Dashboard</span>}
  action={<Button>Export</Button>}
/>

Component Checklist

Before adding a component to your library, verify:

  • Does it use design tokens? No hardcoded colors, spacing, or font sizes.
  • Is it accessible? Proper ARIA labels, keyboard navigation, focus styles.
  • Does it handle edge cases? Long text, empty states, loading states, error states.
  • Is it documented? Usage examples, prop descriptions, do/don't guidelines.
  • Is it tested? Unit tests for logic, visual regression tests for appearance.

A component library is only as good as its weakest component. Invest time in quality and documentation, and the team will actually use it.