Design system

Type & color

The two ramps everything else is built from. Every value on this page is read from tokens.css or derived from it, so what you see is what ships.

Inter

Self-hosted under the SIL Open Font License, so every visitor sees the same letterforms on every platform. Chosen over the two obvious alternatives partly on licensing: Helvetica Neue is commercially licensed, and SF Pro cannot legally be embedded on a website — Apple licenses it only for mocking up Apple platform UI. Inter is the closest freely embeddable analog: screen-first, tall x-height, and apertures that stay open at 13px where Helvetica's close up.

This is the variable build carrying Inter v4's optical size axis, so letterforms genuinely change shape with size the way SF's do, rather than being scaled uniformly. One file covers the whole 100–900 range, which makes 600 a real cut.

"Inter Variable", Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif

AaLight 300
AaRegular 400
AaMedium 500
AaSemibold 600
AaBold 700
AaExtrabold 800

Three families, one ramp

The mono and serif are not separate ramps — they are the same three steps spoken in another voice. Put .font-mono or .font-serif on any element and it and everything inside it switches family, weight and tracking together, while every call site goes on saying font: var(--text-body).

The pairings were chosen by measurement rather than taste, because a shared ramp only holds if one step reads as one size in every family — and x-height governs perceived size far more than the nominal em does. Both companions sit within 3% of Inter's x-height, so no per-family size correction is needed. JetBrains Mono lost on exactly this point despite matching cap height almost perfectly: it runs 6.7% large in the x-height.

FamilyRolex-heightCapWeights
InterSans — default0.51560.7275100–900
Roboto MonoMono0.5283+2.5%0.7109-2.3%100–700
LiterataSerif0.5070-1.7%0.7010-3.6%200–900

The same three steps in each voice — identical size and leading, only the family and its tracking differ:

Inter

Objects in a room

The quick brown fox jumps over the lazy dog, and keeps going until the line wraps.

Roboto Mono

Objects in a room

The quick brown fox jumps over the lazy dog, and keeps going until the line wraps.

Literata

Objects in a room

The quick brown fox jumps over the lazy dog, and keeps going until the line wraps.

Neither switch overrides weight. Roboto Mono's axis stops at 700, which used to need handling because the display steps were Extrabold and the browser would have clamped them silently. With the ramp topping out at Semibold that ceiling is nowhere near tested, so the special case was removed rather than kept against a future that may not come.

Type ramp

Three steps, and the small number is the design rather than an omission.This site has exactly one text size. --text-body sets every word on it, and hierarchy comes from weight, family, fills, rules and whitespace instead. Size is not a variable this design reaches for. Body is 10px on purpose — well below the 16px most accessibility guidance assumes, and a deliberate stylistic choice here rather than an oversight.

Three rounds of trimming got here. A ten-step ramp first lost h1–h4, lead and sm, seven of its ten steps having had no consumer outside this page documenting them. Then body dropped from 12px to 10px, which made the 10px caption step an exact duplicate of it. Then the 9px tracked-uppercase label step went too, and the caps tracking that existed only to serve it. Each time the same rule held: nothing stays in the ramp describing a distinction the site does not make.

The two fluid steps interpolate between a 375px and a 1440px viewport — resize the window and they move. Neither is spent on the site proper: the masthead above and the section heads on this page are the only things using them, and the portfolio itself is body throughout. They are kept because a large size invented later would sit outside the system rather than in it, and because the gap between heading and body is deliberately a cliff: there is no gentle ladder down, since nothing on this site needs one.

--text-display40 → 72px · Semibold 600 · 0.9 · -0.032em

One large moment per page. Currently unused.

House of Roe

--text-heading20 → 32px · Semibold 600 · 1 · -0.024em

A section head, where a page needs one.

Objects in a room

--text-body10px · Medium 500 · 1.38 · 0.008em

Every piece of text on the site, without exception.

The quick brown fox jumps over the lazy dog, and keeps going until the line wraps.

Gray ramp

Thirteen steps, pure black to pure white, spaced by even increments of CIELAB L* rather than even hex values. An even hex ramp is perceptually wrong — sRGB is gamma-encoded, so the dark end bunches into near-identical swatches while the light end spreads out. Stepping L* keeps the visual gap between neighbours constant the whole way up, which is what makes contrast pairing arithmetic: 7 steps apart clears 4.5:1 and 5 steps apart clears 3:1.

TokenHexL*On whiteOn black
--gray-1200#0000000.021.001.00
--gray-1100#1818188.217.761.18
--gray-1000#29292916.614.551.44
--gray-900#3b3b3b24.911.201.87
--gray-800#4e4e4e33.28.322.52
--gray-700#62626241.66.103.44
--gray-600#77777750.04.484.69
--gray-500#8c8c8c58.33.366.25
--gray-400#a2a2a266.62.558.23
--gray-300#b9b9b975.11.9610.70
--gray-200#d0d0d083.51.5413.62
--gray-100#e7e7e791.61.2416.98
--gray-0#ffffff100.01.0021.00

Full-contrast ratios clear 4.5:1 for body text. --gray-600 lands at 4.48 on white — it misses by a hair, so --gray-700 is the lightest step safe on white, and --gray-500 the darkest safe on black.