Nine rules, each already followed by two independently built sites before it was written down here. A rule that has survived one build is a preference; these survived two, with different palettes and different faces.
Rule 01
Two faces, and the mono is the constant
One monospace sets every label, number, address, breadcrumb, button and footer line — anything that tells you what kind of thing you are looking at rather than saying something. A second face carries the language, and it is the site’s own. If a piece of text tells you what kind of thing you are looking at, it is mono, uppercase and tracked. That single test decides every case.
--mono is shared across sites · --language is not · 0.68–0.75rem · 0.08–0.16em tracking
Rule 02
No cards
No radius, no fill, no shadow to say “separate object”. Objects are separated by a hairline and by space. The only fill in the system is the row hover at about 1.07:1 — felt rather than seen. If you can identify its colour, it is too strong. The single exception is a code field, which earns its border by genuinely being a different surface.
border-radius: 0 everywhere · one fill, --sunk · zero box-shadow
Rule 03
Two weights of rule, and they mean different things
A 1px hairline separates peers. A 2px ink rule opens and closes the document: under the masthead, above the first block, above the footer. Those three lines are what hold a page together at a glance. Do not add a third weight — the moment there are three, none of them mean anything.
1px var(--rule) between peers · 2px var(--ink) at the document edges
Rule 04
The numbered rail
A sticky left column carrying a number and a mono section name, content on the right. It stays beside its content for as long as that content is on screen, and collapses to a line above the content on narrow viewports. align-self: start is required, or there is nothing to stick within — a grid item stretches to the row height by default, and a stretched item cannot be sticky. Remove that one line and the rail silently stops moving, with no error anywhere.
grid: var(--rail) minmax(0, 1fr) · position: sticky · align-self: start
Rule 05
One row anatomy, everywhere
Mono label left, title in the display face, mark right, then an optional description and a mono line carrying the destination. The whole row is the link and the mark is decorative. On hover the hairline redraws in ink from the left and the mark nudges up and right. Use ↗ for a destination that leaves the site and → for one that does not, so the mark keeps meaning something.
rules bleed 0.75rem past the text so the hover fill has somewhere to sit
Rule 06
Tokens declared three times
Bare :root is the light palette and the fallback. The media query is guarded with :not([data-theme="light"]) so an explicit choice beats the system preference. [data-theme="dark"] restates it so the toggle wins in both directions. Never style a component from inside one of those blocks — a rule written there exists in one theme and not the other, and you will not notice for weeks.
:root · @media (prefers-color-scheme: dark) · :root[data-theme="dark"]
Rule 07
Contrast is measured, and the measurement is written down
Every text token carries its ratio, measured against the worst-case surface rather than the flat background — the grain overlay sits between the text and the page and moves the surface toward the text in both themes. Ink is never pure black on pure white; at 21:1 that pairing glares on long text. In this repo the measuring is done by the build, and a token below AA fails it.
WCAG AA 4.5:1 for text · hairlines and fills exempt · measured, not estimated
Rule 08
A bordered control, not another word in a row of words
The theme toggle is a 2rem square with a hairline border, because it does something where the links beside it only go somewhere. It ships with the hidden attribute set and is revealed by script, so a visitor without JavaScript is never offered a button that cannot work.
2rem square · 1px border · hidden until bootstrapped
Rule 09
It prints
Ink on white, controls gone, every panel open, break-inside: avoid on structural blocks, and link destinations expanded after the link text. A printed page has no hover and no address bar, so a bare “read more” prints as a dead end. Print is a real target here, not an afterthought — try it on this page.
@media print · --grain: 0 · href expanded on content links only