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).
--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
| Category | What to Define | Count |
|---|---|---|
| Colors | Primary accent, secondary, backgrounds (page, card, sidebar), text (primary, secondary, muted, inverse), borders, states (success, warning, error, info) | 20-30 tokens |
| Typography | Font families (heading, body, mono), size scale (xs through 2xl), weight scale (regular, medium, semibold, bold), line heights | 15-20 tokens |
| Spacing | Scale based on a base unit (typically 4px): 0.25rem, 0.5rem, 0.75rem, 1rem, 1.5rem, 2rem, 3rem, 4rem | 8-10 tokens |
| Radii | Border radius scale: sm (4px), md (6px), lg (8px), xl (12px), full (9999px) | 4-5 tokens |
| Shadows | Elevation scale: sm (subtle card), md (dropdown), lg (modal), xl (tooltip) | 4 tokens |
| Transitions | Duration scale: fast (100ms), normal (200ms), slow (300ms). Easing curves. | 4-6 tokens |
/* 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.
| Concern | Choice | Rationale |
|---|---|---|
| Headless UI | Radix UI | Best React accessibility primitives. Composable. Unstyled. |
| Icons | Custom icon set | Linear has a distinctive icon style. Custom icons reinforce the brand. |
| Tables | TanStack Table | Headless. Supports sorting, filtering, grouping, virtual scrolling. |
| Forms | React Hook Form + Zod | Performant (no re-renders). Type-safe validation with Zod schemas. |
| Drag and Drop | Pragmatic DnD (Atlassian) | Performance-focused. Works with virtualized lists. Kanban-native. |
| Command Palette | cmdk | Purpose-built for Cmd+K interfaces. Composable. Accessible. |
| Charts | Recharts + custom SVG | Recharts 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.
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.
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:
- Transient errors (network glitch, timeout): Toast notification, bottom-right, auto-dismiss after 5s, with retry action.
- Persistent errors (validation failure, permission denied): Inline message below the affected field or section. Does not auto-dismiss. Clears when the user corrects the input.
- Catastrophic errors (page crash, data load failure): Full-page error state with branded illustration, clear message, and primary action (retry or go home).
Optimistic UI Strategy
Define when to use optimistic updates and which pattern to apply. This prevents agents from guessing.
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:
| Pattern | When to Use | Implementation |
|---|---|---|
| Via UI | Additive operations (create, toggle, mark as read) | Render optimistic state using mutation's isPending and variables in JSX. Do not touch the cache. |
| Via Cache | Reorder/move/delete operations (drag-and-drop, kanban move) | Manipulate query cache in onMutate. Save previous state. Restore in onError. Invalidate in onSettled. |
| None | Irreversible 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:
| Category | Duration | Examples |
|---|---|---|
| Micro | 100-150ms | Button press, checkbox toggle, hover states |
| Standard | 200ms | Dropdown open, tooltip appear, tab switch |
| Emphasis | 300ms | Modal open/close, slide-over, page transition |
| Slow | 400-500ms | Skeleton shimmer cycle, drag-and-drop settle |
What You Leave With
| Document | Size | Key Output |
|---|---|---|
| Brand Guide | 3-5 pages | Wordmark, color tokens, typography scale, usage rules |
tokens.css | 100-200 lines | CSS custom properties imported by all components |
| UI Component Plan | 5-8 pages | Component inventory, library decisions, build waves with dependencies |
| UX Coherence Guidelines | 5-8 pages | Loading 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.