Typography is a derivation, not a series of choices
Most typography feels hard because it is being done as forty independent decisions. It is closer to three decisions and a set of consequences. This manual is organised around what derives from what.
The one rule that reduces the others
Set your body size, your largest size, and your number of levels. Everything else — the ratio, every intermediate size, line heights, container widths, the gaps between elements — falls out of those three numbers plus a small number of relationships. When a project feels like it is starting from zero, it is because those three numbers were never fixed.
The seven dials
Personality in type does not come from the typeface. It comes from where these seven sit. Change the typeface and hold the dials, you get a variant. Hold the typeface and change the dials, you get a different design.
- Ratio — how far apart the levels sit
- Hierarchy axis — whether size, weight, colour, case, space or family carries the structure
- Leading — line height relative to size
- Measure — the width of the reading line
- Tracking — letter spacing, mainly at the extremes
- Density — the ratio of ink to whitespace
- Case and alignment
Every rule here is a default you break knowingly, not a law. The value of stating them as numbers is that a number can be argued with. A feeling cannot.
Text roles
A text role is a distinct job a piece of text does for the reader, named by that job. Not by its size, not by where it appears. "Caption", "input label", "section heading".
A role is a locked bundle of seven properties: size, weight, line height, tracking, colour, maximum measure, and the space below it. If you have only assigned a size, you have not yet made a role. The bundle is the unit you style once and reuse everywhere.
The full inventory
Around forty roles exist across all of interface and editorial work. A working system needs ten to fourteen. The collapsing is the design work.
Structural
- Display / hero
- Page title
- Section heading
- Subsection heading
- Eyebrow / kicker
- Deck / standfirst
- Nav item
- Tab label
- Breadcrumb
- Contents entry
Reading
- Body
- Lead paragraph
- List item
- Blockquote
- Pull quote
- Term & definition
- Caption
- Credit / byline
- Timestamp
- Footnote
Interface
- Button label
- Input label
- Placeholder
- Helper text
- Validation message
- Inline link
- Standalone link
- Tooltip
- Badge / tag
- Status label
- Menu item
- Table header
- Table cell
- Tabular figure
- Metric value
- Empty state
- Toast
- Modal title
- Settings group label
- Keyboard shortcut
- Inline code
- Code block
Meta
- Legal / fine print
- Copyright
- Counts & metadata
- Skeleton text
Collapsing them
- Create a role when a new job appears. Never when a new screen appears.
- If two roles always render identically, they are one role. Merge and rename to the broader job.
- Use size for level changes, weight for peer-level distinction. Two things at the same level should share a size and differ in weight or colour.
- Pick one or two signals per role. Size plus weight is enough. Size plus weight plus colour plus caps plus tracking is noise.
- Don't bind semantic heading level to visual size. Keep
h1–h6for document structure and let a separate visual role decide how it looks. - Don't use placeholder text as a label. It disappears the moment someone types.
- Don't let a role exist that has never been used twice.
A ten-role starter set
If you need somewhere to begin, this covers most products before any specialisation:
displaytitleheadingsubheadingbodybody-smlabelcaptioncodemetric
The scale
A modular scale multiplies a base size by a ratio, repeatedly. The formula is trivial. The mistake is choosing the ratio first.
Pick your endpoints instead — body size, largest display size, and how many steps you need — then solve for the ratio:
Choosing r blind is how you end up with a 182px heading you will never use. A ratio of 1.5 compounds faster than intuition expects.
Build one
Pure geometry produces 12.8px and 13.9px at the bottom, which are indistinguishable, and absurd numbers at the top. Let the small end go linear — 12, 14, 16 — and the large end stay geometric. Round everything to values a human would type. Switch the control above to raw geometry to see why this matters.
Ratios and what they feel like
| Ratio | Name | Character | Use for |
|---|---|---|---|
| 1.125 | Major second | Dense, technical. Levels barely separate, so weight and colour must carry hierarchy. | Dashboards, data tools |
| 1.2 | Minor third | Neutral workhorse. Calm, unremarkable, safe. | General UI |
| 1.25 | Major third | Balanced, quietly confident. | Product default |
| 1.333 | Perfect fourth | Clear voice, obvious levels. | Editorial, marketing |
| 1.414 | Augmented fourth | Assertive. Starts eating vertical space. | Landing pages |
| 1.5 | Perfect fifth | Bold, magazine-like. Survives four or five steps at most. | Editorial display |
| 1.618 | Golden | Dramatic. Needs three levels maximum. | Fashion, poster, brutalist |
Rules for the relative gaps
- Skip steps in use. A 1.25 scale with every step used reads as mush. Use every other step and you get an effective 1.56 between roles while keeping the in-between sizes available for edge cases.
- Cap at five or six levels. If you need more, the content structure is the problem, not the scale.
- Shift the ratio by viewport, not the roles. 1.2 on mobile, 1.333 on desktop, same content, same role names. This is what clamp() is actually for.
- Give metric numbers their own scale. A KPI at 40px above a 12px label is a legitimate 3.3× jump inside a 1.2 system. It works because the number is data, not a heading.
- Don't set two adjacent steps against each other. Close-but-different reads as an error, not a hierarchy.
- Don't go below 12px for anything a user is required to read, and hold a 15px floor for body in dense contexts.
Weight
Weight is the second hierarchy axis and the one that does the work when size cannot. It behaves differently from size: it is not perceptually linear, and small increments are invisible.
- 200 units minimum between weights. 400 against 500 looks like a rendering bug. 400 against 600, or 400 against 700, reads as intentional.
- One body weight across the whole system. Variation lives in the roles around body, never inside it.
- Bold for emphasis inside a paragraph, italic for titles and voice. Not the reverse.
- Reversed text — light on dark — needs slightly less weight and slightly more leading than the same setting on white.
- Don't use weights below 400 under 14px. Thin strokes disappear at small optical sizes and on low-density displays.
- Don't adopt a family that only ships two weights. You will want 400, 500 or 600, and 700 at minimum.
Leading
Line height is inverse to size and proportional to measure and x-height. Large text needs less leading because the eye does not need help finding the start of the next line. A wide column needs more, because the return sweep is longer and easier to lose.
| Role | Size | Leading | Note |
|---|---|---|---|
| Fine print | 12–13px | 1.4–1.5 | Small text needs air to stay legible |
| Body | 16–18px | 1.5–1.6 | 1.6 for serif, 1.5 for sans |
| Lead paragraph | 19–22px | 1.45–1.5 | |
| Subheading | 22–28px | 1.25–1.3 | |
| Heading | 30–44px | 1.15–1.2 | |
| Display | 48px+ | 1.0–1.1 | Below 1.0 only in deliberate lockups |
| Table cell | 13–14px | 1.3–1.4 | Density constraint overrides comfort |
A wide column with tight leading is the most common readability failure in interface work. The eye finishes a line, sweeps back to the left edge, and has to guess which line comes next. Widen the measure and the return sweep grows longer; the leading has to grow with it or the guess starts failing. Narrow the measure and generous leading starts to look loose and disconnected instead of comfortable. The two dials are bound together, which is why neither has a single correct value.
- Set leading unitless — line-height:1.55 — never in px, so it survives every size change.
- Large x-height faces need more leading than small x-height faces at the same nominal size.
- Don't chase strict baseline grids in component-based work. Vertical rhythm is a good idea that rarely survives real components. It is not a hill worth dying on.
Tracking
Letter spacing corrects for what the type designer could not know: your size. Most faces are drawn for a specific optical size, and you are almost always using them at a different one.
- Positive below 13px — roughly +1% to +3%. Small type crowds.
- Negative above 40px — roughly −1% to −3.5%. Large type gaps.
- All caps always needs +5% to +10%. Capitals were never designed to sit together.
- Use an optical-size axis instead if the variable font offers one. It is a better correction than tracking.
- Don't track lowercase body text in either direction. It is already correct.
- Don't leave the middle of the scale alone-adjusted. Between 14px and 36px, do nothing.
Measure and containers
Container width is a consequence of measure, and measure is set per role, not per size. Size is only the input that converts one to the other.
The 0.5 is the average character advance for most sans faces — use 0.45 for condensed, 0.55 for wide, 0.6 for monospace. So 66 characters lands at roughly 528px at 16px, 594px at 18px, and 660px at 20px. This is why "600px is the ideal column" is only true at one size.
Targets by role
| Role | Measure | Why |
|---|---|---|
| Display | 15–25ch | Meant to be seen as a shape, not read line by line |
| Page title | 20–35ch | Two or three lines maximum |
| Section heading | 30–45ch | Headlines lose impact past ~40 characters |
| Deck / standfirst | 45–60ch | Narrower than body so it reads as a bridge |
| Body | 60–75ch | 66 is the classic target |
| Blockquote | 50–60ch | Inset from body to signal the change of voice |
| Caption, helper | 40–60ch | Short text, short line |
| Table cell | 30–45ch | The tightest measure you will set legibly |
| Tooltip | 30–45ch | Beyond this it should be a panel |
| Button label | 12–20ch | Hard cap. Longer means it is not a button. |
| Legal / fine print | 60–80ch | Small size permits a longer line |
Measure is the width of the reading line expressed in characters, and it is the single most reliable readability control you have. It is stated in characters rather than pixels because the eye is counting glyphs, not measuring distance. A line that is comfortable at sixteen pixels becomes a strain at twenty-four in the same container, and a column that works for a serif at seventy characters can feel loose for a sans at the same count.
Set max-width in ch or em, padding in em for anything that scales, and line height unitless. Then a single size change propagates correctly instead of breaking three other decisions.
Grid, margins and padding
Decouple the grid from the measure
The grid column is a layout constraint. Measure is a reading constraint. Trying to make one serve both is where most layouts go wrong. Let the grid place the block, then give the text its own max-width inside that cell. A twelve-column article layout can span eight columns for its background and images while the paragraphs sit at 66ch inside it.
That said, it is worth tuning the grid so one common span lands near your body measure. On a 1200px container with twelve columns and 24px gutters, columns are 78px wide, and six columns span 588px — about 73ch at 16px. Half the grid is your measure. That is a good grid.
Left-aligned text in a wider container beats centred narrow text, because images and asides can then break out to the full column while the reading line stays fixed.
Margins, gutters, padding
| Value | Mobile | Tablet | Desktop | Rule |
|---|---|---|---|---|
| Page margin | 16–24 | 32–48 | 48–80 | Or fluid, clamped at both ends |
| Gutter | 16–24 | 24 | 24–32 | Never wider than the page margin |
| Panel padding | 16–20 | 24 | 24–32 | At least 1.5× the body line height |
| Button padding | 12 vertical / 24–28 horizontal | Horizontal 2–2.5× vertical | ||
- The proximity check. If a container has no background or border, its internal padding must be smaller than the gap to its siblings, or the grouping inverts and the padding reads as separation. With a background, padding can safely exceed the gap.
- Optical top padding is always less than the number. The line box includes half-leading above the cap height, so 32px of visual space needs about 28px set. text-box-trim fixes this properly where support allows.
- Padding in em for buttons, badges and inputs, so small and large variants stay proportional automatically.
- Don't use the type scale for spacing. Spacing runs on its own 4/8 scale.
- Don't set multi-column unless each column can hold 40ch or more.
Spacing between roles
Space belongs to the element below it. That single principle resolves most spacing questions, because it forces you to think about which relationship the gap is describing.
The relationship ladder
| Relationship | Example | Gap |
|---|---|---|
| Bonded | eyebrow → title, label → input, number → unit | 4–8 |
| Attached | heading → its paragraph, input → helper text | 8–12 |
| Sibling | paragraph → paragraph, field → field | 16–24 |
| Grouped | subsection → subsection, card → card | 32–48 |
| Separated | section → section | 64–96 |
| Page-level | major region breaks | 96–160 |
The cleaner derivation for headings
A 32px heading gets roughly 40px above and 12px below. The three-to-four times asymmetry falls out on its own, larger headings automatically get more air, and you never maintain a separate spacing table per level.
The previous section ends on this line and then continues past it.
The heading now floats between two paragraphs and belongs to neither.
The previous section ends on this line and then continues past it.
The heading is now clearly attached to the paragraph it introduces.
- White space around a block is a stronger grouping signal than any border you can draw. Reach for space before you reach for a line.
- Scale section spacing with the viewport. 96px of air between sections is generous on desktop and oppressive on a phone.
- Don't use the same gap between paragraphs as between sections. It destroys scannability faster than any other single mistake.
Lockups
A lockup is two or three roles that always travel together with a fixed internal relationship. You style the lockup once, not the roles separately. This is the level at which pairing questions actually get answered.
The five rules that hold for every lockup
- Internal gap smaller than external gap. If the eyebrow sits closer to the previous section than to its own title, the lockup does not exist.
- One dominant element. Two equals is not a lockup, it is two things.
- Differ on exactly two axes. Size and colour, or size and weight, or case and colour. Three is noise.
- Share one edge. Same left alignment or same baseline. Never both drifting.
- The subordinate must survive removal. If deleting the eyebrow breaks comprehension, it was never an eyebrow.
The recurring catalogue
| Lockup | Where it appears | Ratio | Gap |
|---|---|---|---|
| Eyebrow + Title | Heroes, section openers | 0.7–0.8× | 4–8 |
| Title + Deck | Articles, landing pages | 1.2–1.5× body | 12–16 |
| Heading + Body | The base unit of everything | 1.5–2.5× | 8–12 |
| Label + Value | Specs, profiles, metadata | 0.75–0.85× | 4–8 |
| Value + Unit + Delta | Metrics, KPIs | 0.5–0.6× | 2–12 |
| Label + Input + Helper | Every form | 0.85× | 6 / 6 |
| Title + Metadata | Bylines, timestamps, read time | 0.8× | 8 |
| Quote + Attribution | Testimonials, pull quotes | 0.75–0.85× | 16–24 |
| Caption + Credit | Images, figures | 0.85× | 4 |
| Icon + Label | Buttons, nav, menus | 1.0–1.25× cap | 6–8 |
| Term + Definition | Glossaries, documentation | 1.0× / bold | 4 |
| Card title + Body + Action | Every card grid | 1.25× | 8 / 16 |
| Avatar + Name + Role | Comments, lists, teams | 0.85× | 2 / 12 |
| Price + Period | Pricing tables | 0.4–0.5× | 4 |
| Empty state | Heading + body + action | 1.4× | 8 / 20 |
Live gallery
The failure was never the outage. It was that nobody could tell which region was serving.
How many popular versions are there?
Fewer than it looks. The structure of each lockup is essentially constant. What varies is the surface treatment of the subordinate element, and there are usually three or four of those in circulation.
| Version | Treatment | Reads as |
|---|---|---|
| Editorial | Small caps, tracked, tertiary grey | Considered, print-derived |
| Product | Sentence case, semibold, brand colour | Friendly, contemporary |
| Technical | Uppercase monospace | Systematic, machine-adjacent |
| Sequence | A number, only if the content really is a sequence | Procedural |
If you define sixteen lockups, you have defined sixteen components. Lockups sprawl faster than roles do. Start with five — heading + body, label + value, label + input + helper, title + metadata, icon + label — and promote a sixth only after it has been used three times in different contexts.
Vibes
A vibe is a coordinated setting of the seven dials. The panel below runs the same content through each one so you can see how much of the personality is coming from the dials rather than the typeface.
The catalogue in words
| Vibe | Setting | Feels |
|---|---|---|
| Swiss | Neutral grotesque, ratio 1.2, hierarchy on weight, flush left ragged right, leading 1.4 | Objective, engineered |
| Editorial | Old-style serif body, ratio 1.333–1.5, leading 1.6, measure 60–66ch, italic subheads | Authored, unhurried |
| Brutalist | Ratio 1.6+, two or three levels, leading 1.0–1.1, full-bleed measure, no optical correction | Raw, deliberately unpolished |
| Luxury | Didone display at huge size, everything else tiny and tracked, leading 1.8+, near-zero ink coverage | Expensive, because it wastes space |
| Technical | Grotesque or mono, ratio 1.125, hierarchy on colour and weight, tabular figures, leading 1.35 | Precise, dispassionate |
| Wellness | Humanist sans, low weight contrast, leading 1.7–1.8, lowercase headings, warm off-black | Calm, non-demanding |
| Consumer | Geometric or rounded sans, ratio 1.25, hierarchy on weight and colour, leading 1.5 | Friendly, unthreatening |
| Institutional | Transitional serif or safe sans, ratio 1.2, moderate everything | Credible, slightly dull |
| Newspaper | Condensed headline face, big ratio, 35–45ch columns, leading 1.3, hairline rules | Urgent, dense |
| Academic | One family throughout, small caps, ratio 1.15, minimal levels, footnotes at 0.75× | Rigorous, self-effacing |
| Terminal | Mono only, one or two sizes, hierarchy entirely on colour and indentation | Machinic |
| Zine | Unrelated families, inconsistent alignment, mixed scales, visible imperfection | Human, anti-institutional |
| Poster | One element at 10–20× body, everything else at a single small size. Two levels. | Declarative |
Three things worth knowing about vibe
- Whitespace does more work than the typeface. Set the same page in one family twice, once at leading 1.35 with tight margins and once at 1.7 with wide ones, and people will describe them as different brands.
- Match the vibe to content density, not to the mood board. Luxury typography collapses the moment there is real information to convey. Newspaper density feels oppressive on a marketing page.
- Commit to one hierarchy axis. Split hierarchy across size, weight and colour at once and everything reads as equally important, which is identical to nothing being important.
Dashboards
A dashboard is not a vibe. It is a constraint set that produces a narrow band of workable vibes. The constraint is that everything on screen is nominally important, so the classic hierarchy move — make one thing big — is unavailable. You lose your loudest dial before you start.
What the constraint forces
| Dial | Where it lands | Because |
|---|---|---|
| Ratio | 1.125–1.2 | No hero exists, and vertical space is scarce |
| Hierarchy axis | Colour and weight | Nearly everything sits at 12–16px. This is the one context where colour-as-hierarchy is correct rather than lazy. |
| Leading | 1.3–1.4 | Anything looser pushes the table below the fold |
| Measure | 30–45ch | Panels and cells are the tightest measure you set legibly |
| Grey ramp | Three tiers | Primary value, secondary support, tertiary chrome |
| Figures | Tabular, always | Proportional figures in a numeric column is a defect, not a preference |
| Metric scale | Separate | 32–48px numbers above 12px labels break the type scale legitimately |
Where dashboard typography actually goes wrong
- Panel titles competing with the data. If the title is as loud as the metric, users read chrome instead of content. Titles want to be small, grey and deliberately quiet.
- Units and deltas set at the value's size. "142.7", "ms" and "+12%" are three different roles. Unit at 0.5–0.6× the value, baseline-aligned rather than centred.
- Truncation with no strategy. Long labels wrap, truncate, or tooltip. Choosing one per role is a real decision. Most dashboards default to whatever happens.
- Density set once. A monitoring wall and an analyst's laptop need different densities. A compact/comfortable toggle should scale the spacing scale, never the type scale.
- Delta colour with no second signal. Red and green alone fail for roughly one in twelve men. Add an arrow or a sign.
Table typography
- Header at 11–12px, uppercase, tertiary grey. Cell at 13–14px, primary. They should differ in colour and case, not size, so column widths stay stable.
- Numeric columns right-aligned with tabular figures. Text columns left. Never centre either.
- Row separation by hairline or by zebra, never both.
Monitoring is glanceable: few numbers, large, read from a distance, colour doing most of the signalling. Analysis is dense: many values, small, read up close, with sorting and comparison as the primary action. These are different type systems wearing the same name, and picking one is the first decision.
Typefaces
Why monospace fails for prose
Monospace forces every glyph into an identical advance width, so i and l get padded out while m and w get squeezed. Word shapes lose their distinctive silhouettes, which is what the eye is actually matching at reading speed. Fixation efficiency drops and reading slows measurably past a sentence or two. Mono also consumes more horizontal space, so either the measure runs long or the size has to shrink.
A definition should be read once and understood. Proportional spacing preserves the outline of each word, so the eye can recognise whole shapes instead of assembling them letter by letter.
A definition should be read once and understood. Proportional spacing preserves the outline of each word, so the eye can recognise whole shapes instead of assembling them letter by letter.
Where mono does earn its keep: character-level precision (code, IDs, hashes, hex values), vertical alignment (columns of numbers, diffs, logs), and machine-voice signalling. A short label or a five-word tag in mono is fine. A paragraph of definition text is not.
Much of the "mono is unreadable" claim is inherited from terminal-era faces with poor letterfit. Modern humanist monospaces are considerably better than that reputation suggests. It is still worse than a proportional face for prose — just not disastrously so.
Faces worth knowing, by job
| Job | Faces | Note |
|---|---|---|
| UI body, sans | Inter, Public Sans, Source Sans 3, IBM Plex Sans | Large x-height, tested at small sizes |
| Accessibility-first | Atkinson Hyperlegible | Genuinely better for low vision, not just marketed as such |
| Long-form serif | Source Serif 4, Literata, Charter/Charis, Newsreader, Spectral | Literata is drawn for screen reading |
| Display with personality | Fraunces, Instrument Serif, Bricolage Grotesque, Archivo Expanded | Variable axes give you range without extra files |
| High-contrast display | Playfair Display | Never below ~28px. The hairlines vanish. |
| Monospace | JetBrains Mono, IBM Plex Mono, Commit Mono, Berkeley Mono (paid) | Plex Mono is the one with a proportional sibling |
Pairing
The reliable move is a superfamily: IBM Plex Sans with Plex Serif and Plex Mono, or Source Sans with Source Serif. Same skeleton, different voices, guaranteed harmony, and the decision is permanently removed. This manual is set that way.
If you want more contrast than a superfamily gives you, the rule is that the two faces must differ obviously. Serif against sans. Condensed against normal. High contrast against low. Two grotesques that are only slightly different is the worst possible outcome, because it reads as a mistake rather than a choice.
- Two families plus a mono, maximum.
- Check the family ships the weights, italics and figure sets you need before falling in love with it.
- Test at your smallest real size on your worst target display before committing.
- Don't set ultra-geometrics for body. The circular o and e make word shapes too uniform and reading gets tiring over a page.
- Don't use a face with a very low x-height at UI sizes.
Colour and contrast
- 4.5:1 minimum for body. 3:1 for text above 24px, or bold above 19px.
- Three tiers of grey, no more. Primary, secondary, tertiary. A fourth tier is always an unresolved hierarchy question in disguise.
- Use a near-black rather than pure black on white. Pure black on pure white is harsher than it needs to be, and slightly reduced contrast reads as more comfortable over long sessions.
- Links need a non-colour signal too — underline, weight, or icon.
- Don't let colour carry hierarchy alone outside dense data contexts. Grey text is a de-emphasis signal, not a level.
- Don't encode meaning in colour without a second channel: an icon, a weight change, or a label.
Micro-typography
These are small, cheap, and the fastest way to make work look considered.
| Mark | Correct | Wrong | Use |
|---|---|---|---|
| Apostrophe | it’s | it's | Curly, never the prime |
| Quotes | “so” | "so" | Curly pairs |
| Hyphen | well-formed | — | Compounds only |
| En dash | 2019–2026 | 2019-2026 | Ranges |
| Em dash | a break—here | a break - here | Parenthetical breaks |
| Prime | 6′ 2″ | 6' 2" | Feet and inches only |
| Multiply | 1440×900 | 1440x900 | Dimensions |
| Ellipsis | … | ... | One character |
- Non-breaking space before units and after short numbers, so 90 days never breaks across a line.
- Tabular figures wherever numbers stack vertically: font-variant-numeric: tabular-nums.
- Hanging punctuation on pull quotes where the layout allows it.
- One space after a period.
- Widows and orphans matter in headings. In body text they matter much less than people claim.
- Flush left, ragged right, as the default. Justify only with hyphenation enabled and a measure wide enough to avoid rivers.
- Don't centre paragraphs. Centring works for one to three lines and fails past that, because the left edge moves and the eye loses its return point.
- Don't set all caps beyond two or three words.
Stress tests
Every role must be tested in its worst case before it ships, not after. Four cases catch nearly everything.
| Case | Test | Catches |
|---|---|---|
| Longest | The longest realistic string, not a placeholder | Truncation with no strategy, broken alignment |
| Empty | Null, zero, no data yet | Collapsed layouts, orphaned labels |
| Wrap | Force two and three lines | Leading that only worked at one line, broken lockup gaps |
| Translated | Assume +30% length; German and Finnish are the usual worst cases | Fixed-width buttons, nav overflow, tab labels |
Padding in em and no fixed width means all four survive. The empty case is why button labels need a fallback string, not an empty state.
The full checklist
Everything above, compressed. These are defaults you break knowingly.
Size and scale
Body 16–18px on screen. 15px floor for dense contexts. Never below 12px for required reading.
Set endpoints first, then solve for the ratio.
Don't use adjacent scale steps against each other. Skip one.
Cap at five or six levels.
Metric numbers get their own scale.
Round geometric output to values a human would type.
Shift the ratio by viewport; keep the roles fixed.
Weight
200+ units of separation or it reads as a bug.
No weights under 400 below 14px.
Bold for emphasis in a paragraph, italic for titles and voice.
One body weight across the whole system.
Leading
Inverse to size: body 1.5, subhead 1.25, display 1.05–1.15.
Proportional to measure and to x-height.
Unitless, never px.
Never below 1.0 except in intentional display lockups.
Reversed text: slightly more leading, slightly less weight.
Measure and containers
Body 45–75ch, 66 as the target.
Headings 25–40ch. Dashboard cells 30–45ch.
Set max-width in ch or em, not px.
Multi-column only if each column holds 40ch or more.
Grid places the block; the text sets its own measure inside it.
Spacing
Space belongs to the element below it.
Above a heading ≈ 1–1.5× its size; below ≈ 0.25–0.5×.
Spacing runs on its own 4/8 scale, independent of type.
White space groups more strongly than any border.
Unbordered container padding must be smaller than the sibling gap.
Optical top padding is less than the number you set.
Scale section spacing with viewport.
Tracking, case, alignment
Positive below 13px, negative above 40px, nothing in between.
All caps always needs +5–10%, and never past three words.
Never track lowercase body.
Sentence case for UI labels and buttons.
Flush left ragged right by default. Centre only 1–3 lines.
Colour
4.5:1 body, 3:1 for large or bold.
Three tiers of grey, no more.
Near-black, not pure black.
Colour never carries meaning alone.
Typefaces
Two families plus a mono, maximum.
Superfamily is the safest pairing; otherwise make contrast obvious.
Mono for precision and alignment, never for prose.
Tabular figures wherever numbers stack.
Verify weights and figure sets before committing.
Structure
Semantic heading level and visual size are independent.
One role per job. New job, new role. New screen, no new role.
A role is a locked bundle of seven properties.
Lockup internal gap always smaller than external gap.
One dominant element per lockup; two axes of difference.
Stress-test longest, empty, wrapped, translated.
Micro
Curly quotes and apostrophes.
En dash for ranges, em dash for breaks, hyphen for compounds.
Non-breaking space before units.
One space after a period.
Widows matter in headings, less in body.
Adjacent scale steps. Spacing attached to the wrong element. Hierarchy split across three axes at once.