Interaction & Visual Craft

tidy-typography.css: A Reset That Lets CSS Do the Typographer's Job

A new reset, tidy-typography.css, replaces JavaScript orphan fixers and negative-margin hacks with container query units, text-box-trim and tuned hyphenation — all degrading gracefully.

  • AuthorRebecca Stone
  • ChannelInteraction & Visual Craft
  • Filed
  • Read4 min
Taming Typography: Building a Modern CSS Reset
Taming Typography: Building a Modern CSS ResetPexels / pixabay
  • tidy-typography.css ships five fluid type steps sized in cqi units, from 1rem–1.125rem up to 2.5rem–4rem
  • text-wrap: balance works best for blocks up to about six lines; text-wrap: pretty replaces JavaScript orphan fixers
  • Hyphenation limits cap consecutive hyphenated lines at 2 and set an 8% hyphenation limit-zone
  • Dark mode dials the GRAD axis to -50 to compensate for light-on-dark weight gain
  • The proposed :heading selector from Selectors Level 5 is kept behind an @supports check because no browser ships it

A new stylesheet called tidy-typography.css moves typography decisions that once required JavaScript or per-font hacks into plain CSS declarations — with every rule written to degrade gracefully when a browser doesn't support it.

The reset covers eight areas: rendering and fallback rules, fluid sizing, vertical rhythm, text wrapping, hyphenation, hanging punctuation, variable font tuning, and OpenType detail. You can adopt it whole or lift a single declaration that solves the irritation in front of you.

What does container-type change for fluid type?

Viewport-based fluid type has a blind spot: it can't see how wide its own element is. A heading in a 300px sidebar scales as if it fills the whole viewport. The fix is one line — container-type: inline-size on a wrapper — which turns it into a container query. Children can then size themselves in cqi units, so the same heading scales correctly in an article column or a narrow sidebar without component-specific media queries:

--step-3: clamp(2rem, 1.6rem + 3cqi, 2.75rem);

The reset ships five type steps, from --step-0 (clamped between 1rem and 1.125rem) up to --step-4 (2.5rem to 4rem).

How does text-box-trim fix vertical rhythm?

Every line box carries half-leading: extra space above the cap-height and below the baseline. It shows up whenever text must align tightly with another element. text-box: trim-both cap alphabetic removes that space, measuring from capital-letter caps to the alphabetic baseline — a metrics-aware replacement for font-specific negative-margin nudges.

The proposed :heading selector from Selectors Level 5 would match h1–h6 and [role="heading"], but it hasn't landed in browsers. The author keeps it behind an @supports selector() check.

Which wrapping rules replace JavaScript?

Three values do the work. text-wrap: balance distributes headline text across lines and works best for blocks up to about six lines. text-wrap: pretty prevents a stranded final word in paragraphs without balancing the whole block. text-wrap: stable stops editable text from reshuffling on every new word — the reason a contenteditable rule sits alongside the others. Both balance and pretty replace JavaScript orphan fixers outright.

Hyphenation gets detailed tuning rather than a bare hyphens: auto, which needs a correct lang attribute to work at all:

  • hyphenate-limit-chars: 6 3 3 — minimum word length and fragment sizes around a break
  • hyphenate-limit-lines: 2 — no more than two consecutive hyphenated lines
  • hyphenate-limit-zone: 8% — less frequent breaks overall
  • hyphenate-limit-last: always — never hyphenate a paragraph's final word
  • Safari-prefixed equivalents declared alongside

Code, variables and headings stay at hyphens: manual: a broken code word changes meaning, and a hyphenated heading reads as an error.

What do variable fonts and OpenType features add?

font-optical-sizing: auto lets a font use thinner strokes at display sizes and thicker, more legible strokes at small sizes, based on its rendered size. A dark-mode block dials a font's GRAD axis to -50, because light text on dark reads heavier — without changing glyph shape or width the way a real weight change would.

Numeric handling splits by context. Tables get tabular-nums lining-nums so digits stack into clean columns; flowing prose gets oldstyle-nums proportional-nums so digits sit naturally beside lowercase letters. font-variant-emoji makes the colour-versus-monochrome choice explicit instead of leaving it to platform fallback. And font-variant-position swaps synthesized sub and superscripts — browsers just shrink text and shift the baseline — for glyphs actually designed for that position, though most typefaces include proper numerals only, so check before applying broadly.

Locale-aware quotes rules render the native q element correctly per language: double-outside-single for English, reversed for British English, guillemets with a thin space for French. Wrapping them in :where() keeps specificity at zero, so one plain class can override any default — the principle behind the whole reset.

Browser support for text-wrap and text-box-trim will keep shifting, and unsupported declarations are silently ignored — which only helps when the unenhanced typography stands on its own. As the author puts it, a news article, a marketing page and an editable product interface don't ask the same things of their text; what belongs in your reset depends on the font, the content, and the browsers you still support.

Original: drafts.csswg.org

Share this article:

More from Rebecca Stone

Rebecca Stone

Show full bio

News editor covering technology and product news in Product and interaction design, design systems at Interface Wire.

50 articles

Related articles

  1. Firefox's headingoffset Is Not the Document Outline Algorithm

  2. CSS-Tricks Starts Redesign Rollout, Cutting Zipped CSS by 22%

  3. Scroll-State Queries, Custom Borders and Baseline Wins: CSS Roundup

  4. CSS-Tricks Revives What's !important as a Newsletter on November 10

« Previous article