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, and a rule that says you compose — you do not invent.

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/*.jsxPages 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 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 accenttext-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, gray-500, slate-700, stone-50. Never: dark: variants — the site is light-mode only.

Primitive Catalogue

DomainFolderWhat lives here
UI primitivesui/Badge, SectionLabel, Callout, TwoColumnBox, Pill. No domain knowledge. Compose into everything above.
Layoutdesign-system/Section, Container, SectionHeader, Button. Every page starts here.
Contentcontent/StoryCard, EvalTarget, TriggerLegend. Composable value-story shapes.
Data displaydata-display/StatCard, CheckItem, StageHeader, ScoreBar, PrincipleRow. Metric and list shapes.
Predictionspredictions/ConvictionBar. Five-block filled bar with optional probability readout.
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.
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 barrel export. No exceptions.

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

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

The Test

Before writing a new component in a page file, run three questions:

  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.

Anti-Patterns

  • Inline TRIGGER_STYLES or DIMENSION_LABELS objects 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

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/ and prd/), where should it live?
  • How do you prove a token change (e.g. new --color-status-*) has not broken an existing composition?