Skip to main content

Design Language

How does a page stay consistent when twenty authors edit it over two years?

Consistency is not taste. It is a short list of tokens, a short list of primitives, a hierarchy with size gates, and a rule that says you compose — you do not invent.

For numbers and thresholds, see the Hard Thresholds Table. This page teaches the why behind the numbers.

The Three Layers

LayerLives inRule
Tokenssrc/css/custom.cssSemantic names only. Never hex in a component.
Primitivessrc/components/PascalCase React files. Pure renderers. No page knowledge.
Compositionsrc/pages/*.{jsx,tsx}Pages import primitives. Pages do not define components.

A page that defines a component has invented a primitive. The primitive belongs in src/components/.

Tokens

Source of truth: src/css/custom.css. Read src/components/design-system/CLAUDE.md for the full table.

Token familyPurposeExamples
InkText and dark backgroundstext-ink, bg-ink, text-ink-muted, text-ink-subtle
ChalkText on darktext-chalk, text-chalk-muted, text-chalk-subtle
SurfaceCard and section backgroundsbg-surface, bg-surface-alt, bg-surface-muted, bg-surface-accent
EdgeBordersborder-edge
BrandCrimson accent (alarm role only — see no-dark-mode amendment)text-brand, bg-brand, text-brand-on-dark
StatusSemantic feedbacktext-status-success, bg-status-danger-muted, text-status-warning
ShadowElevationshadow-card, shadow-card-hover

Never: raw hex, arbitrary Tailwind values (text-[#b91c1c], p-[7px]), gray-500/slate-700/stone-50, dark: variants (see amended rule below).

No Dark Mode — Component Themes Amended

Browser dark-mode toggles are not supported. The site is light-mode-only.

Amendment (2026-05-17): Deliberate dark themes per-component are permitted when ALL of these hold:

  1. WCAG AA contrast is verified at the actual hex values (every text-on-bg pair)
  2. All six computed contrast pairs are cached in the component's own design.md (recipe + numbers)
  3. Brand red remains alarm-only (never primary chrome, never positive states)
  4. The component ships a runtime gate (e.g. scoreGenericTest() per src/components/prompt-deck/design-dna.tsx) that blocks ship if the dark theme drifts off-spec

Without all four, the rule reverts to its strict form: no dark surfaces. The amendment exists because some psychology instruments (the prompt deck) earn the dark canvas through verified contrast; it does NOT license dark mode as a default.

Tailwind: Mechanism Per Context

ContextMechanism
This Docusaurus repoTailwind v3.4.17. tailwind.config.js extends theme.colors; tokens defined in :root of src/css/custom.css and referenced via var(--color-*). Locked to v3 because Docusaurus webpack runs @import before PostCSS — v4's @theme directive fails in this pipeline. Detailed in src/css/CLAUDE.md.
Greenfield apps (Vite, Next.js)Tailwind v4 — CSS-first config via @theme / @theme inline directives. See Tailwind v4 Theme.

Both forms apply the same principle: CSS variables are SSOT; Tailwind utilities reference them via var(). Mechanism differs; principle does not.

Component Hierarchy

Components nest in predictable layers. Each layer has a size gate — cross it and you need to decompose. Numbers per Hard Thresholds Table rows 12–15.

LevelDefinitionExampleReuse scopeSize gate
AtomSingle-purpose element, no childrenButton, Input, BadgeEntire platform< 30 lines, < 3 props
Molecule2–4 atoms with one jobSearch field (input + button)Entire platform< 80 lines, < 7 props
OrganismMolecules + atoms forming a distinct sectionNavigation bar, Data tableFeature or product< 150 lines, < 10 props
TemplatePage layout with placeholder slotsDashboard layout, Settings shellProductDefines slots, no data
PageTemplate filled with real data/settings/teamSingle routeComposes organisms into template

The hierarchy is a smell test. If a molecule has 12 props, it is an organism pretending to be simple. Decompose.

The 30-second isolation test

A component passes when: a stranger picks the file out of context, reads it for 30 seconds, and can answer (a) what it does, (b) how to render it in isolation, and (c) what each prop means. If they cannot, the component has hidden dependencies. Find them and extract before integrating.

Boundary System

Not all components belong in the same library. The boundary determines dependency rules — and dependency rules determine how fast a new venture gets to market.

BoundaryLivesDependency ruleExample
Generic interactiveButtons, modals, inputs, tabsZero domain importsButton, Dialog, Tabs
Generic formsFields, validation wrappers, form layoutsMay import generic interactiveFormField, DatePicker
Generic marketingHeroes, feature grids, testimonials, CTAsMay import generic interactiveHeroSection, PricingCard
Domain-specificBusiness logic componentsMay import any genericDealPipeline, AgentCard
SpecialisedMaps, 3D, blockchain, rich editorsOwn dependency tree, isolatedMapView, WalletConnect

The decision: can another product use this component unchanged? Yes → generic. No → domain-specific. Generic boundaries never import from domain or specialised. Domain may import generic. Specialised imports nothing from the product. When a new venture plugs in, it gets every generic boundary for free.

Maturation Pipeline

Components mature through three consumption modes:

ModeStageWhenWhat you get
CompareTestingExploring approaches, not committedSide-by-side prototypes in the proving ground
CopyOperationalCustomising heavily for a specific productSource files in your project, diverging from shared
ImportOptimisedProven, stable, used in 3+ placesShared library, single source of truth

COMPARE graduates to IMPORT when one approach wins and stabilises across 3 uses. IMPORT graduates to COPY only when you need to diverge permanently from the shared version — that divergence should be rare if boundaries are right.

Proving Ground

A design system application — separate from any product — where every component is tested in isolation before any venture consumes it. The proving ground:

  • Renders every component in every state: default, loading, error, empty, disabled
  • Organises components by boundary type with a live dependency graph
  • Documents props with interactive examples, not static docs
  • Catches visual regressions through snapshot comparison
  • Proves accessibility before a component reaches a page

If a component cannot render in the proving ground, it has a hidden dependency. Find it and extract it before integrating.

The proving ground is also the onboarding tool. A new developer browses it to learn what exists before building something new. A new venture checks it to see what is already solved.

Reference implementations in this repo:

Browser Canvas Storytelling

Markdown is a reading format. MDX is a thinking surface. Climb to MDX/React when the page needs the reader to see a relationship that prose would force them to remember.

Trigger conditions:

Content shapeUse this visual modelWhy
A sequence or workflowTimeline, pipeline, loop, or numbered card railThe order is the argument
A choice between modesTwo-column contrast, tabs, or decision matrixThe difference is the argument
A system with inputs and outputsLoop, ledger, or cause/effect gridThe relationship is the argument
A list of five or fewer principlesTightFive or five-card gridThe ceiling helps memory
A table with three or more columnsDataListTableMobile readers need cards first, table second

Teaching-page arc:

Problem tension → Operating principle → Visual model → Application → Proof/loop → Next action

This is not a landing-page funnel. /docs/ teaches universal know-how, so the page earns action by improving the reader's model of reality. The action may be a copied prompt, a diagnostic question, or a routed link to the next concept.

Construction rules:

  1. Start with the reader question. If the question is vague, the visual will be vague.
  2. Choose one spatial grammar: sequence, comparison, matrix, loop, or ledger. Do not mix three grammars in one viewport.
  3. Compose from primitives first: Section, Container, SectionHeader, Card, Rhythm, TightFive, DataListTable, Stat.
  4. Keep long-form prose in a readable column. Let visual blocks expand to lg or xl only when the extra width carries meaning.
  5. Close the page with one next action. A strong model that does not change behaviour is unfinished work.

Hard boundary: /docs/ pages remain .mdx. Do not move docs content to .tsx to get richer layout. Use JSX inside MDX, and extract reusable patterns only after a second use.

Composition Score

The ratio of library imports to raw HTML on a page is the metric that proves the platform works for UI.

Composition ratioWhat it means
> 80% library importsThe proving ground carried the UI — new venture launched at speed
60–80%Gaps in the component library — some custom work needed
< 50%The proving ground has holes — components being rebuilt instead of reused

High composition = the mycelium carried the work. Low composition = the venture had to grow its own roots.

Health Signals

SignalHealthyUnhealthy
Props per component≤ 7> 10 (god component)
File length< 150 lines> 300 lines (needs decomposition)
Nesting depth≤ 3 levels> 4 levels (flatten or extract)
Prop drilling depth≤ 2 levels> 3 levels (use context or composition)
Children vs props for contentChildren for layoutProps for everything (inflexible)

Anti-patterns to catch early

  • God components — one file renders an entire page section with 15+ props
  • Barrel blowouts — re-exporting everything from index.ts kills tree-shaking; export only what consumers need
  • Prop drilling — passing data through 3+ levels instead of composing with children or context
  • Premature abstraction — creating a shared component before the third use
  • Inline tokensTRIGGER_STYLES or DIMENSION_LABELS objects defined in a page file; move beside the component
  • Deep relative imports (../../../../../) — use @site/src/components/...
  • Copying a component from one page to another — the second copy is the signal it belongs in src/components/
  • Arbitrary Tailwind values (text-[#b91c1c], p-[7px]) — tokens exist for a reason
  • Hardcoded hex in a component — every color reads from :root or a Tailwind token utility

Primitive Catalogue

DomainFolderWhat lives here
UI primitivesui/Badge, SectionLabel, Callout, TwoColumnBox, Pill. No domain knowledge.
Layoutdesign-system/Section, Container, SectionHeader, Button, Card, Stat, TightFive, Prose, Rhythm, Eyebrow, InlineLink, CopyablePrompt, DataListTable. Every page starts here.
Contentcontent/StoryCard, EvalTarget, TriggerLegend. Composable value-story shapes.
Data displaydata-display/StatCard, CheckItem, StageHeader, ScoreBar, PrincipleRow.
Predictionspredictions/ConvictionBar.
PRDprd/PRDHero, PRDNavigation, PRDScoreCard, PRDStatusBadge, SIOBlock, BuildOrderTable, PRDRelationships, VVStoriesStrip.
Ventureventure/Cards, matrices, unit-economics, scorecards, competitor tables.
Landing pagelanding-page/Homepage sections.
Feature matrixfeature-matrix/Feature table, category summary, filter bar, health bar.
Prompt deckprompt-deck/PromptDeck, Slide, SlideDepth, PromptDeckShowcase, DesignDnaPreview, scoreGenericTest.
Imagesimage-components/<img> wrappers. Never raw markdown image syntax.

Naming

Every component is PascalCase in a kebab-case folder. Every folder has an index.{js,ts} barrel export.

BadGood
storyCard.jsxStoryCard.jsx
src/components/StoryCard/src/components/content/StoryCard.jsx
src/pages/.../vv-stories/index.jsx defining StoryImport StoryCard from @site/src/components/content

Full rules: docs/standards/naming-standards.md.

Composition Rules

  1. Pages never define function components for patterns used twice. Second use is a primitive. Third use is tech debt.
  2. Primitives never import from src/pages/. Pages depend on primitives, not the reverse.
  3. Higher domains (venture/, prd/) may import from lower (design-system/, content/).
  4. Primitives expose the smallest prop surface that covers the use case. API decay begins the day you add a prop.
  5. Children are the extension slot. Use them before adding a new prop. <StoryCard>{extras}</StoryCard> beats <StoryCard extras={...}/>.
  6. Imports use @site/src/components/..., never ../../../../../components/....

The Decision Tree

New component needed
→ Does it exist in the proving ground?
YES → Import it
NO → Will multiple products use it?
YES → Build in generic boundary, add to proving ground
NO → Will multiple features use it?
YES → Build in domain boundary
NO → Co-locate with the feature

Co-located components graduate to domain boundary after the third feature uses them. Domain components graduate to generic boundary after the third product uses them. Every graduation adds to the proving ground — the library grows through use, not planning.

F-Pattern and Z-Pattern Reading

How readers scan a page when the content is unfamiliar:

  • F-pattern — long-form content (articles, search results, lists). Eye sweeps horizontally across the top, drops down the left edge, sweeps horizontally again at a lower row. Implication: most-important content goes top-left; scannable subheads earn the second sweep; bullet lists earn the vertical drop.
  • Z-pattern — sparse landing pages with hero + 3–4 elements. Eye sweeps top-left → top-right, diagonals to bottom-left, sweeps to bottom-right. Implication: hero CTA top-right; secondary trust signal top-left; final CTA bottom-right.

Pick the pattern per page type. Mixing patterns within one page produces visual confusion.

The Test

Before writing a new component in a page file:

  1. Does a primitive already cover this? grep src/components/ first.
  2. Will this pattern appear on another page? If yes, extract on the first write, not the second.
  3. Is the primitive the right layer? design-system/ for layout, content/ for content shapes, domain folders for domain shapes.

If all three answers are clean AND the component is still page-specific, keep it inline. Otherwise extract.

Context