Design Systems
Purpose
Build a component library that teams actually adopt: tokens instead of hardcoded values, components with a small honest API, and documentation that answers "which one do I use" without a meeting.
When to Use
- Starting a design system or component library.
- Auditing a codebase for inconsistency (fourteen shades of grey, six button implementations).
- Designing the API of a shared component.
- Deciding whether a new pattern belongs in the system.
Capabilities
- Token architecture: primitive, semantic, and component layers.
- Component API design: variants, sizes, states, composition.
- Theming, including dark mode and per-brand overrides.
- Documentation with live examples and usage guidance.
- Adoption tracking and migration from ad-hoc components.
Inputs
- The existing visual language, however inconsistent.
- The consuming applications and their frameworks.
- The team's appetite for governance — a system nobody enforces will not hold.
Outputs
- A token set with three layers and no hardcoded values in components.
- Components with documented variants, states, and accessibility behavior.
- A contribution and deprecation process.
Workflow
- Audit first — Extract every colour, spacing value, font size, and radius currently in use. The count is always shocking, and it is the argument for the system.
- Build tokens in layers — Primitives (
blue-600) hold raw values. Semantic tokens (color-action-primary) reference primitives and carry meaning. Component tokens (button-bg-primary) reference semantic tokens. Only the primitive layer contains literal values.
- Design the component API around variants —
variant, size, state. Not fifteen boolean props whose combinations are mostly invalid.
- Cover the states — Default, hover, active, focus-visible, disabled, loading, error. A component missing focus-visible is inaccessible, not merely incomplete.
- Document with live examples — Show the correct usage and the incorrect one. "Do / Don't" prevents more misuse than prose.
- Govern adoption — A lint rule that forbids hardcoded colours is worth more than a style guide nobody reads.
Best Practices
- Semantic tokens are what let you re-theme. A component referencing
blue-600 directly cannot be themed; one referencing color-action-primary can.
- Boolean props multiply:
isPrimary, isLarge, isDanger allows isPrimary + isDanger, which is meaningless. A variant union makes invalid combinations unrepresentable.
- Every interactive component needs a visible focus indicator. Removing the outline without replacing it is the most common accessibility failure in design systems.
- Provide an escape hatch (
className, style) but do not design around it. If every consumer overrides the same thing, the component's API is wrong.
- Version and deprecate properly. Removing a prop without a deprecation cycle breaks consumers you do not know about.
- A component used by one team is not a design-system component. Promote on the second consumer, not on speculation.
Examples
Three-layer token architecture:
:root {
/* 1. Primitives — raw values, never used directly by components. */
--blue-600: #2563eb;
--blue-700: #1d4ed8;
--grey-100: #f3f4f6;
--grey-900: #111827;
/* 2. Semantic — meaning, referencing primitives. This layer is what themes swap. */
--color-action-primary: var(--blue-600);
--color-action-primary-hover: var(--blue-700);
--color-surface: #ffffff;
--color-text: var(--grey-900);
/* 3. Component — scoped to one component, referencing semantic tokens. */
--button-bg-primary: var(--color-action-primary);
--button-bg-primary-hover: var(--color-action-primary-hover);
}
[data-theme="dark"] {
/* Only the semantic layer is redefined. Components need no changes. */
--color-action-primary: #60a5fa;
--color-surface: var(--grey-900);
--color-text: var(--grey-100);
}
Component API: variants, not boolean soup:
type ButtonProps = {
variant?: "primary" | "secondary" | "ghost" | "danger";
size?: "sm" | "md" | "lg";
loading?: boolean;
disabled?: boolean;
} & ButtonHTMLAttributes<HTMLButtonElement>;
Four variants and three sizes yield twelve valid combinations. Four booleans would yield sixteen, of which most are nonsense.
Notes
- Enforce token usage with a lint rule (
stylelint-declaration-strict-value or an ESLint rule for inline styles). Without enforcement, hardcoded values return within a month.
- A design system's real adoption metric is the number of hardcoded values remaining in consuming applications, trending toward zero. Track it.
- Do not build a component until it has two real consumers with the same requirements. Building for a hypothetical second consumer produces an API that fits neither.
1---2name: design-systems3description: Use when building or maintaining a component library and design tokens. Covers token architecture, component API design, variants and states, documentation, and governing adoption across a codebase.4---56# Design Systems78## Purpose910Build a component library that teams actually adopt: tokens instead of hardcoded values, components with a small honest API, and documentation that answers "which one do I use" without a meeting.1112## When to Use1314- Starting a design system or component library.15- Auditing a codebase for inconsistency (fourteen shades of grey, six button implementations).16- Designing the API of a shared component.17- Deciding whether a new pattern belongs in the system.1819## Capabilities2021- Token architecture: primitive, semantic, and component layers.22- Component API design: variants, sizes, states, composition.23- Theming, including dark mode and per-brand overrides.24- Documentation with live examples and usage guidance.25- Adoption tracking and migration from ad-hoc components.2627## Inputs2829- The existing visual language, however inconsistent.30- The consuming applications and their frameworks.31- The team's appetite for governance — a system nobody enforces will not hold.3233## Outputs3435- A token set with three layers and no hardcoded values in components.36- Components with documented variants, states, and accessibility behavior.37- A contribution and deprecation process.3839## Workflow40411. **Audit first** — Extract every colour, spacing value, font size, and radius currently in use. The count is always shocking, and it is the argument for the system.422. **Build tokens in layers** — Primitives (`blue-600`) hold raw values. Semantic tokens (`color-action-primary`) reference primitives and carry meaning. Component tokens (`button-bg-primary`) reference semantic tokens. Only the primitive layer contains literal values.433. **Design the component API around variants** — `variant`, `size`, `state`. Not fifteen boolean props whose combinations are mostly invalid.444. **Cover the states** — Default, hover, active, focus-visible, disabled, loading, error. A component missing focus-visible is inaccessible, not merely incomplete.455. **Document with live examples** — Show the correct usage and the incorrect one. "Do / Don't" prevents more misuse than prose.466. **Govern adoption** — A lint rule that forbids hardcoded colours is worth more than a style guide nobody reads.4748## Best Practices4950- Semantic tokens are what let you re-theme. A component referencing `blue-600` directly cannot be themed; one referencing `color-action-primary` can.51- Boolean props multiply: `isPrimary`, `isLarge`, `isDanger` allows `isPrimary + isDanger`, which is meaningless. A `variant` union makes invalid combinations unrepresentable.52- Every interactive component needs a visible focus indicator. Removing the outline without replacing it is the most common accessibility failure in design systems.53- Provide an escape hatch (`className`, `style`) but do not design around it. If every consumer overrides the same thing, the component's API is wrong.54- Version and deprecate properly. Removing a prop without a deprecation cycle breaks consumers you do not know about.55- A component used by one team is not a design-system component. Promote on the second consumer, not on speculation.5657## Examples5859**Three-layer token architecture:**6061```css62:root {63 /* 1. Primitives — raw values, never used directly by components. */64 --blue-600: #2563eb;65 --blue-700: #1d4ed8;66 --grey-100: #f3f4f6;67 --grey-900: #111827;6869 /* 2. Semantic — meaning, referencing primitives. This layer is what themes swap. */70 --color-action-primary: var(--blue-600);71 --color-action-primary-hover: var(--blue-700);72 --color-surface: #ffffff;73 --color-text: var(--grey-900);7475 /* 3. Component — scoped to one component, referencing semantic tokens. */76 --button-bg-primary: var(--color-action-primary);77 --button-bg-primary-hover: var(--color-action-primary-hover);78}7980[data-theme="dark"] {81 /* Only the semantic layer is redefined. Components need no changes. */82 --color-action-primary: #60a5fa;83 --color-surface: var(--grey-900);84 --color-text: var(--grey-100);85}86```8788**Component API: variants, not boolean soup:**8990```typescript91type ButtonProps = {92 variant?: "primary" | "secondary" | "ghost" | "danger";93 size?: "sm" | "md" | "lg";94 loading?: boolean;95 disabled?: boolean;96} & ButtonHTMLAttributes<HTMLButtonElement>;97```9899Four variants and three sizes yield twelve valid combinations. Four booleans would yield sixteen, of which most are nonsense.100101## Notes102103- Enforce token usage with a lint rule (`stylelint-declaration-strict-value` or an ESLint rule for inline styles). Without enforcement, hardcoded values return within a month.104- A design system's real adoption metric is the number of hardcoded values remaining in consuming applications, trending toward zero. Track it.105- Do not build a component until it has two real consumers with the same requirements. Building for a hypothetical second consumer produces an API that fits neither.