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.