Design Systems

A design system is not a component library. The library is the deliverable people notice; the system is the library plus the tokens it's built from, the documentation that explains when to use each part, the contribution process, the release discipline, and the social agreement that this is how the organization builds interfaces.

That distinction explains the field's most common failure. Teams build a beautiful set of components, publish it, and discover a year later that product teams are still writing one-off buttons — because the system's components didn't fit their cases, the documentation didn't answer their questions, and nobody had authority or incentive to close the gap. Design systems fail on adoption far more often than on craft.

The successful ones tend to start small, grow from demonstrated demand rather than speculation, and treat the product teams as customers rather than as an audience.

TL;DR

Quick Example

A component designed for composition rather than configuration:

The composition version absorbs requirements the author never anticipated, which is precisely what determines whether a system survives contact with real products.

Core Concepts

The layers

Each layer is derived from the one below. When a product team needs a value that isn't in the token set, that's a signal about the token set — not a reason to hardcode a hex value.

What belongs in the system

The test is repetition: something built for the third time in three different products belongs in the system. Something speculative does not — a component built for an imagined future consumer usually has the wrong API when a real one appears.

Build vs. adopt

Level 2 or 3 is right for most organizations. Headless libraries have already solved keyboard navigation, focus management, screen reader semantics, and the genuinely hard parts of a combobox or a modal — problems that take months to get right and are invisible until they're wrong. Building those yourself is rarely the differentiating work.

Level 4 is correct when speed matters more than visual distinctiveness, such as internal tools. Level 1 is correct when you have a dedicated systems team and a strong brand requirement.

Documentation

Documentation is not a byproduct of the system; it's the interface to it. Each component needs:

Tools: Storybook for component development and interaction testing, Docusaurus or a custom site for narrative guidance, and type-generated prop tables so the documentation can't drift from the code. See Docs-as-Code.

Versioning and release

Visual changes deserve care: a button whose padding changes by 4px breaks layouts downstream even though nothing in the API changed. Treat significant visual changes as breaking, ship a codemod with any rename, and support the previous major for a stated period.

A changeset-driven release (Changesets, semantic-release) with an automatically generated changelog removes the friction that otherwise makes teams batch up risky releases.

Governance

Federated is the common landing point, and it needs an explicit contribution process: how to propose a component, what "done" means (tests, docs, a11y review, tokens only), who reviews, and how long it takes. Without that, contributors are blocked and go build their own thing — which is exactly the outcome the system exists to prevent.

Adoption

This is the part that decides whether the investment pays off, and it's mostly not a technical problem.

Measure the right things

Instrumenting this is feasible: lint rules that flag raw hex colors and non-token spacing, a codebase scan for local component definitions that shadow system ones, and analytics on the documentation site.

Make the system the easiest path

Adoption follows convenience far more reliably than it follows mandates. That means: excellent documentation with copyable examples, a CLI or template that scaffolds correctly by default, codemods for migrations, fast responses to contribution requests, and — critically — components that actually cover the real cases rather than the idealized ones.

Fix the escape hatches

When a team writes a one-off component, that's information. Either the system component was missing a capability, the documentation didn't surface it, or the API made the case awkward. Treat each one as a bug report about the system rather than as non-compliance.

Best Practices

Start with what's already repeated

Audit the existing products for duplicated components. The five most-duplicated ones are your first release, and they arrive with proven demand and known requirements — a far better starting point than a component inventory drawn from a design file.

Build on a headless foundation

Radix, Ark UI, or React Aria give you correct keyboard interaction, focus trapping, and ARIA semantics. These are hard, boring, and heavily tested upstream. Spend your effort on the token system and the API design, which is where your organization's needs are actually specific.

Design APIs for composition

Compound components (Card.Header) and slot props absorb requirements you can't anticipate. A component with more than about eight props is usually one that should have been decomposed.

Ship accessibility as a system property

Contrast ratios come from the color system. Focus visibility comes from the token set. Keyboard behavior and ARIA come from the components. Solved once, every consumer inherits it; solved per feature, it's relitigated forever and inconsistently. See Accessibility.

Allow controlled escape hatches

A className passthrough and an asChild pattern let teams handle the 5% of cases the system doesn't cover, without forking. A system with no escape hatch gets abandoned at the first hard case; one with an unbounded style prop stops being a system.

Version and communicate changes properly

Semver, generated changelogs, codemods for renames, and a deprecation period. Teams that have been burned by a surprise breaking change pin the version and stop upgrading, which is how a system quietly dies.

Treat product teams as customers

Office hours, a responsive support channel, and short turnaround on contributions. The systems that get adopted are the ones where asking for something works. This is a service relationship, not a compliance one.

Test visually

Storybook plus visual regression (Chromatic, Playwright screenshots) catches the changes unit tests can't see — a padding shift, a broken dark variant, a focus ring that disappeared. For a shared library, visual regressions are the expensive kind of bug because they land everywhere at once.

Common Mistakes

Building the whole library before anyone uses it

Configuration props instead of composition

Hardcoding values inside components

No escape hatch

Measuring component count as success

Treating accessibility as a component-by-component task

FAQ

When is a design system worth building?

When the cost of inconsistency exceeds the cost of coordination — usually multiple products or teams, or one product with enough surface that the same component is being rebuilt. Below that, adopt an existing library and layer your tokens on it. A two-person team building a design system is usually avoiding the product.

Should we build from scratch or adopt something?

Adopt, then customize. Headless libraries (Radix, React Aria) have solved the accessibility and interaction problems that take the longest and are hardest to verify. shadcn/ui's copy-in model is popular precisely because you own the resulting code while starting from something correct. Build from scratch only with a dedicated team and requirements nothing else meets.

Who should own it?

A small dedicated core team (often 2–5 people spanning design and engineering) that owns standards, tokens, infrastructure, and review — with product teams contributing components under that review. Pure central ownership becomes a bottleneck; pure federation produces inconsistency. The core team's most important output is often unblocking contributors rather than writing components.

How do we handle multiple brands or themes?

Through the token layer. Semantic tokens (color-action-primary) reference primitive tokens (blue-600), and a brand swaps the primitive mapping while every component stays unchanged. Components must never reference primitives directly — that single rule is what makes multi-brand possible. See Design Tokens and Dark Mode & Theming.

How do we keep design and code in sync?

Generate both from the same token source — Figma variables and CSS custom properties from one pipeline (Style Dictionary, Tokens Studio). For components, accept that a Figma component and a React component are two implementations of one contract and require review on both sides for changes. Perfect automatic sync is a persistent aspiration; a shared token pipeline plus a shared review process is what actually works.

How do we get teams to actually use it?

Make it the easiest path, respond fast to gaps, and demonstrate value on a real product first. Mandates without quality produce compliance theater — teams wrapping system components in their own layer. The strongest adoption lever is a flagship product visibly built on the system, shipping faster because of it.

Related Topics

References