What everything is made of.
7 parts that every other part is built from. None of them is optional and none of them takes a variant.
One ink, one ramp.
- LIVES IN
- src/index.css (@theme)
- USED ON
- Everywhere
- PROPS
- canvas · paper · ink ramp (ink, ink-soft, ink-mid, ink-muted, ink-faint) · accent (oxblood) · tints · rules
ink-muted is the lightest text token; ink-faint is decorative only. The Colophon palette list mirrors these; update both.
As it renders.
- canvas
- paper
- ink
- ink-soft
- ink-mid
- ink-muted
- ink-faint
- accent
- accent-tint
- accent-dark
How it is called.
/* src/index.css, @theme */
--color-canvas: #fdfcfa; --color-paper: #ffffff;
--color-ink: #16110f; --color-ink-soft: #3d3431;
--color-ink-mid: #5a504b; --color-ink-muted: #706560; --color-ink-faint: #c8bdb7;
--color-accent: #96222f; --color-accent-tint: #f6dfe1; --color-accent-dark: #ea8a97;
/* Utilities follow the tokens */
<p className="text-ink-soft">Running text</p>
<span className="text-accent">Spot ink</span>Four faces, fixed jobs.
- LIVES IN
- src/index.css (@theme)
- USED ON
- Everywhere
- PROPS
- Playfair Display (bold headings, index titles) · Instrument Serif (display italic) · Source Serif 4 (running text) · JetBrains Mono (eyebrows, data). Running text is set by two parts in Editorial.jsx: Paragraphs (16px/1.7, optional dropCap) and Body (15px/1.65)
Body text is never Playfair. Running text goes through Paragraphs or Body, never a hand-set <p>. Display italic is the voice of titles and pull quotes.
As it renders.
Source Serif 4: running text, captions, and spec values. It is the only face body text is ever set in.
Running text goes through Paragraphs: Source Serif 4 at sixteen pixels, a 1.7 line, a 680px measure, and a drop cap when a chapter section opens with it.
Body is the caption weight: the same face one step down, for ledger rows, spec notes, and the text beside a figure.
How it is called.
<h1 className="display-italic">Instrument Serif: display italic</h1>
<h2 style={{ fontFamily: 'var(--font-sans)', fontWeight: 700 }}>Playfair: bold headings</h2>
<span className="label-mono">JETBRAINS MONO · EYEBROWS, DATA</span>
{/* running text: two parts, never a hand-set <p> */}
import { Paragraphs, Body } from '../components/Editorial.jsx'
<Paragraphs dropCap content={[`First paragraph…`, `Second…`]} /> {/* 16px / 1.7 */}
<Body text="A caption-weight paragraph." /> {/* 15px / 1.65 */}Two sizes, tracked.
- LIVES IN
- src/index.css (.label-mono, .label-mono-lg)
- USED ON
- Every strip, row label, and opener
- PROPS
- label-mono (10px) for rows and metadata · label-mono-lg (12px) for section and page openers
Two sizes only. Uppercase, tracked. Never a third size.
As it renders.
How it is called.
<span className="label-mono text-ink-muted">ROW LABEL · 10PX</span>
<span className="label-mono-lg text-ink-soft">SECTION OPENER · 12PX</span>
<span className="label-mono-lg text-accent">¶ 03</span>The only divider.
- LIVES IN
- src/index.css (.rule, .rule-soft, .rule-draw)
- USED ON
- Openers, ledgers, footers
- PROPS
- rule (strong) · rule-soft · rule-draw (extends in on reveal) · rule-draw-center
Hairlines are the only dividers. No boxes around content that a hairline can separate.
As it renders.
How it is called.
<div className="rule" /> {/* strong */}
<div className="rule-soft" /> {/* soft */}
<div data-reveal>
<div className="border-b border-rule-strong rule-draw" /> {/* extends in */}
</div>Where the page changes.
- LIVES IN
- src/index.css ([data-ground])
- USED ON
- Numeral Plate, Pull Quote, Note Band, Plate
- PROPS
- canvas (page) · paper (Plate) · accent (data-ground="accent") · dark (data-ground="dark")
A page changes ground at least once. One solid block per screen at most. canvas-deep is a hover tint, never a ground.
As it renders.
How it is called.
<div className="bg-canvas">canvas: the page</div>
<Plate>paper: a figure</Plate>
<div data-ground="accent">accent: one solid block per screen</div>
<div data-ground="dark" style={{ background: '#16110f' }}>dark: the note band</div>One personality, three curves.
- LIVES IN
- src/index.css, src/components/Layout.jsx (useReveal)
- USED ON
- Everywhere
- PROPS
- data-reveal (+ --i stagger) · mark-grow · mark-pop · mark-draw · mark-fade · press · lift · link-draw
Three curves only: spring-snappy, spring-smooth, ease-snap. No keyword easings. Reduced motion collapses to a fade.
As it renders.
How it is called.
{/* settle in on a spring when scrolled into view; --i staggers siblings */}
<section data-reveal style={{ '--i': 1 }}>…</section>
{/* inside a revealed block */}
<span className="mark-grow" /> {/* bars grow */}
<span className="mark-pop" style={{ '--k': 2 }} /> {/* squares pop, staggered */}
<line className="mark-draw" pathLength="1" /> {/* lines draw */}
<a className="link-draw">underline draws in</a>
<button className="press">compresses to 0.98</button>
<div className="group"><img className="lift" /></div> {/* lifts and tilts */}Arrows are components.
- LIVES IN
- src/components/Icons.jsx
- USED ON
- Arrows and chevrons everywhere
- PROPS
- ArrowRight · ArrowUpRight · ChevronLeft · ChevronRight · ChevronDown · CloseIcon · CDMonogram
Arrows are always these components in UI, never a typed → or ↗.
As it renders.
How it is called.
import { ArrowRight, ArrowUpRight, ChevronLeft } from '../components/Icons.jsx'
<Link className="group inline-flex items-center gap-2">
Read the case study
<ArrowRight className="w-4 h-4 group-hover:translate-x-1 transition-transform" />
</Link>