Final StretchOrdered learning track

Design System Engineering

Learn Advanced JavaScript for Web / Frontend Engineering - Part 030

Design system engineering as a frontend platform discipline: tokens, primitives, component contracts, accessibility, theming, versioning, governance, documentation, testing, and migration strategy.

17 min read3249 words
PrevNext
Lesson 3035 lesson track3035 Final Stretch
#javascript#frontend#design-system#design-tokens+4 more

Part 030 — Design System Engineering

A design system is not a component library.

A component library is a set of reusable UI components.

A design system is a product engineering system for making user interfaces consistent, accessible, maintainable, themeable, measurable, and evolvable across teams and applications.

Component library = reusable implementation pieces
Design system     = decisions, contracts, tokens, components, documentation, governance, and migration path

In high-performing engineering organizations, the design system behaves like an internal platform. It has customers, APIs, versioning, compatibility guarantees, operational metrics, and a support model.

This part treats design system work as frontend platform engineering.


1. Kaufman Skill Deconstruction

To become strong at design system engineering, decompose the skill into observable sub-skills.

Sub-skillWhat You Must Be Able To DoCommon Failure
Token modelingRepresent design decisions as typed, layered, themeable tokensRaw hex values and spacing constants scattered everywhere
Primitive designBuild low-level accessible primitives with stable contractsOver-specific components that cannot compose
Component API designDesign props/events/slots that encode intent and prevent misuseBoolean explosion and ambiguous prop combinations
Accessibility contractsBake keyboard, focus, ARIA, semantics, and screen reader behavior into componentsAccessibility depends on every product engineer remembering details
ThemingSupport brand, density, color mode, contrast, and tenant variation safelyRuntime style overrides that break invariants
DocumentationExplain usage, constraints, examples, anti-patterns, and migrationStorybook full of screenshots but no decision guidance
TestingValidate visual, behavioral, accessibility, and API contractsSnapshot tests that catch little and annoy everyone
VersioningEvolve components without breaking product teams unpredictablyBreaking CSS/prop changes shipped as minor updates
GovernanceDecide contribution, review, ownership, deprecation, and support policiesDesign system becomes either bottleneck or junk drawer
Adoption strategyMigrate products incrementally with codemods and compatibility layersBig-bang rewrite that never finishes

Kaufman target performance

After this part, you should be able to review a design system component and produce an engineering assessment like this:

Component: Dialog
Layer: accessible primitive + product wrapper
Contract: controlled/uncontrolled open state; focus trap; restore focus; Escape; outside click policy
Accessibility: role="dialog"/"alertdialog"; aria-labelledby; aria-describedby; inert background; keyboard tests
Tokens: surface, overlay, radius, elevation, spacing, motion.duration.fast
Theming: supports color mode and density through semantic tokens only
API risk: avoid boolean pair isModal/isDismissible; use mode/policy object instead
Testing: unit behavior, Playwright focus cycle, axe, visual regression across themes/RTL
Versioning: adding closeReason is minor; changing focus behavior is breaking
Migration: legacy modal adapter available for one release line

That is engineering-level design system thinking.


2. Core Mental Model: A Design System Is an API

Every design system exposes APIs:

- token API
- component API
- CSS variable API
- theme API
- accessibility behavior API
- documentation API
- migration API
- governance API

If consumers depend on it, it is an API whether you acknowledge it or not.

A design system without API discipline becomes shared technical debt.


3. The Layer Model

A scalable design system should have layers.

Layer 0: Foundations
  color, typography, spacing, radius, elevation, motion, breakpoints

Layer 1: Tokens
  primitive tokens, semantic tokens, component tokens

Layer 2: Primitives
  ButtonBase, DialogPrimitive, PopoverPrimitive, VisuallyHidden, FocusScope

Layer 3: Components
  Button, Dialog, Select, Tabs, Table, Alert, Toast

Layer 4: Patterns
  Search form, filter panel, confirmation dialog, empty state, wizard stepper

Layer 5: Product templates
  Case dashboard, enforcement workflow page, review queue, admin console

Why layers matter

LayerStability ExpectationConsumer
FoundationsHighDesigners + system engineers
TokensVery highComponents + products + themes
PrimitivesHighDesign system engineers
ComponentsHighProduct engineers
PatternsMediumProduct teams
TemplatesLowerSpecific product domains

Do not force every abstraction into one component layer.


4. Design Tokens: Decisions as Data

Design tokens are named design decisions.

Bad:

.button {
  background: #2563eb;
  border-radius: 6px;
  padding: 8px 12px;
}

Better:

.button {
  background: var(--color-action-primary-bg);
  border-radius: var(--radius-control-md);
  padding-block: var(--space-2);
  padding-inline: var(--space-3);
}

Tokens let teams change design decisions without hunting raw values across codebases.

Token taxonomy

Token TypeExampleMeaning
Primitive tokencolor.blue.600Raw palette/reference value
Semantic tokencolor.action.primary.bgRole-based decision
Component tokenbutton.primary.bgComponent-specific decision
Alias tokencolor.brand.default -> color.blue.600Reference indirection
Mode tokencolor.surface.default in light/darkContext-specific value
Density tokencontrol.height.compactUI density variant

Primitive vs semantic token

Primitive token:

{
  "color": {
    "blue": {
      "600": { "$value": "#2563eb", "$type": "color" }
    }
  }
}

Semantic token:

{
  "color": {
    "action": {
      "primary": {
        "bg": { "$value": "{color.blue.600}", "$type": "color" }
      }
    }
  }
}

Product components should usually consume semantic or component tokens, not raw primitive tokens.


5. Token Layering and Theme Resolution

A robust token system separates raw values from role-based decisions.

Example resolution

color.blue.600 = #2563eb
color.action.primary.bg = {color.blue.600}
button.primary.bg = {color.action.primary.bg}
--button-primary-bg = resolved value

Why not use primitive tokens everywhere?

Because primitive tokens encode appearance, not intent.

color.blue.600 means “a blue value.”
color.action.primary.bg means “primary action background.”

Intent survives redesign better than appearance.

Token invariants

InvariantExplanation
Product code avoids raw valuesRaw hex/spacing values bypass system governance
Semantic tokens encode intentComponents should not know palette internals
Token names are stable APIsRenaming tokens is a breaking change
Modes are completeDark/high-contrast themes cannot miss token values
Tokens are typedColor, dimension, duration, font, shadow are not interchangeable
Token output is automatedDo not manually sync design and code values

6. CSS Variables as Runtime Token Delivery

CSS custom properties are the common runtime delivery mechanism for web tokens.

:root {
  --color-action-primary-bg: #2563eb;
  --color-action-primary-fg: #ffffff;
  --radius-control-md: 0.375rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
}

[data-theme="dark"] {
  --color-action-primary-bg: #60a5fa;
  --color-action-primary-fg: #0f172a;
}

Component:

.buttonPrimary {
  background: var(--color-action-primary-bg);
  color: var(--color-action-primary-fg);
  border-radius: var(--radius-control-md);
  padding-block: var(--space-2);
  padding-inline: var(--space-3);
}

Benefits

- runtime theme switching
- cascade-based scoping
- less JS for visual changes
- browser-native inheritance
- compatibility with SSR
- easier tenant/brand theming

Risks

RiskMitigation
Token missing at runtimeBuild validation + fallback policy
Arbitrary overridesPublic/private token boundary
Cascade surprisesTheme scope documentation
Performance issues with huge variable setsScope tokens reasonably
Debugging complexityDevTools-friendly naming

7. Component API Design

A component API should make correct usage easy and incorrect usage hard.

Bad:

<Button primary secondary danger large small disabled loading icon text />

This creates impossible states.

Better:

type ButtonVariant = "primary" | "secondary" | "danger" | "ghost";
type ButtonSize = "sm" | "md" | "lg";

type ButtonProps = {
  variant?: ButtonVariant;
  size?: ButtonSize;
  isLoading?: boolean;
  leadingIcon?: React.ReactNode;
  trailingIcon?: React.ReactNode;
  children: React.ReactNode;
} & React.ButtonHTMLAttributes<HTMLButtonElement>;

Better still: encode state-specific requirements.

type LoadingButtonProps = {
  isLoading: true;
  loadingLabel: string;
  children: React.ReactNode;
};

type IdleButtonProps = {
  isLoading?: false;
  loadingLabel?: never;
  children: React.ReactNode;
};

type ButtonProps = (LoadingButtonProps | IdleButtonProps) & {
  variant?: "primary" | "secondary" | "danger";
};

Now loading buttons must provide accessible loading text.

Component API invariants

InvariantExplanation
Props express intentvariant="danger", not red
Invalid combinations are impossibleUse unions instead of boolean explosion
Native behavior is preservedButton should still accept button attributes where safe
Accessibility is representedIcon-only buttons require labels
Escape hatches are explicitclassName/slotProps policy documented
Breaking changes are treated as API changesVisual behavior can be breaking too

8. Controlled vs Uncontrolled Components

Design system components often need state ownership flexibility.

Controlled component

<Dialog open={isOpen} onOpenChange={setIsOpen} />

Consumer owns state.

Uncontrolled component

<Dialog defaultOpen />

Component owns state.

Hybrid contract

type ControllableStateProps<T> = {
  value?: T;
  defaultValue?: T;
  onValueChange?: (value: T) => void;
};

Invariants

InvariantExplanation
Do not switch controlledness silentlyWarn or document behavior
Controlled state is source of truthInternal state must not drift
Change callback includes reason when neededDialog close via Escape vs submit can matter
Default value only initializesLater changes to default should not reset unexpectedly

Close reason example

type DialogCloseReason =
  | "escapeKey"
  | "outsidePointer"
  | "closeButton"
  | "submitSuccess"
  | "programmatic";

type DialogProps = {
  open?: boolean;
  defaultOpen?: boolean;
  onOpenChange?: (open: boolean, reason: DialogCloseReason) => void;
};

This is better than a bare boolean event because consumers can encode domain policy.


9. Composition Over Configuration

Design system components must balance power and simplicity.

Configuration-heavy API:

<DataTable
  columns={columns}
  showFilters
  showExport
  enableBulkActions
  enableRowExpansion
  enableColumnPinning
  enableStickyHeader
  emptyStateTitle="No cases"
  emptyStateActionLabel="Create case"
/>

Composition-oriented API:

<DataTable.Root data={cases}>
  <DataTable.Toolbar>
    <CaseFilters />
    <ExportButton />
  </DataTable.Toolbar>
  <DataTable.Header columns={columns} />
  <DataTable.Body />
  <DataTable.EmptyState>
    <EmptyState title={t("cases.empty.title")} />
  </DataTable.EmptyState>
</DataTable.Root>

Decision rule

Use configuration when:

- variation is small and finite
- behavior is standardized
- consistency matters more than flexibility

Use composition when:

- product variation is large
- teams need custom content
- layout slots are natural
- state needs explicit ownership

Anti-pattern

A “universal enterprise table” that attempts to cover every workflow through props usually becomes unmaintainable.

Prefer:

- low-level table primitives
- standard table patterns
- product-specific wrappers

10. Accessibility as a System Contract

The design system should absorb accessibility complexity.

Example: Dialog component contract.

Dialog must:
- render correct role
- associate title/description
- move focus into dialog on open
- trap focus while modal
- restore focus to opener on close
- close on Escape when allowed
- hide/inert background where appropriate
- prevent background scroll if modal
- support screen reader announcement
- expose deterministic test hooks or roles

Product teams should not re-implement this per feature.

Accessibility contract example

type DialogProps = {
  open?: boolean;
  defaultOpen?: boolean;
  onOpenChange?: (open: boolean, reason: DialogCloseReason) => void;
  modal?: boolean;
  initialFocusRef?: React.RefObject<HTMLElement>;
  restoreFocus?: boolean;
  children: React.ReactNode;
};

Icon button contract

type IconButtonProps = {
  "aria-label": string;
  icon: React.ReactNode;
  variant?: "plain" | "subtle" | "solid";
};

Do not allow icon-only buttons without accessible names.


11. Headless Components and Primitives

A headless component provides behavior and accessibility without prescribing visual style.

Examples:

DialogPrimitive
PopoverPrimitive
TabsPrimitive
ComboboxPrimitive
MenuPrimitive
TooltipPrimitive
FocusScope
DismissableLayer
VisuallyHidden

Why headless primitives matter

BenefitExplanation
Behavior reuseFocus/keyboard logic implemented once
Visual flexibilityProduct/design can style separately
Accessibility consistencyARIA and keyboard patterns centralized
Lower duplicationTeams do not rebuild tricky widgets
Easier testingPrimitive contract can be tested directly

Risk

Headless primitives are still APIs. If too low-level, every consumer assembles them incorrectly.

A mature system usually provides both:

- primitives for design system authors
- opinionated components for product engineers

12. Styling Architecture

A design system needs a styling strategy that survives scale.

Options include:

StrategyStrengthRisk
Global CSSSimple, platform-nativeCollision and implicit dependencies
CSS ModulesLocal scopingHarder cross-component theming if unmanaged
CSS-in-JS runtimeDynamic styling powerRuntime cost, SSR complexity
Compile-time CSS-in-JSType-safe/generated CSSTooling complexity
Utility CSSFast compositionToken drift if not governed
Web Components stylesEncapsulationTheming and framework integration concerns

There is no universal winner. Evaluate against:

- SSR requirements
- runtime performance
- theming needs
- design token integration
- component package distribution
- team familiarity
- debugging experience
- framework portability

Styling invariants

InvariantExplanation
Styles consume tokensRaw values are exceptions
Component internals are protectedAvoid consumers depending on private DOM structure
Public styling hooks are documentedProvide slots/classes/tokens intentionally
SSR output is deterministicAvoid hydration style mismatch
Critical styles load earlyPrevent unstyled/flashing UI
Themes do not require rerender when CSS can solve itPrefer CSS variables for visual mode changes

13. Escape Hatches

Consumers will need escape hatches.

Bad policy:

No escape hatches ever.

This causes forks and copy-paste components.

Also bad:

Override anything with arbitrary CSS.

This destroys consistency and makes upgrades risky.

Better escape hatch hierarchy

Escape HatchRiskExample
Variant propLowvariant="danger"
Size/density propLowsize="compact"
SlotsMediumfooter={<CustomFooter />}
Slot propsMediumslotProps={{ trigger: { id } }}
Data attributesMedium[data-state="open"]
CSS variablesMedium--card-padding
classNameMedium/highScoped external class
Raw styleHighOne-off visual override
DOM structure dependencyVery highConsumer targets .button > span:first-child
ForkingHighestCopy component into app

Document which escape hatches are stable API.


14. Theming Beyond Dark Mode

Theming may include:

- light/dark color mode
- high contrast
- brand/tenant identity
- density
- platform theme
- motion preference
- typography scale
- jurisdiction-specific visual standards

Theme dimensions

type ThemeConfig = {
  colorMode: "light" | "dark";
  contrast: "normal" | "high";
  density: "comfortable" | "compact";
  brand: "default" | "agency" | "partner";
  motion: "normal" | "reduced";
};

Theme resolution problem

Naively combining dimensions creates explosion.

2 color modes × 2 contrast modes × 2 densities × 5 brands = 40 theme variants

Instead, layer tokens by concern:

base primitives
brand aliases
semantic tokens
color mode overrides
contrast overrides
density overrides
component tokens

Theme invariants

InvariantExplanation
Themes are completeNo missing token under any supported combination
Contrast is testedDark mode alone does not guarantee accessibility
Density preserves target sizeCompact mode must not break usability
Brand cannot override semantics unsafelyDanger should not look like success
Theme switch avoids layout jumpToken changes should be stable where possible
User preference is respectedprefers-color-scheme, prefers-reduced-motion where appropriate

15. Motion Tokens and Interaction Feedback

Motion is part of the design system.

Tokens:

{
  "motion": {
    "duration": {
      "fast": { "$value": "120ms", "$type": "duration" },
      "normal": { "$value": "200ms", "$type": "duration" },
      "slow": { "$value": "320ms", "$type": "duration" }
    },
    "easing": {
      "standard": { "$value": "cubic-bezier(0.2, 0, 0, 1)", "$type": "cubicBezier" }
    }
  }
}

CSS:

.popover {
  transition-duration: var(--motion-duration-fast);
  transition-timing-function: var(--motion-easing-standard);
}

@media (prefers-reduced-motion: reduce) {
  .popover {
    transition-duration: 1ms;
  }
}

Motion invariants

InvariantExplanation
Motion has semantic purposeNot only decoration
Reduced motion is respectedAvoid vestibular issues
Motion does not block interactionLong animations delay workflow
Exit animations coordinate with unmountAvoid disappearing focus target
Tests account for animationE2E should not be flaky due to transitions

16. Component State Taxonomy

Every component has states.

For a button:

default
hover
active
focus-visible
disabled
loading
pressed
selected
invalid? maybe no, unless toggle-like

For an input:

empty
filled
focused
disabled
readonly
invalid
required
dirty
loading validation
success

For async data component:

idle
loading
success-empty
success-data
error-retryable
error-permission
offline
stale
refreshing

State design invariant

A design system component must define visual, semantic, and behavioral output for every supported state.

State table example

StateVisualSemanticsBehavior
Loading buttonSpinner + labelaria-busy or loading text policyPrevent duplicate submit if configured
Disabled buttonMuteddisabled if native buttonNot focusable by default
Pressed toggleActive stylearia-pressed=trueActivation toggles state
Invalid inputError stylearia-invalid, aria-describedbyError announced/linked

17. Component Contract Documentation

Good docs are not just examples. They encode decisions.

A component page should include:

- purpose
- when to use
- when not to use
- anatomy
- props/API
- accessibility behavior
- keyboard interaction
- content guidelines
- token references
- theming behavior
- examples
- anti-patterns
- migration notes
- testing guidance

Example documentation skeleton

# Dialog

## Purpose
Use Dialog when the user must complete or acknowledge a focused task before returning to the page.

## When Not To Use
Do not use Dialog for non-blocking status messages; use Toast or InlineAlert.

## Accessibility
- Uses role="dialog" by default.
- Requires visible title.
- Moves focus inside on open.
- Restores focus on close.
- Escape closes unless `escapeKeyBehavior="ignore"`.

## API
...

## Anti-patterns
- Do not put large multi-step workflows in a small dialog.
- Do not open a dialog from page load without user context.

Docs are part of the system's runtime because they shape usage.


18. Testing Design System Components

Testing must validate contracts, not implementation trivia.

Test layers

LayerWhat To Test
Type testsInvalid prop combinations fail
Unit testsState transitions and pure logic
Component testsRendered roles, labels, events
Accessibility testsAxe/static checks plus manual pattern coverage
Keyboard testsTab, arrows, Escape, Enter, Space
Visual regressionThemes, states, density, RTL
E2E smokeCritical components in real app contexts
Package testsBuild output, tree shaking, SSR import safety

Example behavioral test

it("requires accessible label for icon-only button", () => {
  // Prefer TypeScript type-level enforcement, plus runtime dev warning if needed.
});

Playwright focus test example

test("dialog traps and restores focus", async ({ page }) => {
  await page.goto("/design-system/dialog");
  await page.getByRole("button", { name: "Open dialog" }).click();

  await expect(page.getByRole("dialog")).toBeVisible();
  await expect(page.getByRole("button", { name: "Confirm" })).toBeFocused();

  await page.keyboard.press("Escape");
  await expect(page.getByRole("button", { name: "Open dialog" })).toBeFocused();
});

Visual matrix

Button:
- variants: primary, secondary, danger, ghost
- sizes: sm, md, lg
- states: default, hover, active, focus, disabled, loading
- themes: light, dark, high contrast
- directions: ltr, rtl
- density: comfortable, compact

You cannot snapshot every permutation forever. Select risk-based coverage.


19. Versioning and Compatibility

Design system versioning is harder than normal library versioning because visual and behavioral changes can break workflows.

Breaking changes include

- removing prop
- changing prop meaning
- changing DOM structure if consumers rely on it
- changing CSS variable names
- changing token names
- changing keyboard behavior
- changing focus behavior
- changing default variant
- changing spacing that breaks layout
- changing z-index/elevation policy
- changing SSR output shape

SemVer interpretation

Version TypeExamples
PatchBug fix that preserves API/behavior; accessibility correction with minimal visual change
MinorNew component, new prop, new token alias, additive variant
MajorRemoved prop, renamed token, changed behavior/defaults, incompatible DOM/styling contract

Deprecation pattern

1. Introduce new API.
2. Keep old API with warning.
3. Provide migration docs.
4. Provide codemod if possible.
5. Track adoption.
6. Remove in next major.

Runtime warning example

if (process.env.NODE_ENV !== "production") {
  if (props.isPrimary !== undefined) {
    console.warn(
      "Button: `isPrimary` is deprecated. Use `variant=\"primary\"` instead."
    );
  }
}

20. Migration Strategy

A design system rarely starts from a clean slate.

You inherit:

- legacy CSS
- inconsistent components
- product-specific forks
- old token names
- ad hoc themes
- inaccessible custom widgets
- duplicated form controls
- abandoned Storybook stories

Migration approaches

ApproachWhen It WorksRisk
Big-bang rewriteSmall app, strong mandateUsually fails at scale
Opportunistic replacementTeams already touching screensSlow, inconsistent
Compatibility wrapperLegacy API mapped to new componentTemporary complexity
Codemod migrationAPI shape can be transformedRequires strong tests
Route-by-route modernizationProduct workflows isolatedNeeds long-term tracking
Token-first migrationVisual consistency firstBehavior/accessibility may remain broken

Practical migration plan

1. Inventory components and usage.
2. Identify high-risk/high-volume primitives: Button, Input, Dialog, Select, Table.
3. Define token foundation.
4. Build compatibility wrappers for top components.
5. Add lint rule against new raw values/legacy imports.
6. Migrate critical workflows first.
7. Track adoption dashboard.
8. Deprecate old APIs with deadlines.
9. Remove after major release or product milestone.

21. Governance Model

Without governance, a design system becomes either a bottleneck or a junk drawer.

Governance questions

- Who owns foundations/tokens?
- Who approves new components?
- Who can change component APIs?
- What counts as breaking change?
- How are accessibility requirements enforced?
- How are product-specific needs evaluated?
- How are bugs prioritized?
- What is the support SLA?
- How do teams contribute safely?
- How is adoption measured?

Contribution flow

RFC template

# Component RFC: [Name]

## Problem
What user/product problem does this solve?

## Existing Alternatives
Can current components solve this?

## Proposed API
Props, slots, events, tokens.

## Accessibility Contract
Roles, keyboard behavior, focus, announcements.

## Theming/Token Needs
New tokens or aliases.

## Migration Impact
Existing usages affected?

## Open Questions
Trade-offs and unresolved constraints.

22. Component Admission Criteria

Not every useful UI belongs in the design system.

A component should be admitted when:

- multiple teams need it
- behavior is reusable
- accessibility is non-trivial
- consistency matters
- design has stable enough pattern
- maintenance owner exists
- API can be generalized without overfitting

A component should stay product-local when:

- it is domain-specific
- requirements are unstable
- only one team uses it
- abstraction would hide important business logic
- design is experimental

Rule

Promote patterns after repeated evidence, not before understanding variation.

Premature design-system abstraction creates rigid components that either block teams or acquire dozens of props.


23. Public vs Private API

A design system must define what consumers may depend on.

Public API:

- documented props
- documented events
- documented slots
- documented CSS variables
- documented data attributes
- documented token names
- documented keyboard behavior

Private implementation:

- internal DOM nesting
- internal class names
- internal helper components
- private token aliases
- implementation-specific effects

Why it matters

If consumers depend on private DOM structure, you cannot refactor internals safely.

Bad consumer CSS:

.myPage .ds-button > span:first-child {
  margin-left: -4px;
}

Better public hook:

<Button slotProps={{ icon: { className: styles.icon } }} />

Or a component token:

.myPage {
  --button-icon-gap: var(--space-1);
}

24. Design System and Frontend Architecture

The design system should not absorb application architecture.

Keep these separate:

ConcernBelongs In Design System?
Button visual/interaction contractYes
Case approval business logicNo
Dialog focus managementYes
Whether case can be deletedNo
Form field layout primitivesYes
Regulatory validation rulesNo
Toast primitiveYes
Error message contentUsually product/domain
Data table primitiveMaybe
Enforcement queue filtering logicNo

Boundary rule

The design system owns UI behavior and presentation contracts.
Product code owns domain decisions and workflow semantics.

Domain wrapper pattern

function DeleteCaseDialog({ caseId }: { caseId: string }) {
  const t = useTranslations("case.deleteDialog");
  const mutation = useDeleteCaseMutation(caseId);

  return (
    <Dialog>
      <Dialog.Title>{t("title")}</Dialog.Title>
      <Dialog.Description>{t("description")}</Dialog.Description>
      <Dialog.Footer>
        <Button variant="secondary">{t("cancel")}</Button>
        <Button variant="danger" onClick={() => mutation.mutate()}>
          {t("confirm")}
        </Button>
      </Dialog.Footer>
    </Dialog>
  );
}

The design system provides Dialog and Button. Product owns case deletion semantics.


25. Performance Considerations

Design system code is multiplied across the product.

Small inefficiencies become systemic.

Performance risks

RiskExample
Heavy componentsEvery button imports large icon library
Runtime style costCSS-in-JS recalculates styles per render
Poor tree shakingImporting one component bundles all components
Excessive context updatesTheme/state provider rerenders entire app
Uncontrolled layout costComponents measure layout during render/effect
Animation jankExpensive properties animated by default
Huge token payloadAll themes loaded for every user

Package design

@org/design-system/tokens
@org/design-system/primitives
@org/design-system/components/button
@org/design-system/components/dialog
@org/design-system/icons
@org/design-system/styles

Support direct imports if bundle size matters:

import { Button } from "@org/design-system/button";

Performance invariants

InvariantExplanation
Components are tree-shakeableConsumers pay for what they use
CSS loads predictablyAvoid FOUC and style waterfalls
Theme change is cheapCSS variables over app-wide rerender where possible
Icons are optimizedAvoid importing entire icon sets
Measurements are intentionalAvoid forced layout in reusable components
Default animations are safeNo layout-triggering animation by default

26. Packaging and Distribution

A design system package must work across target build environments.

Package concerns

- ESM/CJS policy
- TypeScript declarations
- CSS distribution
- sideEffects field
- peer dependencies
- React/framework version compatibility
- SSR safety
- browser-only code guards
- tree shaking
- source maps
- changelog

Example package exports

{
  "name": "@org/design-system",
  "type": "module",
  "exports": {
    "./button": {
      "types": "./dist/button/index.d.ts",
      "import": "./dist/button/index.js"
    },
    "./dialog": {
      "types": "./dist/dialog/index.d.ts",
      "import": "./dist/dialog/index.js"
    },
    "./styles.css": "./dist/styles.css"
  },
  "sideEffects": [
    "**/*.css"
  ]
}

SSR safety

Bad:

const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;

at module top-level.

Better:

function getPreferredColorMode() {
  if (typeof window === "undefined") {
    return "light";
  }

  return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
}

27. Documentation and Interactive Sandboxes

Storybook or equivalent documentation should be more than a gallery.

Useful docs include:

- controlled examples
- uncontrolled examples
- async examples
- form integration
- accessibility notes
- keyboard interaction table
- token table
- theme switcher
- RTL toggle
- density toggle
- error states
- loading states
- anti-pattern examples
- migration notes

Story quality checklist

[ ] Shows default state
[ ] Shows all variants
[ ] Shows disabled/loading/error states
[ ] Shows long text
[ ] Shows icon and no-icon cases
[ ] Shows keyboard/focus behavior
[ ] Shows RTL
[ ] Shows dark/high contrast theme
[ ] Includes accessibility notes
[ ] Includes usage guidance

Docs should teach decisions, not just display components.


28. Linting and Static Enforcement

Adoption improves when correct behavior is enforced automatically.

Possible rules:

- no raw hex values in product CSS
- no direct use of deprecated tokens
- no import from internal design system paths
- icon-only button requires aria-label
- no legacy component imports
- no physical CSS properties in RTL-supported surfaces
- no unsupported variant names
- no hardcoded spacing outside allowed contexts

Example ESLint-style intent

Rule: no-raw-design-values
Detect: #fff, #000, px spacing values, arbitrary box-shadow
Allow: token definition files, one-off documented exceptions
Fix: suggest semantic token

Why linting matters

Review comments do not scale. Static enforcement turns design system policy into immediate feedback.


29. Observability and Adoption Metrics

A design system needs operational visibility.

Track:

- package versions used per app
- component usage counts
- deprecated API usage
- raw token/value violations
- accessibility test failures
- visual regression failures
- bundle size impact
- migration progress
- issue volume by component
- time to resolve design system bugs

Adoption dashboard example

Application: enforcement-admin
Design system version: 3.4.2
Deprecated imports: 18
Raw color violations: 42
Legacy modal usages: 7
A11y component test failures: 0
Top components: Button 412, Input 203, Dialog 34, Table 19
Migration target: v4 by 2026-09-01

Metrics prevent design system work from becoming opinion-driven only.


30. Failure Modes at Scale

Failure ModeSymptomRoot CauseCountermeasure
Junk drawer systemToo many inconsistent componentsNo admission criteriaRFC + ownership model
Bottleneck systemTeams wait weeks for simple changesCentral team owns everythingContribution model + primitives
Forked componentsProducts copy and modifyEscape hatches missingStable customization API
Token chaosRaw values everywhereToken governance weakLint + token docs + migration
Accessibility regressionEach app implements behavior differentlyA11y not centralizedAccessible primitives + tests
API explosionComponents have dozens of booleansOver-configured abstractionComposition + domain wrappers
Theme breakageDark/brand mode incompleteToken matrix untestedToken completeness tests
Upgrade fearTeams stuck on old versionBreaking changes unmanagedSemVer + codemods + deprecation
Poor performanceComponents bloat all appsPackaging/tree-shaking weakImport boundaries + bundle checks

31. Case Study: Enforcement Platform Design System

Imagine a regulatory enforcement platform used by investigators, supervisors, legal reviewers, and external respondents.

Product requirements

- dense data tables
- multi-step workflows
- legal confirmation dialogs
- status badges
- deadline alerts
- document upload flows
- audit-friendly UI states
- accessibility compliance
- internationalization
- tenant/jurisdiction theming

Design system architecture

Foundations:
  color, typography, spacing, motion, elevation, radius, z-index

Tokens:
  semantic status tokens: success, warning, danger, info, neutral
  workflow tokens: overdue, dueSoon, blocked, escalated

Primitives:
  FocusScope, DialogPrimitive, TooltipPrimitive, MenuPrimitive

Components:
  Button, Badge, Alert, Dialog, FormField, TextArea, Select, DataTable

Patterns:
  ConfirmationDialog, DeadlineBanner, ReviewPanel, AssignmentPicker

Product wrappers:
  CaseStatusBadge, EnforcementActionButton, LegalNoticeDialog

Important boundary

Badge belongs in the design system.

CaseStatusBadge may belong in a domain UI package because it maps regulatory case states to badge semantics.

function CaseStatusBadge({ status }: { status: CaseStatus }) {
  const t = useTranslations("case.status");

  const variantByStatus = {
    OPEN: "info",
    UNDER_REVIEW: "warning",
    ESCALATED: "danger",
    CLOSED: "neutral",
  } as const;

  return (
    <Badge variant={variantByStatus[status]}>
      {t(status)}
    </Badge>
  );
}

The design system owns Badge. The domain package owns case semantics.


32. Capstone Exercise for This Part

Design a production-ready Dialog component spec.

Deliverables:

1. Component purpose and non-goals
2. Public API
3. Controlled/uncontrolled behavior
4. Accessibility contract
5. Keyboard behavior table
6. Focus lifecycle
7. Token usage
8. Theming behavior
9. RTL considerations
10. Test matrix
11. Migration plan from legacy modal
12. Versioning policy

Starter API

type DialogCloseReason =
  | "escapeKey"
  | "outsidePointer"
  | "closeButton"
  | "programmatic";

type DialogProps = {
  open?: boolean;
  defaultOpen?: boolean;
  onOpenChange?: (open: boolean, reason: DialogCloseReason) => void;
  modal?: boolean;
  initialFocusRef?: React.RefObject<HTMLElement>;
  restoreFocus?: boolean;
  children: React.ReactNode;
};

Test matrix

Behavior:
  opens/closes
  controlled state
  uncontrolled state
  Escape policy
  outside click policy
  nested dialog policy

Accessibility:
  role/name/description
  initial focus
  focus trap
  restore focus
  screen reader announcement

Visual:
  light/dark
  high contrast
  compact density
  long title/body
  RTL

Integration:
  SSR import
  route transition cleanup
  form submit inside dialog
  async close after mutation

33. Production Readiness Checklist

[ ] Token taxonomy documented
[ ] Token build pipeline automated
[ ] Raw values blocked or tracked
[ ] Components consume semantic/component tokens
[ ] Public/private API boundary documented
[ ] Accessibility contracts written for interactive components
[ ] Keyboard behavior tested
[ ] Focus lifecycle tested
[ ] RTL and i18n considered
[ ] Theme matrix validated
[ ] Visual regression coverage exists
[ ] Package exports are tree-shakeable
[ ] SSR safety validated
[ ] Component docs include usage and anti-patterns
[ ] Deprecation policy exists
[ ] Changelog is maintained
[ ] Contribution RFC process exists
[ ] Adoption metrics are tracked
[ ] Migration path from legacy components exists

34. Summary

Design system engineering is frontend platform engineering.

The key mental models:

  1. A design system is an API.
  2. Tokens are design decisions as data.
  3. Semantic tokens encode intent better than raw values.
  4. Components need contracts, not just props.
  5. Accessibility belongs in primitives and components by default.
  6. Composition often scales better than configuration.
  7. Theming is multi-dimensional, not just dark mode.
  8. Documentation is part of the product API.
  9. Versioning must account for visual and behavioral breaking changes.
  10. Governance prevents both bottlenecks and chaos.

A top-tier frontend engineer does not merely consume design system components. They understand how design system decisions propagate through product architecture, accessibility, performance, localization, testing, and long-term maintainability.


References

  • Design Tokens Community Group: https://www.designtokens.org/
  • Design Tokens Format Module: https://www.designtokens.org/tr/drafts/format/
  • W3C Design Tokens Community Group: https://www.w3.org/community/design-tokens/
  • MDN CSS Custom Properties: https://developer.mozilla.org/en-US/docs/Web/CSS/--*
  • MDN CSS Logical Properties and Values: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_logical_properties_and_values
  • WAI-ARIA Authoring Practices Guide: https://www.w3.org/WAI/ARIA/apg/
  • Storybook Accessibility Testing: https://storybook.js.org/docs/writing-tests/accessibility-testing
Lesson Recap

You just completed lesson 30 in final stretch. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.

Continue The Track

Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.