Part 03 of 13

The Visual Language

Brand tokens, a component inventory, and interaction patterns that ensure every screen built by any agent looks and feels like the same product.

~8 minute read

Why Visual Decisions Before Code

AI agents make visual decisions independently. Without constraints, Agent A picks blue buttons while Agent B picks purple. Agent A uses 16px padding while Agent B uses 24px. Within two milestones, the product looks like it was built by five different people with five different opinions.

The visual language has three layers: brand tokens (colors, typography, spacing), a component inventory (what components exist and how they behave), and UX coherence guidelines (how the product feels in motion).

Principle: Implementation-ready, not aspirational. "Use a purple accent" is aspirational. --color-accent: #5E6AD2 with usage rules (primary actions, active states, focused inputs) is implementation-ready. Every visual decision must resolve to a CSS custom property or a specific value that code can reference.

Brand Tokens

Brand tokens are the atomic visual decisions: colors, fonts, sizes, spacing, radii, shadows. They are defined as CSS custom properties in a tokens.css file that every component imports.

What to Define

CategoryWhat to DefineCount
ColorsPrimary accent, secondary, backgrounds (page, card, sidebar), text (primary, secondary, muted, inverse), borders, states (success, warning, error, info)20-30 tokens
TypographyFont families (heading, body, mono), size scale (xs through 2xl), weight scale (regular, medium, semibold, bold), line heights15-20 tokens
SpacingScale based on a base unit (typically 4px): 0.25rem, 0.5rem, 0.75rem, 1rem, 1.5rem, 2rem, 3rem, 4rem8-10 tokens
RadiiBorder radius scale: sm (4px), md (6px), lg (8px), xl (12px), full (9999px)4-5 tokens
ShadowsElevation scale: sm (subtle card), md (dropdown), lg (modal), xl (tooltip)4 tokens
TransitionsDuration scale: fast (100ms), normal (200ms), slow (300ms). Easing curves.4-6 tokens
Linear Example: Brand Tokens (Partial)
/* Linear's visual identity as CSS custom properties */
:root {
  /* Accent */
  --color-accent: #5E6AD2;        /* Linear's signature indigo */
  --color-accent-hover: #6C78E0;
  --color-accent-subtle: rgba(94,106,210,0.12);

  /* Backgrounds (dark theme is primary for Linear) */
  --bg-page: #1A1A2E;
  --bg-sidebar: #14142B;
  --bg-card: #1E1E3F;
  --bg-elevated: #252547;

  /* Text */
  --text-primary: #E8E8F0;
  --text-secondary: #9898B0;
  --text-muted: #6B6B80;

  /* Typography */
  --font-sans: 'Inter', -apple-system, sans-serif;
  --font-mono: 'JetBrains Mono', monospace;
  --text-xs: 0.75rem;
  --text-sm: 0.8125rem;
  --text-base: 0.875rem;   /* Linear uses 14px base, tighter than typical */
  --text-lg: 1rem;

  /* Spacing (4px base) */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-6: 1.5rem;
}

Note that Linear uses a dark theme as default and a tighter-than-normal base font size (14px). These are deliberate product decisions that reinforce the positioning: this is a developer tool, not an enterprise admin panel.

The Process

Step 1: Generate options visually. Do not pick colors from a palette chart. Build 3-4 rendered UI mockups (a sidebar, a card, a table row, a button group) with different color schemes. Compare them side by side. Pick the one that best matches the product positioning.

Step 2: Define every token. Once the direction is chosen, enumerate every token. Miss nothing. If a value is used in the UI, it must have a token. Hardcoded values (hex codes in component files, pixel values in CSS) are banned.

Step 3: Generate tokens.css. Output a single file that the codebase imports. This file is the single source of truth. Every component references tokens, never raw values.

Step 4: Lock it. Token changes ripple across the entire product. Do not tweak tokens during feature development. If a token needs changing, it is a deliberate, documented decision with a migration plan.

UI Component Plan

The component plan is a complete inventory of every UI component the product needs, organized into build waves by dependency order.

Three Component Categories

Primitives are the atoms: Button, Input, Badge, Avatar, Tooltip, Checkbox, Select, TextArea. Used everywhere. Built first. Zero product-specific logic.

Patterns are composed from primitives: DataTable, FilterBar, KanbanBoard, SlideOver, CommandPalette, EmptyState. Used on specific screen types. Built after primitives exist.

Overlays are layers above the page: Modal, Dialog, Dropdown, ContextMenu, Toast, Popover. Have their own z-index management. Built in parallel with patterns.

Library Decisions

For each component category, decide: build from scratch or use a library? The right answer depends on your product's UX requirements.

Recommendation: Headless + custom styling. Use a headless UI library (Radix UI, Headless UI, Ark UI) for accessibility and behavior. Apply your own styling with your tokens. This gives you full visual control without reimplementing keyboard navigation, focus management, and ARIA attributes.
Linear Example: Library Decisions
ConcernChoiceRationale
Headless UIRadix UIBest React accessibility primitives. Composable. Unstyled.
IconsCustom icon setLinear has a distinctive icon style. Custom icons reinforce the brand.
TablesTanStack TableHeadless. Supports sorting, filtering, grouping, virtual scrolling.
FormsReact Hook Form + ZodPerformant (no re-renders). Type-safe validation with Zod schemas.
Drag and DropPragmatic DnD (Atlassian)Performance-focused. Works with virtualized lists. Kanban-native.
Command PalettecmdkPurpose-built for Cmd+K interfaces. Composable. Accessible.
ChartsRecharts + custom SVGRecharts for standard charts. Custom SVG for branded visualizations.

Build Waves

Components are built in dependency order. Wave 1 has no dependencies. Wave 2 depends on Wave 1. And so on.

Linear Example: Build Waves

Wave 1 (Primitives, ~3 days): Button, Input, TextArea, Select, Checkbox, Badge, Avatar, Tooltip, Icon wrapper.

Wave 2 (Core Patterns, ~4 days): DataTable, FilterBar, EmptyState, Skeleton loaders, Toast system.

Wave 3 (Overlays + Complex, ~3 days): Modal, Dialog, SlideOver, CommandPalette (cmdk), ContextMenu, Dropdown.

Wave 4 (Domain Patterns, ~3 days): KanbanBoard, IssueRow, PropertyEditor, StatusSelector, PrioritySelector.

Wave 5 (Advanced, ~2 days): RichTextEditor, Timeline view, Gantt chart, Keyboard shortcut manager.

UX Coherence Guidelines

Tokens define how things look. Coherence guidelines define how things feel. Two features built by different agents in different weeks must have identical loading behavior, identical error handling, and identical transition timing.

Loading Patterns

Define a single loading strategy for the entire product. Every screen follows the same pattern.

Linear Example: Loading Choreography

Rule 1: Never show a spinner for operations under 200ms. If the data arrives fast, show it instantly.

Rule 2: For operations 200ms-1s, show skeleton loaders. Skeletons match the page structure: sidebar skeleton, list skeleton, card skeleton. Column headers load first (static), then content shimmers in per-row (staggered, 50ms delay between rows).

Rule 3: For operations over 1s, show skeleton + a subtle progress indicator (not a percentage bar, just a thin accent-colored line at the top of the content area).

Rule 4: Navigation between views preserves layout structure. The sidebar never re-renders. Only the content area transitions. This creates the "native app" feel.

Error Handling Patterns

Define three tiers of error presentation:

Optimistic UI Strategy

Define when to use optimistic updates and which pattern to apply. This prevents agents from guessing.

Research note [R3]: React 19's useOptimistic hook is incompatible with TanStack Query's useSyncExternalStore. Optimistic state rebases incorrectly when a query refetch finishes during an action (TanStack Query GitHub #9742). If using TanStack Query, implement optimistic updates through TanStack's own mutation callbacks, not React 19's built-in hook. See Research: R3.

Three optimistic patterns, each assigned per mutation in phase plans:

PatternWhen to UseImplementation
Via UIAdditive operations (create, toggle, mark as read)Render optimistic state using mutation's isPending and variables in JSX. Do not touch the cache.
Via CacheReorder/move/delete operations (drag-and-drop, kanban move)Manipulate query cache in onMutate. Save previous state. Restore in onError. Invalidate in onSettled.
NoneIrreversible operations (delete, submit, advance lifecycle)Wait for server confirmation. Show loading indicator on the trigger element.

Animation Timing

Define a timing scale and stick to it. Every transition in the product uses one of these durations:

CategoryDurationExamples
Micro100-150msButton press, checkbox toggle, hover states
Standard200msDropdown open, tooltip appear, tab switch
Emphasis300msModal open/close, slide-over, page transition
Slow400-500msSkeleton shimmer cycle, drag-and-drop settle

What You Leave With

DocumentSizeKey Output
Brand Guide3-5 pagesWordmark, color tokens, typography scale, usage rules
tokens.css100-200 linesCSS custom properties imported by all components
UI Component Plan5-8 pagesComponent inventory, library decisions, build waves with dependencies
UX Coherence Guidelines5-8 pagesLoading patterns, error handling, optimistic UI, animation timing, empty states, responsive strategy

These documents are excerpted into every frontend and full-stack phase plan. The phase plan's "Excerpted Guidelines" section copies the relevant rules verbatim so the executing agent does not need to reference external documents.