Design Engineering
Design & Architecture

Design System

Establish the visual language of the application through design tokens, typography scales, and spacing systems before components are built.

A design system is the single source of truth for the visual language of an application. It defines the building blocks that every component is constructed from. Without it, every developer makes independent styling decisions that accumulate into an incoherent, unmaintainable interface.

Design Tokens

Design tokens are named variables that store design decisions. They are the mechanism that makes a design system consistent, themeable, and maintainable. Every color, spacing value, font size, and animation duration that appears more than once in the codebase should be a token.

Tokens exist at two levels: primitive tokens (the raw values) and semantic tokens (tokens that reference primitives and carry meaning). Only semantic tokens should appear in component code.

Token architecture — Tailwind CSS v4 + CSS custom properties
/* globals.css */

:root {
  /* Primitive tokens — raw values, rarely used directly */
  --zinc-50:  #fafafa;
  --zinc-100: #f4f4f5;
  --zinc-200: #e4e4e7;
  --zinc-500: #71717a;
  --zinc-900: #18181b;
  --zinc-950: #09090b;

  --blue-500: #3b82f6;
  --blue-600: #2563eb;
  --red-500:  #ef4444;
  --green-500:#22c55e;

  /* Semantic tokens — reference primitives, carry intent */
  --color-background:    var(--zinc-50);
  --color-foreground:    var(--zinc-950);
  --color-border:        var(--zinc-200);
  --color-muted:         var(--zinc-100);
  --color-muted-fg:      var(--zinc-500);

  --color-primary:       var(--zinc-900);
  --color-primary-fg:    var(--zinc-50);

  --color-destructive:   var(--red-500);
  --color-success:       var(--green-500);
  --color-info:          var(--blue-500);
}

Never reference primitive tokens in component code. See Dark Mode & Theming for the full token architecture, system detection implementation, and dark mode testing strategy. Write bg-background, not bg-zinc-50. When you add dark mode or a white-label theme, you change only the semantic token values — all components update automatically.

Color System

A functional color system has four categories. Each category maps to a semantic purpose, not a visual color.

CategoryTokensUsed for
Neutralsbackground, foreground, muted, muted-fg, borderPage backgrounds, body text, dividers, placeholder text
Brandprimary, primary-fgPrimary call-to-action buttons, active states, key highlights
Semanticdestructive, success, warning, infoValidation errors, success confirmations, warning notices
Surfacecard, popover, sidebarElevated surfaces, overlays, distinct content regions

Contrast requirements

Every foreground/background color pair must meet WCAG 2.1 AA contrast ratios. This is a functional requirement, not an aesthetic preference.

Text typeMinimum ratioEnhanced (AAA)
Normal text (under 18px)4.5 : 17 : 1
Large text (18px+ or 14px+ bold)3 : 14.5 : 1
UI components and graphical objects3 : 1

Typography Scale

Use a modular type scale — a set of sizes based on a consistent ratio — rather than arbitrary font sizes chosen per component. A common scale uses a ratio of 1.25 (Major Third) or 1.333 (Perfect Fourth).

Type scale — globals.css @theme
@theme inline {
  /* Font families */
  --font-sans: 'Inter', system-ui, sans-serif;
  --font-mono: 'Geist Mono', ui-monospace, monospace;

  /* Type scale — Major Third (×1.25) */
  --text-xs:   0.64rem;    /* 10.24px — labels, captions */
  --text-sm:   0.8rem;     /* 12.8px  — secondary body, table cells */
  --text-base: 1rem;       /* 16px    — primary body text */
  --text-lg:   1.25rem;    /* 20px    — lead paragraphs, card titles */
  --text-xl:   1.563rem;   /* 25px    — section headings (h3) */
  --text-2xl:  1.953rem;   /* 31px    — page headings (h2) */
  --text-3xl:  2.441rem;   /* 39px    — hero headings (h1) */
  --text-4xl:  3.052rem;   /* 49px    — display headings */

  /* Line heights */
  --leading-tight:  1.25;  /* Headings */
  --leading-snug:   1.375; /* Sub-headings */
  --leading-normal: 1.5;   /* Body text */
  --leading-relaxed:1.625; /* Long-form content */
}

Usage rules

  • Body text must never be below 14px (0.875rem) for readability
  • Use maximum 2 font families per application — one for UI, one for monospace code
  • Line length (measure) for body text: 60–80 characters. Use max-w-prose or max-w-2xl
  • Heading hierarchy must be sequential: no skipping from H1 to H3
  • Font weight: use 400 (regular) for body, 500–600 (medium/semibold) for UI labels, 700 (bold) for headings

Spacing System

All spacing — margin, padding, gap — must come from a predefined scale. Tailwind CSS provides a 4px-base scale (1 = 4px) which maps to p-1 through p-96. Use this scale exclusively. Arbitrary values like p-[13px] are a sign that the design does not align with the spacing system.

Tokenpx valueCommon use
space-1 (4px)4pxIcon-to-label gap, tight inline spacing
space-2 (8px)8pxBetween related items within a component
space-3 (12px)12pxInternal component padding
space-4 (16px)16pxStandard component padding, stack gaps
space-6 (24px)24pxSection internal spacing
space-8 (32px)32pxBetween components on a page
space-12 (48px)48pxBetween major sections
space-16 (64px)64pxPage-level vertical rhythm

Breakpoints

Define breakpoints based on content needs, not device names. The goal is for content to look right at every width, not to target specific devices.

Standard breakpoint set
/* Tailwind v4 default — extend as needed */
--breakpoint-sm:  640px;    /* Minimum: large phones landscape */
--breakpoint-md:  768px;    /* Tablets */
--breakpoint-lg:  1024px;   /* Small laptops */
--breakpoint-xl:  1280px;   /* Standard desktops */
--breakpoint-2xl: 1536px;   /* Wide screens */

/* Usage in components — always mobile-first */
className="flex-col md:flex-row"
className="text-sm lg:text-base"
className="grid-cols-1 sm:grid-cols-2 lg:grid-cols-3"

Motion & Transitions

Define animation durations and easing functions as tokens. Consistent motion creates a sense of physical cohesion across the interface. Inconsistent durations make the UI feel assembled from parts.

Motion tokens
:root {
  /* Durations */
  --duration-instant:  50ms;   /* Feedback: hover, focus rings */
  --duration-fast:     100ms;  /* Small element entrances */
  --duration-moderate: 200ms;  /* Most UI transitions: modals, drawers */
  --duration-slow:     400ms;  /* Large-area transitions, page changes */

  /* Easing */
  --ease-out:   cubic-bezier(0.0, 0.0, 0.2, 1);  /* Elements entering view */
  --ease-in:    cubic-bezier(0.4, 0.0, 1, 1);     /* Elements leaving view */
  --ease-inout: cubic-bezier(0.4, 0.0, 0.2, 1);  /* Position changes */
}

Always respect the prefers-reduced-motion media query. Users who have requested reduced motion should not be subjected to animations. Wrap all motion in @media (prefers-reduced-motion: no-preference) or use the Tailwind motion-safe: variant.

Token Naming Conventions

Token names must be semantic and role-based, not value-based. A token named --color-blue-500 cannot be repurposed without renaming it. A token named --color-interactive can be changed from blue to any color without touching any component code.

Bad (value-based)Good (semantic)Reason
--color-gray-100--color-background-subtleDescribes purpose, not shade
--color-blue-600--color-primaryCan be rebranded without renaming
--font-size-14--text-smScale-relative, not pixel-dependent
--spacing-16px--space-4Abstracted from specific px value
--border-gray--border-defaultContext-independent

AI agents can generate the complete token file from a design specification. Provide your brand colours, type scale ratio, and spacing base, and ask for a full globals.css with primitive tokens, semantic tokens, and dark mode overrides. Agents can also generate TypeScript token type definitions for compile-time safety. Always verify dark mode contrast ratios — this is the most common gap in agent-generated tokens.

On this page