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, and a rule that says you compose — you do not invent.
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 | Pages import primitives. Pages do not define components. |
A page that defines a component is a page that 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 | 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, gray-500, slate-700, stone-50. Never: dark: variants — the site is light-mode only.
Primitive Catalogue
| Domain | Folder | What lives here |
|---|---|---|
| UI primitives | ui/ | Badge, SectionLabel, Callout, TwoColumnBox, Pill. No domain knowledge. Compose into everything above. |
| Layout | design-system/ | Section, Container, SectionHeader, Button. Every page starts here. |
| Content | content/ | StoryCard, EvalTarget, TriggerLegend. Composable value-story shapes. |
| Data display | data-display/ | StatCard, CheckItem, StageHeader, ScoreBar, PrincipleRow. Metric and list shapes. |
| Predictions | predictions/ | ConvictionBar. Five-block filled bar with optional probability readout. |
| 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. |
| 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 barrel export. No exceptions.
| 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
| # | Rule | Why |
|---|---|---|
| 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/. | Dependency direction: pages depend on primitives, not the reverse. |
| 3 | Higher domains (venture/, prd/) may import from lower (design-system/, content/). | Layering. |
| 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 a new prop. | <StoryCard>{extras}</StoryCard> beats <StoryCard extras={...}/>. |
| 6 | Imports use @site/src/components/..., never ../../../../../components/.... | Move-proof. Readable. |
The Test
Before writing a new component in a page file, run three questions:
- Does a primitive already cover this? — grep
src/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.
Anti-Patterns
- Inline
TRIGGER_STYLESorDIMENSION_LABELSobjects in a page — these are component data, move them beside the component. - Deep relative imports (
../../../../../) — use@site/src/components/.... - Copying a component from one page to another — the second copy is the signal that it belongs in
src/components/. - New primitives without a barrel export — future authors will not find them.
- Arbitrary Tailwind values (
text-[#b91c1c],p-[7px]) — tokens exist for a reason.
Context
- Naming Standards — folders, files, components, tags
- Component-Driven Development — CDD workflow
- Rendering Verification — tokens must resolve
- Component Design — API surface principles
Questions
Which primitive in src/components/ has the widest prop surface — and is that surface proof of flexibility or a symptom of doing too much?
- If a new author wanted to add a sixth variant to
StoryCard, would they extract a new primitive or extend the prop surface? - Which pages in
src/pages/still define their own components, and which of those components are the next candidates for extraction? - When a primitive spans two domains (used by both
venture/andprd/), where should it live? - How do you prove a token change (e.g. new
--color-status-*) has not broken an existing composition?