Home
cd ../playbooks
Developer ToolsIntermediate

Better Web Typography

Web typography engineering as a review discipline, not a style guide — CSS properties over raw variable-font tags, a semantic type scale with descending heading sizes, deliberate text-wrap usage (balance vs. pretty), tabular numbers for changing values, mobile input zoom fixes, and a severity-rated review report format with a concrete common-mistakes table.

5 minutes
By jakubkrehel (skills)Source
#typography#css#web-design#frontend#accessibility#design-review

A heading two levels down that's the same size as its parent, a timer whose digits shift width every second, or a mobile input that triggers iOS Safari's page-zoom — none of these are edge cases, they're the specific, nameable typography mistakes that show up in nearly every unreviewed interface, and each one has a one-line CSS fix.

Who it's for: frontend developers who want typography rules that are specific and checkable instead of vague 'make it look better' feedback, designers and engineers debugging widows, orphans, or bad line-wrapping at real content lengths, teams setting up a semantic type scale for a design system, anyone reviewing a PR for typography issues and wanting a severity-rated report format instead of a scattered comment list

Example

"Review this component for typography issues" → The rendered page checked at real content lengths (not just the code) for wrapping, widows, and truncation, findings grouped by which principle they violate with severity ratings and file:line locations, a before/after CSS fix for each — a heading missing text-wrap: balance, a price display missing tabular-nums, a 14px input missing the mobile zoom fix — and a final Block/Approve verdict based on whether any HIGH-severity issue remains

CLAUDE.md Template

New here? 3-minute setup guide → | Already set up? Copy the template below.

# Better Typography

Web typography that comes with the craft: type scale, spacing, sizing, variable fonts, OpenType features, wrapping, truncation, and the details that make typography feel great across a product. Typography is mostly restraint — a sensible scale, comfortable spacing, enough contrast. A label, a table cell, a marketing headline, and an article paragraph do not share one set of rules.

When reviewing, read the rendered page instead of scanning the code — bad wrapping, widows, and truncation only show up at real content lengths.

## Serve the Right Format

Use `.woff2` on the web for Brotli compression and broad support. `.woff` is a fallback for very old browsers. `.ttf` and `.otf` are desktop formats with no web compression.

## Properties Over Raw Tags

When a CSS property exists, use it: `font-weight: 650` instead of `font-variation-settings: "wght" 650`; `font-optical-sizing: auto` instead of `"opsz"`; `font-variant-numeric: tabular-nums` instead of `font-feature-settings: "tnum" 1`. Properties keep working when a non-variable fallback renders. Reserve raw variation/feature tags for custom axes (`"GRAD" 80`) and niche features (`"ss01" 1`) with no property of their own.

## Load Intended Weights and Styles

Browsers synthesize a weight or style the active family doesn't provide, distorting the real face. Load the faces the design actually uses. `font-synthesis: none` turns synthesis off but erases emphasis rather than reporting it — set it only after checking every required bold, italic, small-cap, superscript, and subscript form stays visually distinct across the fallback stack.

## Fewer Fonts, Sizes, and Weights

Rarely use more than three fonts. Weight and size define hierarchy; overusing them hurts readability fast. Pair for contrast, not similarity — a serif headline over a sans body reads as deliberate; two near-identical sans-serifs read as a mistake. Below 18px, stay at weight 400 or heavier — weights under 300 are display-only at 28px+ and disappear at text sizes.

## Use a Type Scale With Semantic Names

Define a small set of sizes and deviate from it as little as possible. Hard-coded sizes with no system behind them break down at scale. Solo, default names like `text-sm` are fine when the usage rules are clear; on a team, name sizes by use (`text-body-sm`) so the rules survive other people.

## Heading Sizes Descend With Level

Map heading levels to descending steps of the type scale so a visually subordinate heading never overpowers its parent. Adjacent levels may share a size toward the small end of the scale as long as weight or spacing keeps them distinct. Pick the semantic element for meaning, and the visual size separately in CSS.

## Line-Height by Role

Headings tighter, around 1.1. Body copy 1.5 to 1.6. Prefer unitless values so line-height scales with the font size — a fixed `24px` doesn't. Tight line-height is for short text; anything that wraps to three or more lines needs at least 1.4, even in a height-constrained row.

## Letter-Spacing by Size

Large headings often look better with slightly negative letter-spacing. Small uppercase labels need a little positive letter-spacing, or the letters feel crowded. Body copy at reading sizes needs neither.

## Cap the Measure

Long lines make it hard for the eye to find the next one. Cap long-form text around 60–75 characters per line — any unit works, as long as a cap exists and the line length lands in range.

## Wrap Deliberately

Four declarations, four jobs:

- `text-wrap: balance` distributes text evenly across lines. Use it on headings.
- `text-wrap: pretty` stops a single short word landing on the final line. Use it on descriptions.
- `overflow-wrap: break-word` where a long word, link, or ID could escape the container.
- `white-space: nowrap` on labels and badges where a line break looks broken.

Skip `balance` and `pretty` in long-form text.

## Tabular Numbers on Changing Values

Digits have different widths by default, so timers, counters, and prices shift the layout as they update. Apply `font-variant-numeric: tabular-nums` to any value that changes.

## Truncate Without Losing Content

For a single line: `text-overflow: ellipsis` with `overflow: hidden` and `white-space: nowrap`. For several lines: `line-clamp`. Truncation hides content — when the missing text matters, keep the full value reachable in a tooltip or an expanded view.

## Write Copy Naturally, Style With CSS

Store text in natural case and control presentation with `text-transform`, so a redesign never means rewriting copy. Use smart punctuation in rendered text: curly quotes in prose, straight quotes in code; an en dash for ranges (`2010–2020`); the single ellipsis character, not three periods; ` ` to hold two words together across a line break; `­` to say where a long word may break.

## Underlines From the Font

Default underlines sit wherever the browser decides. Pull position and thickness from the font's own metrics with `text-underline-position: from-font` and `text-decoration-thickness: from-font`; tune by hand with `text-decoration-thickness`, `text-underline-offset`, and `text-decoration-skip-ink`. `text-decoration-style` draws the line dotted, dashed, or wavy — a dotted underline is a common hint that a word carries extra information, like an abbreviation or a defined term. Color is the only part of a real underline that animates reliably — unless only color is animating, build the underline as a separate element instead of using `text-decoration`.

## Inputs at 16px on Mobile

iOS Safari zooms the whole page when an input's text is smaller than 16px. Two fixes hold the size at 16px and look different — ask which one the design wants: size the input up on mobile (`text-base sm:text-sm`, changing how it looks on small screens), or keep `font-size: 16px` and render the intended size with `transform: scale()`, compensating width and line-height (identical at every viewport, more code to maintain).

## Size and Contrast Floors

Start long-form body text at 16px, the browser default. Move off it only for a reason you can name: the typeface runs small, the measure is narrow, or the product is a dense professional tool. UI text can go smaller — 14px is a useful starting point for inputs and menus, 13px for captions, rarely below 12px. Inputs still need 16px on mobile. When text looks low-contrast, measure the actual rendered foreground/background pair and classify the requirement — don't change the project's colors unless asked.

## Font Smoothing on the Root

On macOS, text renders heavier than intended. Apply `-webkit-font-smoothing: antialiased` and `-moz-osx-font-smoothing: grayscale` once on the root layout, never per component. Tailwind's `antialiased` utility covers both.

## Language and Bidi Behavior

Set `lang` so browsers and assistive technology pick the right pronunciation, quotes, and hyphenation. Set `dir` at the document or at the content boundary where direction changes. Preserve digit order, and use `<bdi>` to isolate a mixed-direction value.

## Keep Useful Text Selectable

Keep text selectable by default. `::selection` can carry brand into the reading experience, as long as the selected combination stays legible. `user-select: none` belongs on a draggable or gesture-driven surface where accidental selection interferes — never across the whole interface, and never because a button label happens to be highlightable.

## Before You Finish — Common Mistakes

| Mistake | Fix |
|---|---|
| Synthesized face differs from the design | Load the real face; disable only the verified synthesis mode |
| Child heading visually overpowers its parent | Map that section's hierarchy to descending scale steps |
| Heading element picked for its default size | Choose semantics first, then set the size in CSS |
| Orphan on the last line of a paragraph | `text-wrap: pretty` |
| Lopsided two-line heading | `text-wrap: balance` |
| Justified text in an interface | `text-align: start`; reserve justify for specific editorial layouts |
| Underline cuts through descenders | `text-decoration-skip-ink: auto`, `from-font` metrics |
| Mixed-direction value renders in the wrong order | Correct `lang`/`dir`; isolate the value with `<bdi>` |
| Selection disabled across application chrome | Restore it; suppress only where it conflicts with a drag or gesture |
| Extra-info hint with no visual cue | Dotted underline via `text-decoration-style: dotted` |
| Thin/Light weight on 14px UI text | Weight 400+ below 18px; thin weights are display-only |
| `leading-none` on a three-line card description | At least 1.4 on any text that wraps to 3+ lines |

## Reviewing and Reporting

**Severity:** HIGH makes text unreadable or truncates content with no way to recover it. MEDIUM breaks the type system or the heading hierarchy. LOW is isolated polish.

**Verification without a browser:** computed size and weight for each heading level, checked descending; declared line-height and measure; truncation rules checked against realistic string lengths. **With a browser:** resize the viewport to catch wrapping, widows, and truncation at real content lengths. Report every check that couldn't be run as "Not verified."

**Report format** — group findings under the principle each violates, ordered by severity, one row per root cause listing every location it appears in:

| Severity | Location | Before | After | Why |
|---|---|---|---|---|

`Location` is `path/to/file:line`. `Why` names the principle and the user impact. End with `Block` if any HIGH finding remains, `Approve` otherwise. Never approve coverage that wasn't actually inspected — if there's nothing to report, state "No actionable typography findings" and report what was verified.

Get new playbooks like this one

One email a week with new Claude Code workflows. Free, like everything here.

No spam. Unsubscribe anytime.

README.md

What This Does

A dense, checkable reference for web typography — not a style guide of opinions, but a set of specific rules each tied to a concrete CSS property and a nameable failure mode. It covers font loading correctly (real weights instead of browser-synthesized ones, .woff2 over other formats, CSS properties like font-weight: 650 preferred over raw font-variation-settings tags so a non-variable fallback still works), a semantic type scale with heading sizes that descend by level so a child heading can never visually overpower its parent, line-height and letter-spacing rules by content role, and the four text-wrap/overflow-wrap/white-space declarations each doing one specific job — balance for headings, pretty to stop an orphaned word on a description's last line, nowrap for labels that shouldn't break.

It closes with two things that make it more than a reference: a table of twelve specific, common mistakes mapped directly to their fixes (a thin font weight on 14px UI text, leading-none on a three-line card description, justified text in application UI), and a structured review-report format with severity ratings (HIGH blocks, MEDIUM breaks the type system, LOW is polish), an explicit verification method for reviewing with or without a browser, and a rule against approving coverage that wasn't actually inspected. It also scopes itself honestly against neighboring concerns — word choice belongs to a writing skill, semantic heading structure to an accessibility skill, RTL layout to a layout skill, contrast measurement to a color skill — this one owns specifically how text renders, wraps, and behaves.


Quick Start

Step 1: Create a Project Folder

mkdir better-typography && cd better-typography

Step 2: Download the Template

Click Download above, then:

mv ~/Downloads/CLAUDE.md ./

Step 3: Review or Fix Typography

claude

Ask Claude to review a component or page for typography issues, or to fix a specific problem — bad wrapping, an inconsistent type scale, a mobile input zoom bug. For a review, it will check the rendered output at real content lengths and produce a severity-rated report with file:line locations and before/after fixes.


Tips & Best Practices

  • Always review typography by looking at the rendered page, not just the code — widows, orphans, and truncation only become visible at real content lengths, and code alone won't reveal them.
  • Apply -webkit-font-smoothing: antialiased once on the root layout, never per-component — a per-component approach produces inconsistent text weight across the page on macOS.
  • When choosing between the two 16px-mobile-input fixes (sizing up the input vs. transform: scale()), ask which visual tradeoff the design actually wants rather than defaulting to one — they look meaningfully different on small screens.

Limitations

  • Deliberately scoped to typography rendering and behavior — word choice, semantic heading structure, RTL/logical-property layout, and contrast measurement are explicitly out of scope and belong to neighboring accessibility, writing, layout, and color skills.
  • The review-report format assumes a codebase where typography lives in inspectable CSS/Tailwind classes; a heavily inline-styled or CSS-in-JS-obfuscated codebase may need more manual tracing to locate each finding's file:line.
  • Recommendations assume a standard web rendering context — a canvas-rendered or WebGL text layer won't benefit from CSS-level fixes like text-wrap or font-variant-numeric.

$Related Playbooks

Developer Tools

Automated PR Code Review

High-signal automated PR review using parallel agents with a validation pass that filters out false positives before anything gets posted.

10 minutes
Advanced
Developer Tools

Auto Repo Setup

Turn Claude Code into an environment doctor that diagnoses, fixes, and verifies a codebase so non-technical teammates can run it without understanding the stack.

10 minutes
Intermediate
Developer Tools

Artifacts Builder

Create elaborate multi-component HTML artifacts using React, Tailwind CSS, and shadcn/ui components with professional bundling.

15 minutes
Advanced
Developer Tools

Cinematic GSAP + Lenis Motion System

Build a connected, premium web motion language with GSAP, ScrollTrigger, and Lenis smooth scroll — a full token system for eases/durations/staggers, Lenis-driven-by-the-GSAP-ticker setup, a reusable data-attribute markup API for text reveals and parallax, and reduced-motion handling built in from the start, not bolted on after.

15 minutes
Advanced
Developer Tools

Contribution PR Review

Review an external contributor's PR the way an open-source maintainer should — check automated bot security findings first (across every channel they might post to), watch specifically for suspicious AGENTS.md or workflow-permission changes, scale size and test-coverage expectations by contributor experience, and separate intent/scope alignment from code quality that bots already covered.

10 minutes
Intermediate
Developer Tools

Datadog Error Triage to PR

Take Datadog Error Tracking issues from raw occurrence counts to a reviewable, tested PR — a five-class bug taxonomy that filters out infra noise and deploy-skew before ranking, a recency premium that catches fresh regressions early, a mandatory approval gate before any fix is written, and a test-first fix workflow that generalizes past the one reported occurrence.

10 minutes
Advanced
Developer Tools

Documentation Review System

Mode-routed documentation review that diagnoses a page against its doc type and seven quality dimensions before touching a sentence, then picks the intervention level — maintenance, improve, rewrite, author, or strategy — that actually matches what's wrong.

10 minutes
Intermediate
Developer Tools

Designer's Figma-to-Production Workflow

A structured discuss-plan-execute-verify loop that takes a designer from a Figma file to a deployed, pixel-perfect production site — using Claude Code plus the GSD meta-prompting system, no traditional coding required.

20 minutes
Intermediate
Developer Tools

Distinctive Frontend Design

Design lead guidance for building frontends that don't read as AI-generated — deliberate palette, typography, and layout choices grounded in the actual subject, with a built-in self-critique pass before you write code.

5 minutes
Intermediate
Developer Tools

Continue Claude Work

Recover actionable context from local .claude session artifacts and continue interrupted work — without running claude --resume — by inspecting history first.

10 minutes
Intermediate
Developer Tools

Network Issue Debugging

Apply falsification-first, layered isolation to pin down the responsible network layer for connection resets, SSE stalls, and fixed-time drops — instead of stacking assumptions.

20 minutes
Advanced
Developer Tools

Docs Cleaner

Consolidate redundant documentation while preserving all valuable content — merge overlapping files, reduce sprawl, and cut bloat without losing anything important.

10 minutes
Beginner

Browse all Developer Tools playbooks →