Component Library Design: Headless and Token-First

Building a component library is one of the highest-leverage frontend specialties. A good library compounds across the entire org. A bad one becomes a liability that engineers route around. Senior frontend interviews probe whether you understand the modern patterns — particularly the headless and token-first approaches that dominate 2026.

The headless library era

Pre-2022: component libraries were styled (Material UI, Bootstrap, Ant Design). You got a Button with built-in styles.

2022+: headless libraries provide behavior without styles. You bring your own design.

Examples:

  • Radix UI: the dominant React headless library
  • React Aria: Adobe’s offering, deeply accessible
  • Headless UI: Tailwind-paired
  • Ark UI: framework-agnostic (Vue, React, Solid)

Build your library on top of these; you get accessibility for free, focus your design effort on the visual layer.

Why headless wins

  • Accessibility: keyboard nav, ARIA, focus management — done right by experts
  • Behavior: complex state machines (combobox, tabs, dialog)
  • Customization: you control every visual aspect
  • Smaller bundles: only what you use

Token-first design

Define tokens before components:

  • Color: primitive (gray.500) and semantic (text.primary)
  • Spacing: scale (1, 2, 3 = 4px, 8px, 12px)
  • Typography: scale, font, line-height
  • Radius, elevation, motion

Tokens stored in JSON; build pipeline emits CSS variables, Sass, JS objects. Style Dictionary is the standard tool.

Theming via tokens

Themes (light/dark, brand variants) are different mappings of semantic tokens to primitives:

  • Light: text.primary → gray.900
  • Dark: text.primary → gray.100

Components consume semantic tokens. Switching theme switches the underlying mapping.

Composing components

Modern pattern: compound components.

<Tabs.Root>
  <Tabs.List>
    <Tabs.Trigger value="a">A</Tabs.Trigger>
    <Tabs.Trigger value="b">B</Tabs.Trigger>
  </Tabs.List>
  <Tabs.Content value="a">...</Tabs.Content>
  <Tabs.Content value="b">...</Tabs.Content>
</Tabs.Root>

Beats opaque <Tabs items={...} /> APIs because you can customize any sub-part.

Variants

Common pattern: components have variants (size, color, intent):

<Button variant="primary" size="md">Save</Button>

Implement with a function that maps variants to classes:

const buttonVariants = cva(
  "inline-flex items-center font-medium",
  {
    variants: {
      variant: {
        primary: "bg-blue-600 text-white",
        secondary: "bg-gray-200 text-gray-900"
      },
      size: { sm: "px-2 py-1", md: "px-4 py-2", lg: "px-6 py-3" }
    }
  }
);

Library: class-variance-authority (CVA) is the standard.

Documentation

Storybook is the industry standard:

  • One story per variant
  • Interactive controls (Args)
  • Code snippets
  • Accessibility audit (axe addon)

Modern alternatives: Ladle (faster), Histoire (Vue-friendly).

Testing

  • Unit tests for behavior (Vitest, Jest)
  • Integration tests (React Testing Library)
  • Visual regression (Chromatic, Percy)
  • Accessibility (axe-core in CI)

Versioning

Semver discipline:

  • Major: breaking changes
  • Minor: additions
  • Patch: fixes

Migration guides for every breaking change. Codemods for big migrations (Storybook does this well).

Distribution

  • Internal monorepo package (most common for company-internal libs)
  • npm package (public or private registry)
  • Tree-shakable bundle

Common antipatterns

  • Building 100 components before 10 are used
  • Hard-coded colors in components
  • No design tokens; values scattered
  • No accessibility
  • API that does not allow customization (forced “blessed” patterns)

Frequently Asked Questions

Should I build my own library or use Material UI / Chakra?

Use a library if your design is generic. Build your own if you have a distinctive design and a team of 50+ engineers.

Radix or React Aria?

Radix is the dominant React-only choice in 2026. React Aria has the strongest accessibility but a steeper learning curve.

How big should our library be?

20–50 components covers most needs. More is over-engineered. Less and engineers feel constrained.

Scroll to Top