Typography System

Typography

Set a body size, a largest size, and a number of levels. Every other value — the ratio, the intermediate sizes, line heights, container widths, the gaps between elements — follows from those three.

Why it feels hard

Typography gets treated as forty independent decisions. It is closer to three decisions and a set of consequences. When a project feels like it is starting from zero, those three numbers were never fixed.

The seven dials

Personality in type comes from where these seven sit, not from the typeface. Change the typeface and hold the dials and you get a variant. Hold the typeface and change the dials and you get a different design.

  1. Ratio — how far apart the levels sit
  2. Hierarchy axis — whether size, weight, colour, case, space or family carries the structure
  3. Leading — line height relative to size
  4. Measure — the width of the reading line
  5. Tracking — letter spacing, mainly at the extremes
  6. Density — the ratio of ink to whitespace
  7. Case and alignment
These are defaults, not laws

Break any of them knowingly. They are stated as numbers because a number can be argued with.

00

Glossary

Every term used elsewhere on this site. Linked terms jump here.

Advance width
The horizontal space a character occupies, including the small gaps either side of it. Not the width of the visible shape.
Ascender
The part of a lowercase letter that rises above the x-height, as in b, d, k.
Baseline
The invisible line letters sit on. Aligning two different sizes of text usually means aligning their baselines, not their boxes.
Cap height
The height of a capital letter above the baseline. Usually shorter than an ascender.
ch (unit)
A CSS unit equal to the width of the character 0 in the current font. Because it scales with the font, it is the right unit for measure.
Counter
The enclosed space inside a letter, as in o, e, a. Small counters close up at small sizes and are why some faces fail as body text.
Deck
The short introductory paragraph between a title and the body. Also called a standfirst. Set larger than body, lighter, and to a narrower measure.
Descender
The part of a letter that drops below the baseline, as in p, g, y.
Didone
A serif class with extreme contrast between thick and thin strokes and flat, unbracketed serifs. Playfair Display is one. The hairlines vanish at small sizes, so these are display faces only.
em
A unit equal to the current font size. Padding set in em scales automatically when the size changes, which is why component padding belongs in em.
Eyebrow
The small label above a title that says what category or section it belongs to. Also called a kicker. Must survive deletion — if removing it breaks comprehension, it was carrying real content and is not an eyebrow.
Figures
Numerals. Lining figures all sit between baseline and cap height. Old-style figures have ascenders and descenders and mix better into prose. Tabular figures share one advance width so columns align. Proportional figures are drawn to look right in a sentence and misalign in a column.
Geometric sans
A sans built from circles and straight lines, as in DM Sans or Futura. Distinctive at display size, tiring as body text because the round letters make word shapes too uniform.
Grotesque
The default class of sans serif — Helvetica, Archivo, Inter. Neutral, workmanlike, and what most interfaces are set in. A humanist grotesque, like IBM Plex Sans, keeps more of the hand-written skeleton and reads more warmly.
Kerning
Adjusting the space between two specific letters, as in AV or To. Built into the font. Distinct from tracking, which adjusts every gap uniformly.
Leading
The vertical distance between lines of text, pronounced "ledding" after the strips of lead once used to make it. In CSS it is line-height, and it should be set unitless so it scales with size.
Lockup
Two or three roles that always travel together with a fixed internal relationship — eyebrow and title, label and value, icon and label. You style the lockup once rather than the roles separately.
Measure
The width of the reading line, counted in characters rather than pixels, because the eye is counting glyphs and not measuring distance. 45 to 75 characters for body.
Modular scale
A set of sizes generated by repeatedly multiplying a base size by a fixed ratio, so that every level relates to every other by the same proportion.
Monospace
A font where every character has the same advance width. Correct for code, IDs and anything requiring vertical alignment. Wrong for prose, because uniform widths destroy the word shapes the eye reads by.
Optical size
The size a typeface was drawn to be used at. A face drawn for 10pt has more open spacing and sturdier strokes than the same design drawn for 60pt. Variable fonts increasingly expose this as an axis you can set directly.
Orphan and widow
An orphan is a single line stranded at the bottom of a column; a widow is the last word of a paragraph alone on its own line. Both matter in headings and matter much less in body text than people claim.
Rag
The uneven right edge of flush-left text. A good rag varies gently. A bad rag makes accidental shapes, or alternates long and short lines in a visible rhythm.
Ratio
The multiplier between one scale step and the next. Solve for it from your endpoints rather than picking it, because a ratio compounds faster than intuition expects.
River
A pale channel of whitespace running down through justified text where word gaps line up by accident. The main reason to justify only with hyphenation enabled.
Role
A distinct job a piece of text does, named by that job rather than by its size or location. A role is a locked bundle: size, weight, leading, tracking, colour, measure and the space below it.
Stroke contrast
The difference between the thickest and thinnest parts of a letter. Low contrast survives small sizes and reversed text; high contrast is a display quality.
Superfamily
A set of typefaces sharing one skeleton across several classes — IBM Plex Sans, Serif and Mono, or Source Sans and Source Serif. The safest pairing there is, because harmony is guaranteed and the decision is removed permanently.
tnum
The OpenType feature that switches a font to tabular figures. Reached in CSS by font-variant-numeric: tabular-nums. It fails silently when the font does not ship the feature, so it must be verified rather than assumed.
Tracking
Uniform letter spacing applied across a run of text, called letter-spacing in CSS. Positive below 13px, negative above 40px, and left alone in between.
Transitional serif
The middle ground of serif design — moderate contrast, fairly upright stress. Source Serif and Charter are examples. The safest choice for long-form reading on screen.
Variable font
A single font file containing a continuous range along one or more axes — weight, width, optical size. One file replaces a dozen, and intermediate values become available.
Weight
Stroke thickness, numbered 100 to 900 where 400 is regular and 700 is bold. Differences under 200 units are not perceived as intentional.
x-height
The height of a lowercase x. The single most useful metric for judging how large a face will look in use, and for deciding whether two faces can be set at the same size.
01

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 interface and editorial work. A working system needs ten to fourteen. Collapsing them 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 h1h6 for 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

Covers most products before any specialisation:

displaytitleheadingsubheadingbodybody-smlabelcaptioncodemetric

02

The scale

A modular scale multiplies a base size by a ratio, repeatedly. The common mistake is choosing the ratio first.

size = base × r

Pick your endpoints instead — body size, largest display size, and how many steps you need — then solve for the ratio:

r = (max ÷ base) ^ (1 ÷ steps)

Choosing r blind produces a 182px heading you will never use. A ratio of 1.5 compounds faster than intuition expects.

Build one

Scale generatorSet the endpoints; the ratio is computed
Hand-tune both ends

Pure geometry produces 12.8px and 13.9px at the bottom, which are indistinguishable, and unusable 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.

Ratios and what they feel like

The usable range. Ratio is the loudest single dial.
RatioNameCharacterUse for
1.125Major secondDense, technical. Levels barely separate, so weight and colour must carry hierarchy.Dashboards, data tools
1.2Minor thirdNeutral workhorse. Calm, unremarkable, safe.General UI
1.25Major thirdBalanced, quietly confident.Product default
1.333Perfect fourthClear voice, obvious levels.Editorial, marketing
1.414Augmented fourthAssertive. Starts eating vertical space.Landing pages
1.5Perfect fifthBold, magazine-like. Survives four or five steps at most.Editorial display
1.618GoldenDramatic. 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.
Adjacent versus skippedSame 1.2 scale
Adjacent steps
Quarterly summary
The two sizes are close enough that the eye reads a rendering fault rather than a relationship.
One step skipped
Quarterly summary
The gap is unambiguous. Same family, same weight, same colour.
03

Weight

Weight is the second hierarchy axis, and the one that works when size cannot. It is not perceptually linear: 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.
Weight separationIBM Plex Sans, all at 19px
400 → 500+100reads as an accident
Available capacity   Available capacity
400 → 600+200the working minimum
Available capacity   Available capacity
300 → 700+400editorial contrast
Available capacity   Available capacity
04

Leading

Line height is inverse to size, and proportional to measure and x-height. Large text needs less because the eye does not need help finding the next line. A wide column needs more, because the return sweep is longer.

Set as a unitless multiplier so it scales with the size.
RoleSizeLeadingNote
Fine print12–13px1.4–1.5Small text needs air to stay legible
Body16–18px1.5–1.61.6 for serif, 1.5 for sans
Lead paragraph19–22px1.45–1.5
Subheading22–28px1.25–1.3
Heading30–44px1.15–1.2
Display48px+1.0–1.1Below 1.0 only in deliberate lockups
Table cell13–14px1.3–1.4Density constraint overrides comfort
Leading against measureThe two trade off

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.
05

Tracking

Letter spacing corrects for size. Most faces are drawn for one optical size and used at another.

  • 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 adjust between 14px and 36px. The letterfit is already correct there.
Tracking at the extremesIBM Plex Sans
Untracked
Return sweep
Storage utilisation
Corrected
Return sweep
Storage utilisation
06

Measure and containers

Container width follows from measure. Measure is set per role, not per size; size only converts one to the other.

width ≈ targetch × font-size × 0.5

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

Set in ch or em on the text itself, never in px on a wrapper.
RoleMeasureWhy
Display15–25chMeant to be seen as a shape, not read line by line
Page title20–35chTwo or three lines maximum
Section heading30–45chHeadlines lose impact past ~40 characters
Deck / standfirst45–60chNarrower than body so it reads as a bridge
Body60–75ch66 is the classic target
Blockquote50–60chInset from body to signal the change of voice
Caption, helper40–60chShort text, short line
Table cell30–45chThe tightest measure you will set legibly
Tooltip30–45chBeyond this it should be a panel
Button label12–20chHard cap. Longer means it is not a button.
Legal / fine print60–80chSmall size permits a longer line
Measure rulerGreen band is 45–75 characters
20ch≈ 66ch120ch

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.

The units that matter

Set max-width in ch or em, padding in em, line height unitless. A single size change then propagates instead of breaking three other decisions.

07

Grid, margins and padding

Decouple the grid from the measure

The grid column is a layout constraint; measure is a reading constraint. Let the grid place the block, then give the text its own max-width inside that cell. A twelve-column layout can span eight columns for background and images while the paragraphs sit at 66ch.

Tune the grid so one common span lands near your body measure. On a 1200px container with twelve columns and 24px gutters, six columns span 588px, about 73ch at 16px. Half the grid is your measure.

Left-aligned text in a wider container beats centred narrow text: images and asides can break out to the full column while the reading line stays fixed.

Margins, gutters, padding

ValueMobileTabletDesktopRule
Page margin16–2432–4848–80Or fluid, clamped at both ends
Gutter16–242424–32Never wider than the page margin
Panel padding16–202424–32At least 1.5× the body line height
Button padding12 vertical / 24–28 horizontalHorizontal 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.
08

Spacing between roles

Space belongs to the element below it. This resolves most spacing questions, because it forces you to name which relationship the gap describes.

The relationship ladder

Gap by relationship strength, on a 4/8 scale.
RelationshipExampleGap
Bondedeyebrow → title, label → input, number → unit4–8
Attachedheading → its paragraph, input → helper text8–12
Siblingparagraph → paragraph, field → field16–24
Groupedsubsection → subsection, card → card32–48
Separatedsection → section64–96
Page-levelmajor region breaks96–160

The cleaner derivation for headings

space-above ≈ 1 – 1.5× the heading's own size space-below ≈ 0.25 – 0.5× the heading's own size

A 32px heading gets about 40px above and 12px below. The asymmetry falls out on its own, larger headings get more air automatically, and there is no separate spacing table per level.

ProximityIdentical type; only the gaps move
Space above the heading is too small

The previous section ends on this line and then continues past it.

Retention policy

The heading now floats between two paragraphs and belongs to neither.

Above is 3× below

The previous section ends on this line and then continues past it.

Retention policy

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.
09

Lockups

A lockup is two or three roles that always travel together with a fixed internal relationship. Style the lockup once, not the roles separately.

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

LockupWhere it appearsRatioGap
Eyebrow + TitleHeroes, section openers0.7–0.8×4–8
Title + DeckArticles, landing pages1.2–1.5× body12–16
Heading + BodyThe base unit of everything1.5–2.5×8–12
Label + ValueSpecs, profiles, metadata0.75–0.85×4–8
Value + Unit + DeltaMetrics, KPIs0.5–0.6×2–12
Label + Input + HelperEvery form0.85×6 / 6
Title + MetadataBylines, timestamps, read time0.8×8
Quote + AttributionTestimonials, pull quotes0.75–0.85×16–24
Caption + CreditImages, figures0.85×4
Icon + LabelButtons, nav, menus1.0–1.25× cap6–8
Term + DefinitionGlossaries, documentation1.0× / bold4
Card title + Body + ActionEvery card grid1.25×8 / 16
Avatar + Name + RoleComments, lists, teams0.85×2 / 12
Price + PeriodPricing tables0.4–0.5×4
Empty stateHeading + body + action1.4×8 / 20

Live gallery

Five lockupsOne family throughout, so structure is the only variable
Infrastructure
Regional failover moves to active-active
Both regions now serve live traffic, and the cutover window drops from eleven minutes to zero.
Regioneu-west-2
Replicas6
Last failover14 Mar 2026
Days before archived logs are deleted.
The failure was never the outage. It was that nobody could tell which region was serving.
Priya Raman, Reliability lead
Migration checklist
28 Aug 20267 min readUpdated
Icon optical size matches cap height, not font size. Both share a vertical centre, not a baseline.

How many popular versions are there?

Fewer than it looks. The structure of each lockup is constant. Only the surface treatment of the subordinate element varies, and there are usually three or four of those in circulation.

Eyebrow + Title, the four versions you will actually encounter.
VersionTreatmentReads as
EditorialSmall caps, tracked, tertiary greyConsidered, print-derived
ProductSentence case, semibold, brand colourFriendly, contemporary
TechnicalUppercase monospaceSystematic, machine-adjacent
SequenceA number, only if the content really is a sequenceProcedural
Lockups are components, and components cost maintenance

Sixteen lockups means sixteen components. Lockups sprawl faster than roles. Start with five: heading + body, label + value, label + input + helper, title + metadata, icon + label. Promote a sixth only after it has been used three times in different contexts.

10

Vibes

A vibe is a coordinated setting of the seven dials.

Vibe switcherIdentical copy in every setting

The catalogue in words

VibeSettingFeels
SwissNeutral grotesque, ratio 1.2, hierarchy on weight, flush left ragged right, leading 1.4Objective, engineered
EditorialOld-style serif body, ratio 1.333–1.5, leading 1.6, measure 60–66ch, italic subheadsAuthored, unhurried
BrutalistRatio 1.6+, two or three levels, leading 1.0–1.1, full-bleed measure, no optical correctionRaw, deliberately unpolished
LuxuryDidone display at huge size, everything else tiny and tracked, leading 1.8+, near-zero ink coverageExpensive, because it wastes space
TechnicalGrotesque or mono, ratio 1.125, hierarchy on colour and weight, tabular figures, leading 1.35Precise, dispassionate
WellnessHumanist sans, low weight contrast, leading 1.7–1.8, lowercase headings, warm off-blackCalm, non-demanding
ConsumerGeometric or rounded sans, ratio 1.25, hierarchy on weight and colour, leading 1.5Friendly, unthreatening
InstitutionalTransitional serif or safe sans, ratio 1.2, moderate everythingCredible, slightly dull
NewspaperCondensed headline face, big ratio, 35–45ch columns, leading 1.3, hairline rulesUrgent, dense
AcademicOne family throughout, small caps, ratio 1.15, minimal levels, footnotes at 0.75×Rigorous, self-effacing
TerminalMono only, one or two sizes, hierarchy entirely on colour and indentationMachinic
ZineUnrelated families, inconsistent alignment, mixed scales, visible imperfectionHuman, anti-institutional
PosterOne 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 twice in one family, once at leading 1.35 with tight margins and once at 1.7 with wide ones. 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.
11

Dashboards

A dashboard is a constraint set, not a vibe. Everything on screen is nominally important, so the usual move of making one thing big is unavailable. You lose the loudest dial before you start.

What the constraint forces

DialWhere it landsBecause
Ratio1.125–1.2No hero exists, and vertical space is scarce
Hierarchy axisColour and weightNearly everything sits at 12–16px. This is the one context where colour-hope-hierarchy is correct rather than lazy.
Leading1.3–1.4Anything looser pushes the table below the fold
Measure30–45chPanels and cells are the tightest measure you set legibly
Grey rampThree tiersPrimary value, secondary support, tertiary chrome
FiguresTabular, alwaysProportional figures in a numeric column is a defect, not a preference. Confirm the family supports it.
Metric scaleSeparate32–48px numbers above 12px labels break the type scale legitimately
Metric lockupToggle the failure modes
Request pipeline

Figures

Tabular figures give every digit an identical advance width, so numbers stack into a clean right edge. Proportional figures are drawn for running prose, where a narrow 1 beside a wide 0 reads better. In a column they misalign and the eye can no longer compare magnitudes by edge position.

One column, three waysLiterata and DM Sans, 21px, right-aligned
Literata, tabular

Digits share one width. The decimal point holds a straight line.

Literata, proportional

Same family, one property changed. Every row now starts somewhere else.

DM Sans, no fix available

DM Sans ships no tnum. The property is set and does nothing.

Check the font before you rely on the property

font-variant-numeric: tabular-nums only works if the family actually ships a tnum feature, and it is silently ignored when it does not. Three cases exist, and you need to know which one you are in.

Measured from the shipped font binaries. Digit widths are in units per 1000 em.
FamilyDigit widthstnumWhat this means
IBM Plex Sansall identicalabsentAlready tabular. The property is unnecessary and has no proportional alternative.
Source Serif 4all identicalpresentAlready tabular. The feature is declared but changes nothing.
Literata409–591presentStrongly proportional by default, switchable. The normal case.
Archivo547–575presentNearly uniform already, so the switch barely shows. Width spread varies by weight.
DM Sans312–684absentProportional with no remedy. Do not use it for stacked numbers.

Quick test: set a column of 111 above 000 and check whether the edges line up. If they do not and tnum does not fix it, the family is disqualified for tables, metrics and log output.

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.
Which dashboard are you building?

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, sorting and comparison as the primary action. These are different type systems with the same name. Pick one first.

12

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 the silhouettes the eye matches at reading speed. Fixation efficiency drops and reading slows measurably past a sentence or two. Mono also consumes more horizontal space, so the measure runs long or the size shrinks.

Same text, size and measureNote where reading slows
Proportional

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.

Monospace

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.

The fair caveat

Much of the "mono is unreadable" claim comes from terminal-era faces with poor letterfit. Modern humanist monospaces are considerably better. Still worse than a proportional face for prose, but not disastrously.

Faces worth knowing, by job

All available on Google Fonts unless noted.
JobFacesNote
UI body, sansInter, Public Sans, Source Sans 3, IBM Plex SansLarge x-height, tested at small sizes
Accessibility-firstAtkinson HyperlegibleGenuinely better for low vision, not just marketed as such
Long-form serifSource Serif 4, Literata, Charter/Charis, Newsreader, SpectralLiterata is drawn for screen reading
Display with personalityFraunces, Instrument Serif, Bricolage Grotesque, Archivo ExpandedVariable axes give you range without extra files
High-contrast displayPlayfair DisplayNever below ~28px. The hairlines vanish.
MonospaceJetBrains Mono, IBM Plex Mono, Commit Mono, Berkeley Mono (paid)Plex Mono is the one with a proportional sibling
SpecimensSame string, same size, same measure
IBM Plex Sanshumanist grotesque
Handgloves & 0123456789
Archivogrotesque, wide range
Handgloves & 0123456789
DM Sansgeometric
Handgloves & 0123456789
Source Serif 4transitional serif
Handgloves & 0123456789
Literatascreen-reading serif
Handgloves & 0123456789
Playfair Displaydidone — display only
Handgloves & 0123456789
Instrument Serifdisplay serif
Handgloves & 0123456789
Bricolage Grotesquedisplay, variable
Handgloves & 0123456789
JetBrains Monomonospace
Handgloves & 0123456789

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 removed permanently.

For more contrast than a superfamily gives, the two faces must differ obviously: serif against sans, condensed against normal, high contrast against low. Two grotesques that differ slightly is the worst 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.
  • Verify the family ships tnum before choosing it for anything numeric. The CSS property fails silently when the feature is absent, and some families have no tabular figures at all.
  • 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.
13

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.
14

Micro-typography

Small, cheap, and the fastest way to make work look considered.

MarkCorrectWrongUse
Apostropheit’sit'sCurly, never the prime
Quotes“so”"so"Curly pairs
Hyphenwell-formedCompounds only
En dash2019–20262019-2026Ranges
Em dasha break—herea break - hereParenthetical breaks
Prime6′ 2″6' 2"Feet and inches only
Multiply1440×9001440x900Dimensions
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.
15

Stress tests

Test every role in its worst case before it ships. Four cases catch nearly everything.

CaseTestCatches
LongestThe longest realistic string, not a placeholderTruncation with no strategy, broken alignment
EmptyNull, zero, no data yetCollapsed layouts, orphaned labels
WrapForce two and three linesLeading that only worked at one line, broken lockup gaps
TranslatedAssume +30% length; German and Finnish are the usual worst casesFixed-width buttons, nav overflow, tab labels
One button role, four waysFixed width fails on the third

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.

16

The full checklist

Everything above, compressed.

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 — after confirming the family ships tnum.

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.

The three most often broken by good designers

Adjacent scale steps. Spacing attached to the wrong element. Hierarchy split across three axes at once.

17

Elsewhere on this site