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
| Layer | Lives in | Rule |
|---|---|---|
| Tokens | src/css/custom.css | Semantic names only. Never hex in a component. |
| Primitives | src/components/ | PascalCase React files. Pure renderers. No page knowledge. |
| Composition | src/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 family | Purpose | Examples |
|---|---|---|
| Ink | Text and dark backgrounds | text-ink, bg-ink, text-ink-muted, text-ink-subtle |
| Chalk | Text on dark | text-chalk, text-chalk-muted, text-chalk-subtle |
| Surface | Card and section backgrounds | bg-surface, bg-surface-alt, bg-surface-muted, bg-surface-accent |
| Edge | Borders | border-edge |
| Brand | Crimson accent (alarm role only — see no-dark-mode amendment) | text-brand, bg-brand, text-brand-on-dark |
| Status | Semantic feedback | text-status-success, bg-status-danger-muted, text-status-warning |
| Shadow | Elevation | shadow-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:
- WCAG AA contrast is verified at the actual hex values (every text-on-bg pair)
- All six computed contrast pairs are cached in the component's own
design.md(recipe + numbers) - Brand red remains alarm-only (never primary chrome, never positive states)
- The component ships a runtime gate (e.g.
scoreGenericTest()persrc/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
| Context | Mechanism |
|---|---|
| This Docusaurus repo | Tailwind 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.
| Level | Definition | Example | Reuse scope | Size gate |
|---|---|---|---|---|
| Atom | Single-purpose element, no children | Button, Input, Badge | Entire platform | < 30 lines, < 3 props |
| Molecule | 2–4 atoms with one job | Search field (input + button) | Entire platform | < 80 lines, < 7 props |
| Organism | Molecules + atoms forming a distinct section | Navigation bar, Data table | Feature or product | < 150 lines, < 10 props |
| Template | Page layout with placeholder slots | Dashboard layout, Settings shell | Product | Defines slots, no data |
| Page | Template filled with real data | /settings/team | Single route | Composes 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.
| Boundary | Lives | Dependency rule | Example |
|---|---|---|---|
| Generic interactive | Buttons, modals, inputs, tabs | Zero domain imports | Button, Dialog, Tabs |
| Generic forms | Fields, validation wrappers, form layouts | May import generic interactive | FormField, DatePicker |
| Generic marketing | Heroes, feature grids, testimonials, CTAs | May import generic interactive | HeroSection, PricingCard |
| Domain-specific | Business logic components | May import any generic | DealPipeline, AgentCard |
| Specialised | Maps, 3D, blockchain, rich editors | Own dependency tree, isolated | MapView, 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:
| Mode | Stage | When | What you get |
|---|---|---|---|
| Compare | Testing | Exploring approaches, not committed | Side-by-side prototypes in the proving ground |
| Copy | Operational | Customising heavily for a specific product | Source files in your project, diverging from shared |
| Import | Optimised | Proven, stable, used in 3+ places | Shared 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:
src/pages/style/prompt-deck.tsx— first proving-ground route, renders the prompt-deck design DNA + every state per part- Live external: stackmates-design-system.vercel.app
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 shape | Use this visual model | Why |
|---|---|---|
| A sequence or workflow | Timeline, pipeline, loop, or numbered card rail | The order is the argument |
| A choice between modes | Two-column contrast, tabs, or decision matrix | The difference is the argument |
| A system with inputs and outputs | Loop, ledger, or cause/effect grid | The relationship is the argument |
| A list of five or fewer principles | TightFive or five-card grid | The ceiling helps memory |
| A table with three or more columns | DataListTable | Mobile 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:
- Start with the reader question. If the question is vague, the visual will be vague.
- Choose one spatial grammar: sequence, comparison, matrix, loop, or ledger. Do not mix three grammars in one viewport.
- Compose from primitives first:
Section,Container,SectionHeader,Card,Rhythm,TightFive,DataListTable,Stat. - Keep long-form prose in a readable column. Let visual blocks expand to
lgorxlonly when the extra width carries meaning. - 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 ratio | What it means |
|---|---|
| > 80% library imports | The 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
| Signal | Healthy | Unhealthy |
|---|---|---|
| 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 content | Children for layout | Props 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.tskills 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 tokens —
TRIGGER_STYLESorDIMENSION_LABELSobjects 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
:rootor a Tailwind token utility
Primitive Catalogue
| Domain | Folder | What lives here |
|---|---|---|
| UI primitives | ui/ | Badge, SectionLabel, Callout, TwoColumnBox, Pill. No domain knowledge. |
| Layout | design-system/ | Section, Container, SectionHeader, Button, Card, Stat, TightFive, Prose, Rhythm, Eyebrow, InlineLink, CopyablePrompt, DataListTable. Every page starts here. |
| Content | content/ | StoryCard, EvalTarget, TriggerLegend. Composable value-story shapes. |
| Data display | data-display/ | StatCard, CheckItem, StageHeader, ScoreBar, PrincipleRow. |
| Predictions | predictions/ | ConvictionBar. |
| PRD | prd/ | PRDHero, PRDNavigation, PRDScoreCard, PRDStatusBadge, SIOBlock, BuildOrderTable, PRDRelationships, VVStoriesStrip. |
| Venture | venture/ | Cards, matrices, unit-economics, scorecards, competitor tables. |
| Landing page | landing-page/ | Homepage sections. |
| Feature matrix | feature-matrix/ | Feature table, category summary, filter bar, health bar. |
| Prompt deck | prompt-deck/ | PromptDeck, Slide, SlideDepth, PromptDeckShowcase, DesignDnaPreview, scoreGenericTest. |
| Images | image-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.
| Bad | Good |
|---|---|
storyCard.jsx | StoryCard.jsx |
src/components/StoryCard/ | src/components/content/StoryCard.jsx |
src/pages/.../vv-stories/index.jsx defining Story | Import StoryCard from @site/src/components/content |
Full rules: docs/standards/naming-standards.md.
Composition Rules
- Pages never define function components for patterns used twice. Second use is a primitive. Third use is tech debt.
- Primitives never import from
src/pages/. Pages depend on primitives, not the reverse. - Higher domains (
venture/,prd/) may import from lower (design-system/,content/). - Primitives expose the smallest prop surface that covers the use case. API decay begins the day you add a prop.
- Children are the extension slot. Use them before adding a new prop.
<StoryCard>{extras}</StoryCard>beats<StoryCard extras={...}/>. - 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:
- Does a primitive already cover this?
grepsrc/components/first. - Will this pattern appear on another page? If yes, extract on the first write, not the second.
- 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
- Naming Standards — folders, files, components, tags
- Audit Loop — how to verify tokens resolve, components render, pages pass
- Hexagonal Architecture — boundary thinking applied to the full stack
- Maps — CDD applied to specialised map components
Links
- Atomic Design (Brad Frost) — the original hierarchy framework
- componentdriven.org — the methodology and community
- Storybook — the most common proving ground implementation