I've built four design systems in my career. Three of them are dead. One of them is still running, maintained by a team that didn't build it, powering a product used by tens of thousands of people every day. The difference between the dead ones and the living one wasn't technology. It wasn't tooling. It was architecture decisions made in the first two weeks.

This is a breakdown of what I learned from each failure — and the principles that finally produced something durable.

The Three Failures

Failure 1: The God Component

The first design system I built had one fatal flaw: I tried to make every component do everything. A single <Button> component that accepted 24 props. A <Card> component with nine variants, seven size options, and a conditional rendering tree that took up 180 lines.

// What I built — The God Button

<Button
  variant="primary"
  size="md"
  iconLeft="arrow"
  iconRight="chevron"
  loading={false}
  disabled={false}
  fullWidth={false}
  rounded="sm"
  // ... 16 more props
/>

This felt powerful at first. One component, infinite flexibility. But what happened in practice: nobody could remember the API. New developers spent twenty minutes reading the docs just to add a button. And when design changed one variant, the ripple effects were unpredictable — changing variant="ghost" broke something in variant="outline" because the logic was so entangled.

The system rotted within six months. Nobody wanted to touch it.

Failure 2: The Unstyled Purity Trap

After Failure 1, I overcorrected. I'd been reading about headless UI libraries, and I was convinced that zero-opinion, fully unstyled components were the future. I built an entire system with no styles at all — just accessibility logic and behaviour, expecting teams to style everything themselves.

The problem: our team was two developers, both of us building features full-time. Nobody had time to retheme unstyled primitives. Every new page became a negotiation about what the base styles should be. We ended up with three different implementations of the same "modal" across the codebase, each styled slightly differently. The design system technically existed, but it wasn't creating consistency — it was creating the illusion of it.

Failure 3: The Documentation Graveyard

The third system was technically well-built. Good component API, reasonable coverage, a Storybook with every variant documented. It died because the documentation was never updated after the initial release. By month four, the docs described components that had been changed, deprecated components that were still listed as current, and failed to mention three components that had been added after launch.

A design system without living documentation isn't a system — it's a snapshot. And snapshots become lies the moment anything changes.

What Finally Worked

The Principle: Composition Over Configuration

The surviving system was built on one core idea: small, focused components that compose together, rather than large, configurable components that try to anticipate everything.

// What worked — Composable primitives

<Button>Submit</Button>

<Button variant="ghost">
  <Icon name="arrow-left" />
  Go back
</Button>

<Button asChild>
  <a href="/dashboard">Open Dashboard</a>
</Button>

Each component does one job well. Composition handles complexity. The API surface is small, the mental model is simple, and changes to one component don't cascade unpredictably into another.

Design Tokens as the Foundation

Everything in a design system that changes together should be defined in one place. Colours, spacing, typography, border radii, shadows — all of it lives in a token file. Components reference tokens, never raw values.

/* tokens.css */
:root {
  /* Colour */
  --color-primary:     #ff5c1a;
  --color-surface:     #0d121d;
  --color-border:      #1e2633;

  /* Typography */
  --font-body:         'DM Sans', sans-serif;
  --font-mono:         'IBM Plex Mono', monospace;
  --text-base:         16px;
  --leading-body:      1.75;

  /* Spacing (8-point grid) */
  --space-1:  4px;
  --space-2:  8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-6: 24px;
  --space-8: 32px;
}

When the brand colour changes, you update one line. When the design team decides to tighten spacing across the whole product, you update the token. No component hunt required.

The Documentation Rule

No component ships without a story. No story ships without a usage example. And — this is the part that actually saved us — documentation lives in the same file as the component. Not in a separate docs site that will inevitably fall behind. In JSDoc comments, in inline prop descriptions, in the Storybook story that auto-generates from the component itself.

When a developer changes the component, the documentation is right there, staring at them. You can't update the logic and forget to update the docs because they're in the same edit.

The Checklist I Use Now

Design systems fail for the same reason most software fails: they're treated as a product launch instead of a living infrastructure. You don't finish a design system. You tend it. The teams that understand that are the ones with systems still running three years later.