
Typography Audit
- 479 installs
- 74 repo stars
- Updated August 5, 2026
- mblode/agent-skills
typography-audit is an agent skill that lints web UI typography against 90 rules for hierarchy, font pairing, and responsive type consistency for developers who need pre-release typography QA.
About
typography-audit is a Claude Code agent skill from mblode/agent-skills that audits web projects against 90 typography rules across ten categories, covering punctuation, font stacks, sizing, spacing, OpenType features, hierarchy, and typeface pairing. The skill loads rule categories progressively, prioritizes critical punctuation and font setup problems over medium polish items, and reports findings with file locations and concrete CSS fixes instead of redesign suggestions. Developers reach for typography-audit when reviewing CSS typography systems, validating font stack configuration, or catching common web type errors such as straight quotes, faux italics, and font-weight bold fakes before merge or release. typography-audit complements ui-audit surface typography sweeps by going deeper on pairing, brand identity, and display type while staying honest about scope: it audits existing work against best practices rather than inventing new scales. Install with npx skills add https://github.com/mblode/agent-skills --skill typography-audit and invoke during design QA or pre-release frontend reviews.
- Type scale and hierarchy checks
- Readability and contrast review
- Font pairing consistency
- Responsive typography audit
- Actionable remediation list
Typography Audit by the numbers
- 479 all-time installs (skills.sh)
- +50 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #607 of 1,880 Design & UI/UX skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/mblode/agent-skills --skill typography-auditAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 479 |
|---|---|
| repo stars | ★ 74 |
| Last updated | August 5, 2026 |
| Repository | mblode/agent-skills ↗ |
How do you audit web typography before release?
Audit live or in-progress UIs for typography scale, hierarchy, readability, font pairing, and responsive type consistency before release.
Who is it for?
Frontend developers polishing CSS typography systems, font stacks, and responsive type scales before a UI ships.
Skip if: Developers who need a new typography scale designed from scratch rather than linting existing CSS against best-practice rules.
When should I use this skill?
User asks to audit typography, check font hierarchy, review type scale consistency, or catch web type errors before merge.
What you get
Prioritized typography findings with file paths, impact ratings, and concrete CSS or font-stack fixes across ten rule categories.
- Prioritized typography finding report
- Per-file CSS fix recommendations
By the numbers
- Covers 90 typography rules across 10 categories
- Prioritizes critical punctuation and font-stack issues over medium polish items
Files
Typography Audit
89 rules across 10 categories for web typography quality. Focuses on concrete issues with concrete fixes.
Audit Workflow
Copy and track this checklist during the audit:
Audit progress:
- [ ] Step 1: Scope changed surfaces and select relevant categories
- [ ] Step 2: Run CRITICAL checks (punctuation, font setup)
- [ ] Step 3: Run HIGH checks (sizing, spacing)
- [ ] Step 4: Run MEDIUM+ checks for remaining categories in scope
- [ ] Step 5: Report findings with file:line and concrete fixes1. Audit only changed files unless a full sweep is requested. 2. Scan CSS for font-family, @font-face, font-feature-settings, and sizing/spacing properties to identify relevant categories. 3. Load rule files progressively by category prefix — read only what applies. 4. Prioritize CRITICAL and HIGH findings before medium-priority polish. 5. After fixes, rerun the relevant rules before finalizing.
Rule Categories by Priority
| Priority | Category | Impact | Prefix | Rules |
|---|---|---|---|---|
| 1 | Punctuation & Special Characters | CRITICAL | punct- | 12 |
| 2 | Font Selection & Weights | CRITICAL | font- | 10 |
| 3 | Sizing & Measure | HIGH | size- | 7 |
| 4 | Spacing & Rhythm | HIGH | spacing- | 10 |
| 5 | OpenType Features | MEDIUM-HIGH | opentype- | 8 |
| 6 | Hierarchy & Scale | MEDIUM-HIGH | hierarchy- | 8 |
| 7 | Alignment & Layout | MEDIUM | layout- | 8 |
| 8 | Typeface Pairing | MEDIUM | pairing- | 10 |
| 9 | Brand & Identity | LOW-MEDIUM | brand- | 8 |
| 10 | Display & Headlines | LOW-MEDIUM | display- | 8 |
Quick Reference
Read only what is needed for the current audit scope:
- Category map and impact rationale:
rules/_sections.md - Rule-level guidance and examples:
rules/<prefix>-*.md
Example rule files:
rules/punct-smart-quotes.md
rules/font-true-styles.md
rules/size-line-height.mdEach rule file contains:
- Why the rule matters
- Incorrect example
- Correct example
Review Output Contract
Report findings in this format:
## Typography Audit Findings
### path/to/file.css
- [CRITICAL] `punct-smart-quotes`: Straight quotes used in heading text.
- Fix: Replace `"` with `“`/`”` entities.
### path/to/clean-file.css
- ✓ pass- Group findings by file.
- Use
file:linewhen line numbers are available. - State issue and propose a concrete fix.
- Include clean files as
✓ pass.
Gotchas
- Don't load all 89 rules up front — load progressively by category prefix in scope.
- Don't report findings without
file:lineand a concrete fix — incomplete findings waste the reader's time. - Don't audit unchanged files when a full sweep wasn't requested.
- Don't treat MEDIUM/LOW polish as equal priority to CRITICAL punctuation or font-setup issues — order findings by impact.
- Don't suggest typography redesigns (new font pairings, new scales) in an audit. Audits report issues with fixes; redesigns are a separate ask.
- Don't flag OpenType features as missing without checking whether the loaded font actually ships them.
Sections
This file defines all sections, their ordering, impact levels, and descriptions. The section ID (in parentheses) is the filename prefix used to group rules.
---
1. Punctuation & Special Characters (punct)
Impact: CRITICAL Description: Incorrect punctuation is the most visible sign of amateur typography. Smart quotes, proper dashes, and correct special characters are non-negotiable for professional work.
2. Font Selection & Weights (font)
Impact: CRITICAL Description: Choosing appropriate typefaces with correct weights, true italics, and proper @font-face setup is the foundation of all web typography.
3. Sizing & Measure (size)
Impact: HIGH Description: Body text size, line length (measure), and line height are the three most important parameters for readable body text.
4. Spacing & Rhythm (spacing)
Impact: HIGH Description: Margins, paragraph spacing, letterspacing, and word spacing control the overall rhythm and breathing room of typeset text.
5. OpenType Features (opentype)
Impact: MEDIUM-HIGH Description: Enabling kerning, ligatures, small caps, and figure styles via CSS font-feature-settings unlocks the full potential of quality fonts.
6. Hierarchy & Scale (hierarchy)
Impact: MEDIUM-HIGH Description: Size contrast, weight variation, and consistent heading levels create scannable, structured content.
7. Alignment & Layout (layout)
Impact: MEDIUM Description: Text alignment, justification, list formatting, hanging punctuation, and widow/orphan control affect page-level readability.
8. Typeface Pairing (pairing)
Impact: MEDIUM Description: Combining typefaces using genre-matching, contrast principles, and superfamilies creates visual harmony.
9. Brand & Identity (brand)
Impact: LOW-MEDIUM Description: Consistent type usage, brand capitalization, licensing, and cross-medium coherence establish typographic identity.
10. Display & Headlines (display)
Impact: LOW-MEDIUM Description: Display cuts, swashes, drop caps, and headline-specific OpenType features add polish to large type.
Rule Title Here
Brief explanation of the rule and why it matters for readability, professionalism, or user experience.
Incorrect (description of what's wrong):
/* Bad CSS example */
body {
font-feature-settings: normal;
}Correct (description of what's right):
/* Good CSS example */
body {
font-feature-settings: "kern", "liga", "clig", "calt";
}Reference: Link to documentation or resource
Choose Consistent Brand Capitalization
Pick a consistent capitalization style for your brand name and enforce it everywhere. Prefer a single word with an initial capital (e.g., "Typewolf" not "TypeWolf" or "TYPEWOLF"). Avoid all-caps brand names in running text \u2014 they disrupt reading flow. Avoid mid-word capitals unless essential to the brand.
Drop ".com" from brand names in prose. Enforce the chosen style across all media, documentation, and code.
Incorrect (inconsistent capitalization):
<p>Welcome to TypeWolf.com</p> <!-- .com, mid-word cap -->
<p>TYPEWOLF is a typography resource.</p> <!-- all caps in prose -->
<p>Visit typewolf for inspiration.</p> <!-- lowercase -->Correct (consistent style):
<p>Welcome to Typewolf</p>
<p>Typewolf is a typography resource.</p>
<p>Visit Typewolf for inspiration.</p>Use Color Intentionally in Typography
Use color to create hierarchy and reinforce brand identity. Ensure sufficient contrast between text and background (WCAG AA minimum). Avoid pure black (#000) on pure white (#fff) \u2014 the extreme contrast causes eye strain. Use slightly tinted blacks and off-whites for a more refined, readable experience.
Incorrect (pure black on pure white, no brand color):
body {
color: #000000;
background: #ffffff;
}Correct (tinted black, brand-informed palette):
:root {
--text-primary: #1a1a2e; /* dark navy, not pure black */
--text-secondary: #4a4a68; /* lighter for secondary text */
--bg-primary: #fafaf8; /* warm off-white */
--accent: #2d5f8a; /* brand blue for links/highlights */
}
body {
color: var(--text-primary);
background: var(--bg-primary);
}
a {
color: var(--accent);
}Subtle color tints (warm, cool, brand-hued) create distinctive atmosphere without sacrificing readability. Keep contrast ratios above 4.5:1 for body text and 3:1 for large text.
Keep Type Consistent Across Media
Use the same typefaces across web, print, and app. License fonts for each medium separately as needed. Consistency builds recognition \u2014 readers subconsciously associate your type choices with your brand.
Incorrect (different faces per medium):
Web: font-family: 'Open Sans', sans-serif;
Print: Myriad Pro (from Adobe)
App: San Francisco (system font)Correct (same family everywhere):
Web: font-family: 'Inter', sans-serif;
Print: Inter (desktop license)
App: Inter (embedded font)When a preferred font is unavailable on a platform, use the closest metrically compatible alternative. Document the type system and its platform-specific variations.
Handle Dark Backgrounds Carefully
Light text on dark backgrounds (reversed type) is harder to read for long passages. Use off-white text (not pure white) on dark backgrounds to reduce glare. Reserve reversed type for short sections, headers, and UI elements \u2014 not for extended body text.
Limit long light-on-dark text blocks. If the design requires dark mode, increase font weight slightly and loosen line height.
Incorrect (pure white on pure black, extended body):
.dark-section {
background: #000;
color: #fff; /* harsh contrast, eye strain */
}
.dark-section .article {
/* Long body text in reversed type \u2014 hard to read */
}Correct (off-white on dark, limited reversed body text):
.dark-section {
background: #1a1a2e;
color: #e8e8ed; /* off-white, reduces glare */
}
.dark-section body {
font-weight: 450; /* slightly heavier for dark backgrounds */
line-height: 1.6; /* slightly looser */
}Test dark backgrounds at different screen brightness levels and in various lighting conditions.
Protect Brand Typographic Equity
Once you establish core type choices, stick with them. Frequent changes erode brand recognition. Document your typographic system (typefaces, sizes, weights, colors, spacing) and require adherence across all touchpoints.
Add at least one distinctive typographic move per project \u2014 but make it a deliberate part of the system, not a one-off deviation.
Guidelines:
- Document type choices in a brand/design system
- Create CSS custom properties or design tokens for all type styles
- Review new designs for adherence to the type system
- Allow evolution but require justification for changes
- Keep reference links to foundry pages and license documentation
/* Document the system in code */
:root {
/* Primary typeface: Inter (licensed for web) */
--font-primary: 'Inter', -apple-system, sans-serif;
/* Heading typeface: Fraunces (Google Fonts, SIL OFL) */
--font-heading: 'Fraunces', Georgia, serif;
/* Monospace: JetBrains Mono */
--font-mono: 'JetBrains Mono', monospace;
}Make Body Text Distinctive
Go beyond default system fonts and generic web fonts. Your body text should be distinctive enough that a reader could identify your brand from a paragraph alone. Experiment with less common typefaces, consider custom fonts, and pair font choices with deliberate size, weight, and color decisions.
Incorrect (generic, indistinguishable body text):
body {
font-family: Arial, sans-serif;
font-size: 16px;
color: #333;
/* Looks like every other website */
}Correct (considered, distinctive body text):
body {
font-family: 'Söhne', sans-serif;
font-size: 18px;
color: #1a1a2e;
line-height: 1.55;
font-feature-settings: "kern", "liga", "calt";
}Add at least one distinctive typographic move per project \u2014 a unique typeface choice, unusual weight, or specific OpenType feature that gives the typography personality.
License Fonts Properly
Every font has a license that specifies permitted uses. Web fonts, desktop fonts, and app-embedded fonts often require separate licenses. Never use pirated fonts. Buy full families when budget allows, or use high-quality open-source alternatives (Google Fonts, Adobe Fonts with Creative Cloud, etc.).
Checklist:
- Verify the license covers web use (self-hosted or CDN)
- Check pageview limits on commercial web font licenses
- Confirm embedding rights for apps or PDFs
- Ensure the license covers the number of domains/projects
- Keep license documentation accessible to the team
Open-source options:
- Google Fonts \u2014 free, SIL Open Font License
- Adobe Fonts \u2014 included with Creative Cloud subscription
- Font Squirrel \u2014 curated free fonts with verified licenses
- fonts.bunny.net \u2014 privacy-friendly Google Fonts alternative
Commercial foundries:
- Hoefler&Co, Commercial Type, Klim, Grilli Type, Dinamo, Colophon
When budget is limited, start with open-source and upgrade to commercial fonts as the brand matures.
Choose Logo Typeface Based on Specific Letters
When selecting a typeface for a logo or wordmark, choose based on the specific letters in the brand name, not on overall typeface aesthetics. Favor typefaces with distinctive glyphs for the letters you actually use. An "A" or "g" that looks generic in one typeface may be striking in another.
Process:
1. Type the brand name in 20\u201330 candidate typefaces 2. Focus on the most prominent letters in the name 3. Look for distinctive, memorable letterforms 4. Test at both large and small sizes 5. Verify the typeface license covers logo use
Correct (chosen for specific letter beauty):
/* "Agatha" \u2014 chosen because Didot's 'A' and 'g' are distinctive */
.logo {
font-family: 'Didot', serif;
font-size: 2rem;
letter-spacing: 0.05em;
}Use swashes, discretionary ligatures, and stylistic alternates sparingly in logos for memorability. Check italic variants for swash characters \u2014 they are often stored in separate font files or accessible via OpenType features.
Use Display Cuts Only at Large Sizes
Display cuts (Display, Banner, Headline, Poster, Titling) have finer details, higher contrast, and tighter spacing optimized for large sizes. They become illegible at body text sizes. Conversely, text cuts look clunky at large sizes.
Enable discretionary ligatures and swashes for display sizes, not for body text.
Incorrect (display cut at body size):
body {
font-family: 'Garamond Display', serif;
font-size: 16px; /* too small for display cut */
}Correct (display cut for headlines, text cut for body):
body {
font-family: 'Garamond Text', serif;
font-size: 18px;
}
h1 {
font-family: 'Garamond Display', serif;
font-size: 48px;
font-feature-settings: "kern", "liga", "dlig", "calt";
}With variable fonts that have an opsz axis, this happens automatically via font-optical-sizing: auto.
Implement Drop Caps or Initial Caps
Drop caps (large initial letter spanning multiple lines) and raised initial caps (large first letter on the baseline) add editorial polish. Use CSS initial-letter (supported in Safari and Chrome) or a JS fallback.
Small caps after the drop cap create an elegant transition to body text.
CSS drop cap:
.article > p:first-of-type::first-letter {
initial-letter: 3; /* spans 3 lines */
font-weight: 700;
margin-right: 0.1em;
color: var(--accent);
}Fallback with float (broader support):
.article > p:first-of-type::first-letter {
float: left;
font-size: 3.5em;
line-height: 0.8;
padding-right: 0.1em;
font-weight: 700;
color: var(--accent);
}Keep drop caps simple. Overly decorative drop caps work in print but can look heavy on screen. Use them at the start of articles or major sections, not on every paragraph.
Break the Grid Intentionally
Oversized type, full-bleed images, and pull quotes that extend beyond the text column create visual interest and break monotony. The key is intention \u2014 break the grid as a deliberate design choice while keeping the overall structure intact.
Incorrect (accidental grid break, sloppy layout):
blockquote {
width: 120%; /* overflows container randomly */
}Correct (intentional grid break with structure):
.article {
max-width: 65ch;
margin: 0 auto;
}
/* Pull quote breaks the grid intentionally */
.pull-quote {
max-width: 85ch;
margin: 3rem -5rem;
padding: 2rem;
font-size: 1.5em;
line-height: 1.35;
font-style: italic;
border-left: 3px solid var(--accent);
}
/* Full-bleed image breaks the grid */
.full-bleed {
width: 100vw;
margin-left: calc(-50vw + 50%);
}
@media (max-width: 768px) {
.pull-quote {
margin: 2rem 0;
}
}Allow grid deviations when they serve the content. Give typography room to breathe. Use whitespace as a design element.
Enable Extra OpenType Features for Headlines
Headlines benefit from OpenType features that would be distracting in body text. Enable discretionary ligatures (dlig), swashes (swsh), and contextual alternates (calt) in addition to the standard body features.
Body features (always on):
body {
font-feature-settings: "kern", "liga", "clig", "calt";
}Headline features (extended set):
h1, h2 {
font-feature-settings: "kern", "liga", "clig", "calt", "dlig", "swsh";
}Enable swashes on specific letters if the font supports indexed swash variants:
.headline .decorative-letter {
font-feature-settings: "swsh" 2; /* second swash variant */
}Only enable features the font actually supports. Unsupported feature tags are silently ignored but add unnecessary CSS weight.
Tighten Spacing for Large Headlines
Large display text needs tighter line height and letter-spacing than body text. At 48px+, the default line height creates excessive vertical gaps, and default tracking feels too loose.
Display cuts may need less tracking adjustment than text cuts used at large sizes.
Incorrect (body line-height applied to headlines):
h1 {
font-size: 64px;
line-height: 1.5; /* way too much space between lines */
}Correct (tightened for display):
h1 {
font-size: 64px;
line-height: 1.05;
letter-spacing: -0.02em;
}
/* Responsive scaling */
@media (max-width: 768px) {
h1 {
font-size: 36px;
line-height: 1.15;
letter-spacing: -0.01em;
}
}Use negative letter-spacing carefully. Test multi-line headlines to ensure letters do not overlap or collide. Descenders on one line should not touch ascenders on the next.
Use Large Type as a Design Element
Huge type can anchor an entire design. If you are loading a web font, use it to its full advantage at large sizes where its character is most visible. Small web fonts are indistinguishable from system fonts, wasting bandwidth.
Scale large type down on smaller screens. Large letters can also be screened back and layered as abstract background elements.
Incorrect (web font only at body size):
body {
font-family: 'Playfair Display', serif;
font-size: 16px;
/* Wasting a beautiful display face at tiny size */
}Correct (web font showcased at large size):
.hero-headline {
font-family: 'Playfair Display', serif;
font-size: clamp(3rem, 8vw, 8rem);
line-height: 1.05;
letter-spacing: -0.02em;
}
body {
font-family: 'Source Serif Pro', serif; /* text face for body */
font-size: 18px;
}On smaller screens, tone down huge type so readers do not scroll through three screen heights to read a headline.
Style a Lead Paragraph
A lead (lede) paragraph \u2014 the opening paragraph of an article \u2014 can be styled larger, lighter, or in a different typeface to draw readers in. Keep the lead short (1\u20133 sentences). It acts as a bridge between the headline and the body.
Incorrect (no lead, body starts abruptly):
<h1>The Art of Typography</h1>
<p>Typography is the art and technique of arranging type...</p>
<!-- Same size as every other paragraph -->Correct (styled lead paragraph):
.article > p:first-of-type {
font-size: 1.25em;
line-height: 1.5;
color: var(--text-secondary);
}<h1>The Art of Typography</h1>
<p>Good typography is invisible. The reader should never notice the type,
only the content it conveys.</p>
<p>Typography is the art and technique of arranging type...</p>The lead can use a larger size, lighter weight, or different color. Start articles with the lead, then transition to body text with initial small caps or a drop cap for extra polish.
Use Swashes and Alternates Sparingly
Swash characters and stylistic alternates add flair to logos and display text but become distracting in body copy. Use them on specific letters for memorability. Look for swashes in italic variants or separate font files.
Incorrect (swashes enabled globally):
body {
font-feature-settings: "swsh", "salt"; /* swashes in body text */
}Correct (swashes only on display elements):
body {
font-feature-settings: "kern", "liga", "calt";
}
.logo {
font-feature-settings: "kern", "liga", "swsh";
}
/* Enable swash on a specific letter */
.logo .initial {
font-feature-settings: "swsh" 1;
}Use font-feature-settings: "ss01" through "ss20" to access numbered stylistic sets, which provide curated alternate glyph collections.
Choose Body Fonts for Legibility
Select body text fonts with low stroke contrast, large x-height, open apertures, and large counters. These traits maximize readability at small sizes. Prefer text-cut faces designed for body use. Avoid overly large x-heights, which reduce the distinctiveness of ascenders and descenders.
Humanist and modern sans-serifs work well if they meet these legibility traits. Geometric sans-serifs are generally weaker for body text.
Incorrect (display face used for body):
body {
font-family: 'Playfair Display', serif; /* high contrast, display face */
font-size: 16px;
}Correct (text-optimized face for body):
body {
font-family: 'Source Serif Pro', serif; /* low contrast, large x-height */
font-size: 18px;
}Look for these qualities when evaluating a body typeface:
- Low stroke contrast \u2014 even thickness between thick and thin strokes
- Large x-height \u2014 lowercase letters are tall relative to capitals
- Open apertures \u2014 openings in letters like c, e, s are wide
- Large counters \u2014 enclosed spaces in letters like o, d, b are spacious
Use Condensed Faces for Headlines Only
Condensed and extra-condensed typefaces are designed for headlines and tight spaces where you need to control line breaks. Never use condensed faces for body copy \u2014 the narrow letterforms reduce readability at small sizes.
Extended variants are rarely needed but can be used for stylistic effect at large sizes. When swapping between normal and condensed widths, re-tune all spacing values.
Incorrect (condensed face for body):
body {
font-family: 'Roboto Condensed', sans-serif;
font-size: 16px;
}Correct (condensed for headlines, normal for body):
body {
font-family: 'Roboto', sans-serif;
font-size: 18px;
}
h1 {
font-family: 'Roboto Condensed', sans-serif;
font-size: 48px;
letter-spacing: -0.01em;
}Condensed faces can also work for tight UI labels (navigation, badges, tags) where space is limited.
When switching fonts on a project, if the new font is metrically compatible with the old one, the swap is easier. Always re-tune size, line-height, letter-spacing, and padding after a font change.
Correct @font-face Declarations
Each @font-face declaration must map to the same font-family name with distinct font-weight and font-style values. This lets the browser automatically select the correct font file when CSS applies font-weight: bold or font-style: italic.
Incorrect (separate family names per weight):
@font-face {
font-family: 'MyFont-Regular';
src: url('/fonts/MyFont-Regular.woff2') format('woff2');
}
@font-face {
font-family: 'MyFont-Bold';
src: url('/fonts/MyFont-Bold.woff2') format('woff2');
}
h1 { font-family: 'MyFont-Bold'; } /* manual switching */
p { font-family: 'MyFont-Regular'; }Correct (single family, weight/style differentiated):
@font-face {
font-family: 'MyFont';
font-weight: 400;
font-style: normal;
src: url('/fonts/MyFont-Regular.woff2') format('woff2');
}
@font-face {
font-family: 'MyFont';
font-weight: 700;
font-style: normal;
src: url('/fonts/MyFont-Bold.woff2') format('woff2');
}
body { font-family: 'MyFont', sans-serif; }
h1 { font-weight: 700; } /* browser selects correct file */
p { font-weight: 400; }Define Strong Fallback Font Stacks
Every font-family declaration needs a carefully chosen fallback stack. Include likely-installed system fonts that match the primary font's metrics and style. Test with fallbacks active to catch layout shifts. Ensure accent and special character support.
Incorrect (no meaningful fallback):
body {
font-family: 'Libre Baskerville';
}Correct (robust fallback stack):
body {
font-family: 'Libre Baskerville', 'Georgia', 'Times New Roman', serif;
}
/* Sans-serif example */
h1 {
font-family: 'Inter', 'Helvetica Neue', 'Arial', sans-serif;
}Use size-adjust, ascent-override, and descent-override in @font-face for fallbacks to minimize layout shift (CLS):
@font-face {
font-family: 'Inter Fallback';
src: local('Arial');
size-adjust: 107%;
ascent-override: 90%;
descent-override: 22%;
}Avoid over-subsetting fonts, which can cause empty glyph boxes for characters outside the subset range.
Reserve Monospaced Fonts for Short Blocks
Monospaced fonts reduce readability in long text because uniform letter widths disrupt natural reading rhythm. Use them only for code blocks, terminal output, and short stylistic elements. Never set body copy in a monospaced face.
Incorrect (monospace for body text):
body {
font-family: 'Fira Mono', monospace;
font-size: 16px;
}Correct (proportional for body, mono for code):
body {
font-family: 'Inter', sans-serif;
font-size: 18px;
}
code, pre {
font-family: 'Fira Code', 'Fira Mono', monospace;
font-size: 0.9em;
}Brief monospaced sections (e.g., a stylistic data readout or a hero element) are acceptable as a design choice, but keep them short.
Use Text and Display Optical Sizes Correctly
Many type families offer optical size variants: Text, Display, Headline, and Caption. Each is optimized for a specific size range. Display cuts have finer details, tighter spacing, and higher contrast suited for large sizes. Text cuts have more open spacing, larger x-height, and lower contrast for body readability.
Never use Display/Headline cuts for body copy. Never use Caption cuts for web body text (they are designed for very small print sizes).
Incorrect (display cut used at body size):
body {
font-family: 'Garamond Display', serif; /* designed for 24px+ */
font-size: 16px; /* too small for display cut */
}Correct (text cut for body, display cut for headings):
body {
font-family: 'Garamond Text', serif; /* optimized for 12-18px */
font-size: 18px;
}
h1 {
font-family: 'Garamond Display', serif; /* optimized for 24px+ */
font-size: 48px;
}Variable fonts with an opsz axis handle this automatically:
body {
font-family: 'Garamond VF', serif;
font-optical-sizing: auto; /* browser adjusts based on font-size */
}Evaluate Font Quality Before Use
Test a font's quality by examining its kerning with word samples before committing. Poor kerning (uneven letter spacing) indicates low quality. Prefer fonts from reputable foundries and distributors. Never use pirated fonts.
How to evaluate:
1. Set the font in a paragraph of body text and look for uneven spacing between letter pairs (T+y, A+V, W+a are common problem pairs) 2. Check that punctuation is well-positioned relative to letters 3. Verify the font includes all needed glyphs (accents, special characters, figure styles) 4. Confirm the license covers web use
Reputable sources:
- Commercial: Adobe Fonts, Hoefler&Co, Commercial Type, Klim, Grilli Type
- Open source: Google Fonts, Font Squirrel (with verification), fonts.bunny.net
- Variable font collections: v-fonts.com
License fonts properly for each use case (web, desktop, app). Buy complete families when budget allows, or use high-quality open-source alternatives.
Apply Antialiased Font Smoothing on macOS
On macOS, the browser defaults to subpixel antialiasing, which renders text slightly heavier and thicker than intended. Setting -webkit-font-smoothing: antialiased switches to grayscale antialiasing, producing thinner, crisper text — especially visible on light-on-dark surfaces and smaller text sizes.
Apply it once at the layout root so it cascades to all text elements. Adding it per-component causes inconsistent rendering across the interface.
Incorrect (applied per-component or not at all):
.button {
-webkit-font-smoothing: antialiased; /* inconsistent: only buttons get crisp text */
}Correct (applied at root):
body {
-webkit-font-smoothing: antialiased;
}Tailwind:
<body class="antialiased">Apply to the outermost layout element — <body> or the root layout wrapper — so every text element in the app inherits it.
Note: this only affects macOS rendering. It has no effect on Windows or Linux, so it is safe to apply unconditionally.
Load Real Italic and Bold Styles
Load actual regular, italic, bold, and bold italic font files to prevent the browser from synthesizing faux styles. Faux italics are mechanically slanted and lack the redesigned letterforms of true italics. Faux bold adds artificial weight that looks blotchy.
Prefer true italics (redesigned letterforms) over obliques (slanted roman). Use WOFF2 format and consider variable fonts to reduce file count.
Incorrect (single font file, browser synthesizes styles):
@font-face {
font-family: 'MyFont';
src: url('/fonts/MyFont-Regular.woff2') format('woff2');
}
/* Browser will synthesize italic and bold */
em { font-style: italic; }
strong { font-weight: bold; }Correct (all four styles loaded explicitly):
@font-face {
font-family: 'MyFont';
font-weight: 400;
font-style: normal;
src: url('/fonts/MyFont-Regular.woff2') format('woff2');
}
@font-face {
font-family: 'MyFont';
font-weight: 400;
font-style: italic;
src: url('/fonts/MyFont-Italic.woff2') format('woff2');
}
@font-face {
font-family: 'MyFont';
font-weight: 700;
font-style: normal;
src: url('/fonts/MyFont-Bold.woff2') format('woff2');
}
@font-face {
font-family: 'MyFont';
font-weight: 700;
font-style: italic;
src: url('/fonts/MyFont-BoldItalic.woff2') format('woff2');
}If bandwidth is constrained, drop the least-used style (usually bold italic) rather than relying on faux rendering.
Prefer WOFF2 and Variable Fonts
Use WOFF2 as the primary web font format \u2014 it offers the best compression (30-50% smaller than WOFF). Variable fonts bundle multiple weights and styles into a single file, reducing HTTP requests and total file size.
Incorrect (legacy formats, multiple files):
@font-face {
font-family: 'MyFont';
font-weight: 400;
src: url('/fonts/MyFont-Regular.ttf') format('truetype');
}
@font-face {
font-family: 'MyFont';
font-weight: 700;
src: url('/fonts/MyFont-Bold.ttf') format('truetype');
}Correct (WOFF2 with variable font):
@font-face {
font-family: 'MyFont';
font-weight: 100 900;
font-style: normal;
font-display: swap;
src: url('/fonts/MyFont-Variable.woff2') format('woff2-variations');
}
body { font-weight: 400; }
h1 { font-weight: 700; }
.light { font-weight: 300; }Use font-display: swap to prevent invisible text during loading.
Use Appropriate Body Text Weight
Set body text weight to regular (400) or book/medium (500). Avoid ultra-light or thin weights for longform reading \u2014 they lack sufficient contrast against the background, especially on low-resolution screens. Use heavier weights only at large display sizes.
Test rendering across platforms: fonts often appear thinner on macOS than on Windows.
Incorrect (ultra-light body text):
body {
font-family: 'Helvetica Neue', sans-serif;
font-weight: 200; /* ultra-light, hard to read at body sizes */
font-size: 16px;
}Correct (readable body weight):
body {
font-family: 'Helvetica Neue', sans-serif;
font-weight: 400; /* regular weight for body */
font-size: 18px;
}
/* Thin weights reserved for large display text */
.hero-title {
font-weight: 200;
font-size: 72px;
}Start Layout with Body Text
Begin every design by setting the body text: choose the typeface, set the size, line height, and measure. All other type decisions (headings, labels, captions) derive from this foundation. Starting with headings or decorative type and working backward leads to body text that feels like an afterthought.
Incorrect (starting with the hero heading):
/* Designed the flashy heading first */
h1 {
font-family: 'Playfair Display', serif;
font-size: 72px;
}
/* Then tried to make body text work around it */
body {
font-family: Arial, sans-serif; /* default, unconsidered */
font-size: 14px; /* too small, squeezed in */
}Correct (body text first, headings derived):
/* 1. Set body text foundation */
body {
font-family: 'Source Serif Pro', serif;
font-size: 18px;
line-height: 1.5;
max-width: 65ch;
}
/* 2. Derive headings from body */
h1 {
font-family: 'Source Serif Pro', serif;
font-size: 2.5rem;
line-height: 1.15;
}
h2 { font-size: 1.75rem; }
h3 { font-size: 1.25rem; }Edit copy for clarity. Remove redundant UI text. Typography is best served by well-written content.
Use Letterspaced Caps for Subheadings
Letterspaced uppercase or small caps work well for subheadings and labels. Size caps down slightly to avoid shouting \u2014 uppercase at the same size as body text feels too loud. Small caps with slight tracking create an elegant secondary heading level.
Incorrect (full-size uppercase, no spacing):
.subhead {
text-transform: uppercase;
font-size: 18px; /* same as body, feels aggressive */
}Correct (sized down, tracked uppercase):
.subhead {
text-transform: uppercase;
font-size: 0.75rem;
letter-spacing: 0.1em;
font-weight: 600;
color: var(--text-secondary);
}
/* Or small caps variant */
.subhead-sc {
font-variant-caps: all-small-caps;
letter-spacing: 0.05em;
font-size: 1rem;
}Define and Document a Type System
Define a consistent set of type styles (sizes, weights, line-heights, colors) and apply them uniformly. Avoid random one-off style changes. Document the system so all contributors follow the same conventions. Use CSS custom properties or design tokens to enforce consistency.
Incorrect (ad hoc styling throughout):
.page-a h2 { font-size: 28px; font-weight: 700; }
.page-b h2 { font-size: 24px; font-weight: 600; } /* different from page-a */
.page-c h2 { font-size: 30px; font-weight: 800; } /* different again */Correct (systematic type scale):
:root {
--font-body: 'Inter', sans-serif;
--font-heading: 'Inter', sans-serif;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.25rem;
--text-xl: 1.5rem;
--text-2xl: 2rem;
--text-3xl: 2.5rem;
--leading-tight: 1.2;
--leading-normal: 1.5;
}
h2 {
font-family: var(--font-heading);
font-size: var(--text-2xl);
line-height: var(--leading-tight);
font-weight: 700;
}Do not obsess over baseline grids on the web. Prioritize consistent font size, line height, and line length over pixel-perfect vertical alignment. Accept web fluidity.
Lighten Headings as They Grow
As headings increase in size, reduce their weight or lighten their color to maintain visual balance. A 48px bold heading can feel overwhelming. Prefer darkened brand hues over flat gray for lightened headings \u2014 gray headings feel lifeless.
Incorrect (heavy weight at all sizes):
h1 { font-size: 48px; font-weight: 900; color: #000; }
h2 { font-size: 32px; font-weight: 900; color: #000; }
h3 { font-size: 24px; font-weight: 900; color: #000; }Correct (progressive lightening):
h1 {
font-size: 48px;
font-weight: 400; /* lighter weight at large size */
color: #1a1a2e; /* slightly tinted, not pure black */
}
h2 {
font-size: 32px;
font-weight: 600;
}
h3 {
font-size: 24px;
font-weight: 700;
}Avoid heading colors that match your link color (typically blue), as readers will try to click them.
Keep Heading Levels Shallow and Descriptive
Limit heading depth to h1\u2013h3 in most contexts. Deeper levels (h4\u2013h6) often indicate content that should be restructured. Write descriptive, skimmable headings that tell the reader what the section contains \u2014 not generic labels.
Avoid heading colors that match link colors (causes confusion). Avoid all-italic headings (they feel weak).
Incorrect (deep nesting, generic headings):
<h1>Documentation</h1>
<h2>Getting Started</h2>
<h3>Prerequisites</h3>
<h4>System Requirements</h4>
<h5>Minimum Specifications</h5> <!-- too deep -->
<h6>Processor</h6> <!-- way too deep -->Correct (shallow, descriptive headings):
<h1>Documentation</h1>
<h2>Install on macOS, Linux, or Windows</h2>
<h3>System requirements</h3>
<!-- Restructure deeper content as lists or prose -->Use text-transform: uppercase or text-transform: capitalize in CSS rather than typing headings in all caps in the HTML.
Use a Modular Scale as a Guide, Not a Constraint
A modular scale (e.g., 1.25, 1.333, 1.5 ratios) provides a starting set of harmonious sizes. But typography is not always about numbers. If the scale produces a size that feels optically wrong for your typeface, break the scale. Typefaces have "sweet spots" where they look best; use those sizes even if they fall outside the scale.
Incorrect (rigid adherence to scale despite optical issues):
/* Scale: 1.25 ratio */
:root {
--text-sm: 12.8px; /* scale says 12.8, but 13px reads better */
--text-base: 16px;
--text-lg: 20px;
--text-xl: 25px; /* scale says 25, but 24px pairs better */
}Correct (scale as starting point, optical adjustments):
:root {
--text-sm: 13px; /* rounded for readability */
--text-base: 18px; /* typeface sweet spot */
--text-lg: 20px;
--text-xl: 24px; /* optically balanced with body */
--text-2xl: 32px;
--text-3xl: 48px;
}Choose body size first, then derive headings. Ensure strong size contrast between levels \u2014 sizes should be clearly different or exactly the same, never almost the same.
Ensure Strong Size Contrast Between Levels
When two elements have different roles, make their sizes clearly different. Near-equal sizes (e.g., 16px and 18px) create ambiguity \u2014 the reader cannot tell which is more important. Sizes should either be identical (same role) or noticeably different (different hierarchy levels).
Incorrect (ambiguous near-equal sizes):
h2 { font-size: 20px; }
h3 { font-size: 18px; } /* barely different from h2 */
p { font-size: 16px; } /* barely different from h3 */Correct (clear size jumps between levels):
h2 { font-size: 28px; }
h3 { font-size: 20px; } /* clearly smaller than h2 */
p { font-size: 18px; } /* clearly body text */Use at least a 20\u201325% size increase between adjacent hierarchy levels. If you cannot achieve clear differentiation with size alone, use weight, color, or caps as additional differentiators.
Build Hierarchy with Multiple Axes
Create typographic hierarchy using weight, italics, caps, color, and size \u2014 but change one axis at a time. Combining multiple changes simultaneously (bold + large + colored + caps) creates visual noise rather than clear hierarchy.
Incorrect (too many changes at once):
h2 {
font-size: 32px;
font-weight: 900;
text-transform: uppercase;
letter-spacing: 0.1em;
color: #ff0000;
/* five axes changed simultaneously */
}Correct (one or two axes per level):
/* Size + weight for primary heading */
h2 {
font-size: 28px;
font-weight: 700;
}
/* Size + caps for section label */
.section-label {
font-size: 14px;
text-transform: uppercase;
letter-spacing: 0.1em;
}
/* Style for inline emphasis */
.aside-note {
font-style: italic;
color: var(--text-secondary);
}Build hierarchy incrementally: start with size differences, then add weight contrast, then consider caps or color for specific elements.
Center-Align Text Sparingly
Center-aligned text disrupts the left reading edge that anchors the reader's eye. Use it only for intentional formal or display contexts: invitations, short hero text, or single-line captions. Never center-align more than a few lines.
When centering text, increase line height slightly to compensate for the ragged edges on both sides.
Incorrect (centered body paragraphs):
.article p {
text-align: center; /* hard to read at paragraph length */
}Correct (centered for display only, left for prose):
.article p {
text-align: left;
}
.hero-tagline {
text-align: center;
line-height: 1.6; /* slightly more than normal */
max-width: 40ch; /* keep lines short */
margin: 0 auto;
}Avoid default text-align: center on containers that may hold paragraph-length text.
Choose Hanging vs Indented Bullets
Hanging bullets (flush with the text margin) look cleaner in print but can cause alignment issues on the web. Indented bullets (text indented from the margin) often scan better because the bullets stand out from the text edge.
Avoid hanging bullets on mobile where horizontal space is limited.
Hanging bullets (bullets in the margin):
ul {
list-style-position: outside;
padding-left: 0;
margin-left: 1em; /* bullets hang in the margin */
}Indented bullets (text indented, default web behavior):
ul {
list-style-position: outside;
padding-left: 1.5em; /* text indented from left edge */
}Add vertical spacing between bullet items to improve scannability. Decide between hanging and indented based on your layout \u2014 indented is the safer default for web.
Avoid Justified Text on the Web
Justified text creates rivers of whitespace and uneven word spacing on the web, where hyphenation support is inconsistent. Only justify text when paired with strong hyphenation (hyphens: auto) and a sufficiently long line length. Never justify narrow columns.
Never use letterspacing to fill justified lines \u2014 it produces grotesque spacing.
Incorrect (justified without hyphenation):
.article {
text-align: justify;
/* no hyphenation \u2014 creates rivers and stretched words */
}Correct (left-aligned, or justified with hyphenation):
/* Preferred: left-aligned */
.article {
text-align: left;
}
/* If justified, require hyphenation and adequate measure */
.article-justified {
text-align: justify;
hyphens: auto;
-webkit-hyphens: auto;
max-width: 65ch; /* ensure long lines for justification to work */
}Browser hyphenation depends on language dictionaries. Set lang on the <html> element and test thoroughly.
Format Lists with Proper Markup and Spacing
Use semantic <ul> or <ol> elements for lists. Ensure list text does not wrap under the bullet \u2014 use proper list styling. Add vertical spacing between multi-line items so wrapped lines stay visually grouped with their bullet.
Incorrect (fake lists, text wraps under bullet):
<p>- First item that is long enough to wrap onto a second line which
goes under the bullet</p>
<p>- Second item</p>Correct (proper markup, indented text):
<ul>
<li>First item that is long enough to wrap onto a second line, which
stays indented from the bullet.</li>
<li>Second item</li>
</ul>ul {
padding-left: 1.5em;
list-style-position: outside; /* prevents wrap under bullet */
}
li {
margin-bottom: 0.5em; /* space between items */
}
/* For items with titles or multi-line content */
li + li {
margin-top: 0.75em;
}Test lists with long content at narrow viewport widths. If list item titles wrap, increase vertical padding and tighten line height so wrapped lines stay grouped.
Never Distort Type
Never stretch, squish, or skew type using CSS transforms or width adjustments. Distortion destroys the carefully designed proportions of letterforms. If you need a narrower or wider typeface, use a condensed or extended variant.
The only exception is intentional distortion for logos as an explicit design choice.
Incorrect (CSS distortion):
h1 {
font-family: 'Inter', sans-serif;
transform: scaleX(0.8); /* squished horizontally */
}
.wide-text {
transform: scaleX(1.3); /* stretched */
}Correct (use proper width variants):
h1 {
font-family: 'Inter Tight', sans-serif; /* condensed variant */
}
/* Or with a variable font width axis */
h1 {
font-family: 'Inter VF', sans-serif;
font-stretch: 85%; /* proper condensed rendering */
}Balance Layouts Optically
Optically center elements slightly above the true mathematical center \u2014 the human eye perceives the geometric center as too low. Account for overshoot in round and pointed shapes: an "O" must extend slightly beyond the baseline and cap height to appear the same size as flat-edged letters.
Trust your eye over measurements.
Incorrect (mathematically centered, looks low):
.modal {
position: fixed;
top: 50%;
transform: translateY(-50%); /* looks like it sags */
}Correct (optically centered, slightly above midpoint):
.modal {
position: fixed;
top: 45%; /* slightly above center */
transform: translateY(-50%);
}
/* Or use padding bias */
.card-icon {
padding: 1rem 1rem 1.25rem 1rem; /* more bottom padding */
}This principle applies to icons in buttons, text in cards, logos in headers, and any element where visual centering matters more than pixel math.
Use Proximity and Dividers to Clarify Associations
Place captions and descriptions closer to the images or elements they describe. Use dividers (horizontal rules) to separate unrelated sections, placing them above headings rather than below. Dividers below headings visually underline them, which is a Victorian typographic convention that weakens the heading's connection to its content.
Incorrect (divider below heading, caption far from image):
<h2>Our Process</h2>
<hr> <!-- separates heading from its content -->
<p>We follow a three-step approach...</p>
<img src="photo.jpg" alt="Team meeting">
<p class="caption">The team during a strategy session.</p>
<!-- Caption is far from image if other content intervenes -->Correct (divider above heading, caption adjacent to image):
<hr> <!-- separates from previous section -->
<h2>Our Process</h2>
<p>We follow a three-step approach...</p>
<figure>
<img src="photo.jpg" alt="Team meeting">
<figcaption>The team during a strategy session.</figcaption>
</figure>Choose between hanging bullets that improve reading or standard indented bullets \u2014 decide based on readability in your specific layout.
Manage Widows and Orphans with Non-Breaking Spaces
A single word dangling on the last line of a headline looks awkward. Insert a non-breaking space ( ) between the last two words of headlines and navigation items to keep them together. Use white-space: nowrap sparingly for short phrases.
On body paragraphs, accept imperfection \u2014 dynamic content and responsive design make full widow/orphan control impractical.
Incorrect (headline with a dangling word):
<h1>Getting Started with Web
Typography</h1>
<!-- "Typography" sits alone on the second line -->Correct (non-breaking space prevents break):
<h1>Getting Started with Web Typography</h1>Also use for:
- Navigation items:
About Us - Time expressions:
3:00 PM - Brand names:
New York - Short phrases on homepage hero text
CSS text-wrap (supported in modern browsers) can also help. Use balance for headings and short UI copy — it distributes text evenly across all lines. Use pretty for body paragraphs — it avoids orphaned last words with a cheaper algorithm that only adjusts the final lines.
h1, h2, h3 {
text-wrap: balance;
}
p {
text-wrap: pretty;
}Enable Standard OpenType Features for Body
Always enable these four OpenType features for body text: kern (kerning), liga (standard ligatures), clig (contextual ligatures), and calt (contextual alternates). These improve letter spacing and glyph substitution automatically.
Incorrect (default browser settings, features may be off):
body {
font-family: 'Source Serif Pro', serif;
/* relies on browser defaults */
}Correct (explicit OpenType features enabled):
body {
font-family: 'Source Serif Pro', serif;
font-kerning: normal;
font-feature-settings: "kern", "liga", "clig", "calt";
}Modern CSS alternative using individual properties:
body {
font-kerning: normal;
font-variant-ligatures: common-ligatures contextual;
}Most browsers enable kern and liga by default, but explicit declarations ensure consistent behavior across browsers and font configurations.
Avoid Faux Bold, Italic, and Small Caps
Browsers synthesize bold, italic, and small caps when the actual font files are not loaded. Faux italic mechanically slants the roman design. Faux bold adds artificial weight that looks blotchy. Faux small caps shrink uppercase, resulting in thin, unbalanced glyphs.
Load real font styles to prevent this. Identify true italics by checking if letterforms are redesigned (a, e, f, g typically change shape in true italic).
Incorrect (relying on browser synthesis):
@font-face {
font-family: 'MyFont';
src: url('MyFont-Regular.woff2') format('woff2');
/* Only regular loaded \u2014 browser fakes everything else */
}Correct (prevent synthesis, load real styles):
/* Load all needed styles */
@font-face {
font-family: 'MyFont';
font-weight: 400;
font-style: normal;
src: url('MyFont-Regular.woff2') format('woff2');
}
@font-face {
font-family: 'MyFont';
font-weight: 400;
font-style: italic;
src: url('MyFont-Italic.woff2') format('woff2');
}
/* Prevent synthesis as a safety net */
body {
font-synthesis: none;
}font-synthesis: none prevents the browser from generating faux styles, which helps catch missing font files during development.
Use Metrics Kerning, Adjust Tracking First
Use the font's built-in kerning tables (metrics kerning) rather than optical kerning. High-quality fonts have carefully crafted kern pairs. Before manually adjusting individual kern pairs, adjust overall tracking (letter-spacing) first. Manual kerning should only be necessary for large display type and logos.
Incorrect (optical kerning or excessive manual adjustment):
body {
font-kerning: auto; /* may not use metrics */
}
/* Over-kerning body text */
body {
letter-spacing: -0.02em;
}Correct (metrics kerning, tracking adjustments before kerning):
body {
font-kerning: normal; /* use font's built-in kern tables */
}
/* If large heading spacing feels off, adjust tracking first */
h1 {
letter-spacing: -0.02em; /* tracking adjustment */
}
/* Manual kerning only for logos/display (use spans) */
.logo .letter-A { margin-right: -0.05em; }Use hair spaces ( ) or thin spaces ( ) where normal spacing feels off but a full adjustment is too much. Accept minor kerning irregularities in body text \u2014 they are inherent in the Latin alphabet.
Manage Ligatures by Context
Enable standard ligatures (liga) for body text. Keep discretionary ligatures (dlig) off in body \u2014 they can be distracting. Disable all ligatures in code blocks where character accuracy matters (fi, fl, ffi ligatures obscure individual characters).
Incorrect (discretionary ligatures in body, ligatures in code):
body {
font-feature-settings: "liga", "dlig"; /* dlig distracting in body */
}
code {
/* inherits ligatures, fi becomes a single glyph */
}Correct (standard only in body, none in code):
body {
font-feature-settings: "liga", "clig", "calt";
/* no "dlig" in body text */
}
code, pre {
font-variant-ligatures: none;
font-feature-settings: "liga" 0, "clig" 0, "calt" 0;
}
/* Discretionary ligatures for headlines only */
h1 {
font-feature-settings: "liga", "dlig", "calt";
}Standard ligatures: fi, fl, ffi, ffl (always appropriate in body). Discretionary ligatures: st, ct, decorative forms (headlines/display only).
Do Not Adjust Spacing on Monospaced or Script Fonts
Monospaced fonts require uniform character widths \u2014 adjusting letter-spacing breaks their alignment purpose. Connected script fonts rely on precise glyph connections \u2014 adding or removing space breaks the joins between letters.
Keep spacing and kerning at the font's built-in metrics for both types.
Incorrect (letterspacing a monospaced or script font):
code {
font-family: 'Fira Code', monospace;
letter-spacing: 0.05em; /* breaks column alignment */
}
.script-heading {
font-family: 'Great Vibes', cursive;
letter-spacing: 0.1em; /* breaks letter connections */
}Correct (use default spacing):
code {
font-family: 'Fira Code', monospace;
letter-spacing: normal;
}
.script-heading {
font-family: 'Great Vibes', cursive;
letter-spacing: normal;
}Do not apply optical kerning to these font types either. Use metrics kerning only.
Use Oldstyle Figures in Running Text
Oldstyle (lowercase) figures blend with body text because they have ascenders and descenders like lowercase letters. Use onum for running text. Use lining figures (lnum) next to uppercase letters, in UI elements, and in headings where alignment matters.
Incorrect (lining figures disrupting body text flow):
body {
/* default lining figures stick up from the baseline */
}<p>The event attracted 1,247 attendees in 2024.</p>
<!-- Lining "1,247" and "2024" look oversized in body text -->Correct (oldstyle figures for body, lining for UI):
body {
font-variant-numeric: oldstyle-nums;
}
/* Or using font-feature-settings */
body {
font-feature-settings: "onum";
}
/* Lining figures for UI and headings */
.btn, .heading, .table-cell {
font-variant-numeric: lining-nums;
}Spell out numbers one through nine in prose if desired (style guide dependent). Verify the font supports onum before enabling \u2014 not all fonts include oldstyle figure glyphs.
Use Real Small Caps, Not Pseudo
Real small caps are specially designed glyphs with proportions matching lowercase letters. Pseudo small caps (generated by shrinking uppercase) look thin and uneven. Enable real small caps via font-variant-caps or font-feature-settings. Add slight letter-spacing (0.05em) to small caps.
Use small caps for abbreviations (NASA, HTML) in running text and for subheadings.
Incorrect (pseudo small caps via font-size reduction):
.abbr {
font-size: 0.8em;
text-transform: uppercase; /* fake small caps */
}Correct (real OpenType small caps):
.abbr {
font-variant-caps: all-small-caps;
letter-spacing: 0.05em;
}
/* Or using font-feature-settings */
.abbr {
font-feature-settings: "smcp", "c2sc";
letter-spacing: 0.05em;
}Verify the font includes smcp (small caps from lowercase) and optionally c2sc (small caps from uppercase) features before using. Not all fonts include real small cap glyphs.
Use Tabular Figures in Data Contexts
Use tabular (monospaced-width) figures in tables, price lists, and anywhere numbers need to align vertically. Right-align columns of numbers. Use commas as thousands separators. Enable tnum via font-feature-settings or use a font with tabular figures by default.
Incorrect (proportional figures in a table):
.price-table td {
/* default proportional figures, numbers don't align */
text-align: left;
}Correct (tabular figures, right-aligned):
.price-table td.number {
font-variant-numeric: tabular-nums;
text-align: right;
}
/* Or using font-feature-settings */
.price-table td.number {
font-feature-settings: "tnum";
text-align: right;
}If the chosen font lacks tabular figure support, use a monospaced or system font for data columns as a fallback.
Pair by Contrast or Harmony, Not Similarity
Typefaces should either harmonize (share underlying structural qualities) or contrast strongly (differ in a clear, intentional way). Avoid pairs that are "almost the same" \u2014 similar but not matching faces create visual tension without purpose.
Judge harmony by comparing the handwritten vs constructed feel of the letterforms. Compare stress angles and skeletal structures.
Incorrect (almost the same, neither matching nor contrasting):
h1 { font-family: 'Helvetica', sans-serif; }
body { font-family: 'Univers', sans-serif; }
/* Both neo-grotesque, nearly identical \u2014 no contrast, no harmony */Correct (harmonious pair \u2014 shared calligraphic roots):
h1 { font-family: 'Palatino', serif; }
body { font-family: 'Optima', sans-serif; }
/* Both have calligraphic influence and humanist proportions */Correct (contrasting pair \u2014 clear structural difference):
h1 { font-family: 'Futura', sans-serif; } /* geometric */
body { font-family: 'Baskerville', serif; } /* transitional */
/* Strong serif vs sans contrast with different geometries */Match stress direction to find harmony: vertical-stress fonts pair with other vertical-stress fonts; diagonal-stress fonts pair with other diagonal-stress fonts.
Pair Geometric Sans with Modern Serifs
Geometric sans-serifs (Futura, Avenir, Century Gothic) share the rational, constructed quality of modern/didone serifs (Bodoni, Didot). Both have vertical stress and clean geometry. This creates a harmonious pair with inherent serif/sans contrast.
Geometric sans-serifs are generally weaker for body text at small sizes \u2014 ensure legibility when using them.
Incorrect (geometric sans + old-style serif):
h1 { font-family: 'Avenir', sans-serif; } /* geometric/vertical */
body { font-family: 'Caslon', serif; } /* old-style/diagonal */
/* Stress mismatch */Correct (geometric sans + modern serif):
h1 { font-family: 'Avenir', sans-serif; }
body { font-family: 'Didot', serif; }
/* Both share vertical stress and rational geometry */Ensure the text remains legible at small sizes. Geometric sans at body sizes can be challenging for extended reading.
Pair Grotesques with Transitional Serifs
Grotesques/gothics (Franklin Gothic, News Gothic, Trade Gothic) pair naturally with transitional serifs (Baskerville, Times, Georgia) for a classic, reliable combination. Neo-grotesques (Helvetica, Univers, Akzidenz-Grotesk) pair well with slab serifs (Rockwell, Clarendon, Sentinel).
Note: "Grotesque," "grotesk," and "gothic" are different names for the same sans-serif genre.
Neo-grotesques are weaker for body text than humanist sans-serifs. Slabs can be good body choices when the goal is a sturdy, approachable feel.
Correct (grotesque + transitional):
h1 { font-family: 'Franklin Gothic', sans-serif; }
body { font-family: 'Baskerville', serif; }Correct (neo-grotesque + slab):
h1 { font-family: 'Helvetica Neue', sans-serif; }
body { font-family: 'Sentinel', serif; }Pair Humanist Sans with Old-Style Serifs
Humanist sans-serifs (Gill Sans, Frutiger, Myriad) and old-style serifs (Garamond, Caslon, Jenson) share calligraphic roots and diagonal stress. This pairing is extremely versatile and readable.
Neo-humanist sans-serifs (Calibri, Verdana, Lucida Grande) pair well with contemporary serifs (Charter, Utopia, Chaparral) for high screen readability. This combination is arguably the best all-around choice for web typography.
Correct (humanist sans + old-style serif):
h1 { font-family: 'Frutiger', sans-serif; }
body { font-family: 'Adobe Caslon', serif; }
/* Shared calligraphic DNA, diagonal stress */Correct (neo-humanist + contemporary serif):
h1 { font-family: 'Calibri', sans-serif; }
body { font-family: 'Charter', serif; }
/* Optimized for screens, highly readable */Treat all pairing rules as guidance, not law. Know the rules before breaking them.
Limit to Two Typefaces
Use at most two typefaces in a project: one for body text and one for display/headings. Adding more faces increases complexity and the risk of visual discord. If you need more than two, enforce a strict system with clear roles for each face.
Incorrect (too many faces, no system):
h1 { font-family: 'Playfair Display', serif; }
h2 { font-family: 'Lora', serif; }
h3 { font-family: 'Merriweather', serif; } /* three serifs! */
p { font-family: 'Open Sans', sans-serif; }
.label { font-family: 'Roboto', sans-serif; } /* two sans! */Correct (two typefaces with clear roles):
:root {
--font-heading: 'Playfair Display', serif;
--font-body: 'Source Sans Pro', sans-serif;
}
h1, h2, h3 { font-family: var(--font-heading); }
body { font-family: var(--font-body); }A single typeface with multiple weights can also work for an entire project, especially with a versatile superfamily.
Avoid Pairing Two Sans-Serifs
Two sans-serif typefaces in the same design typically lack sufficient contrast and create visual confusion. Readers cannot distinguish their roles. The safer approach is serif + sans-serif, which provides inherent structural contrast.
The exception is pairing sans-serifs from distinctly different genres (e.g., a geometric sans for headings with a humanist sans for body), but this requires careful handling.
Incorrect (two similar sans-serifs):
h1 { font-family: 'Helvetica', sans-serif; }
body { font-family: 'Arial', sans-serif; }
/* Nearly identical \u2014 no contrast, no purpose */Correct (serif + sans-serif):
h1 { font-family: 'Source Serif Pro', serif; }
body { font-family: 'Source Sans Pro', sans-serif; }If you must use two sans-serifs, ensure they come from different genre families (geometric vs humanist) and serve clearly different roles.
Avoid Pairing Two Serifs
Two serifs together tend to compete rather than complement. If you must pair two serifs, use strong contrast (e.g., a slab serif heading with an old-style body) or choose two variants from the same family. Never mix modern serifs with old-style serifs \u2014 their structural philosophies clash.
Incorrect (two competing serifs):
h1 { font-family: 'Bodoni', serif; } /* modern serif */
body { font-family: 'Garamond', serif; } /* old-style serif */
/* Vertical stress clashes with diagonal stress */Correct (serif + sans, or same-family serifs):
/* Option A: serif + sans */
h1 { font-family: 'Garamond', serif; }
body { font-family: 'Gill Sans', sans-serif; }
/* Option B: same family, different cuts */
h1 { font-family: 'Freight Display', serif; }
body { font-family: 'Freight Text', serif; }Pairing by the same type designer can also help, as designers tend to use consistent design principles across their typefaces.
Match Stress Angles When Pairing
The stress angle of a typeface (where the thickest part of curved strokes falls) is a strong indicator of compatibility. Vertical stress pairs well with vertical stress. Diagonal stress pairs well with diagonal stress. Mixing stress angles creates subtle but persistent visual tension.
Stress angle guide:
| Stress | Serif Examples | Sans Examples |
|---|---|---|
| Vertical | Bodoni, Didot, Modern serifs | Futura, Avenir, Geometric sans |
| Diagonal | Garamond, Caslon, Old-style serifs | Gill Sans, Optima, Humanist sans |
Incorrect (mismatched stress):
h1 { font-family: 'Futura', sans-serif; } /* vertical/geometric */
body { font-family: 'Garamond', serif; } /* diagonal/old-style */
/* Stress mismatch creates tension */Correct (matched stress):
/* Both diagonal/humanist stress */
h1 { font-family: 'Gill Sans', sans-serif; }
body { font-family: 'Garamond', serif; }
/* Both vertical/rational stress */
h1 { font-family: 'Futura', sans-serif; }
body { font-family: 'Bodoni', serif; }Use Superfamilies for Easy Pairing
Superfamilies include serif and sans-serif variants designed to share the same underlying skeleton. They are guaranteed to pair harmoniously, removing guesswork.
Popular superfamilies:
- FF Tisa + FF Tisa Sans
- Calluna + Calluna Sans
- Freight Text + Freight Sans
- Adelle + Adelle Sans
- FF Meta + FF Meta Serif
- FF Scala + FF Scala Sans
- Fedra Serif + Fedra Sans
- Source Serif + Source Sans
Incorrect (unrelated pair that clashes):
h1 { font-family: 'Bodoni', serif; }
body { font-family: 'Futura', sans-serif; }
/* High-contrast modern serif with geometric sans \u2014 risky */Correct (superfamily pair):
h1 { font-family: 'Source Serif Pro', serif; }
body { font-family: 'Source Sans Pro', sans-serif; }
/* Same design DNA, guaranteed harmony */Most sans-serifs in superfamilies are humanist, since readable text serifs tend to be humanist designs requiring a humanist sans counterpart.
Choose UI Fonts with Distinct Ambiguous Glyphs
For UI and data-heavy interfaces, choose fonts where l (lowercase L), I (uppercase i), and 1 (one) are visually distinct. Ambiguous glyphs cause confusion in IDs, codes, passwords, and data entry. Consider serifs for UI \u2014 the serifs on I and l aid disambiguation.
Use condensed faces for headlines and labels when space is tight, but never for body text.
Incorrect (ambiguous glyphs in UI):
.code-input {
font-family: 'Helvetica', sans-serif;
/* I, l, and 1 look nearly identical */
}Correct (distinct glyphs for UI):
.code-input {
font-family: 'Inter', sans-serif;
/* Inter has distinct I (with serifs), l, and 1 */
}
/* Or use a monospaced font for code/ID fields */
.code-input {
font-family: 'JetBrains Mono', monospace;
}Fonts with distinct l/I/1: Inter, IBM Plex Sans, Fira Sans, Source Sans Pro, Noto Sans. Test with the string "Il1|" before committing to a UI typeface.
Clean Up Abbreviations and Decades
Remove unnecessary punctuation from abbreviations and decades. Drop apostrophes from decade references (1990s, not 1990\u2019s). Remove periods from acronyms (NASA, not N.A.S.A.). These are common typographic errors that mark amateur work.
Incorrect (unnecessary punctuation):
<p>The company was founded in the 1990's.</p>
<p>She works at N.A.S.A. on A.I. projects.</p>Correct (clean abbreviations):
<p>The company was founded in the 1990s.</p>
<p>She works at NASA on AI projects.</p>Exception: lowercase abbreviations that could be misread as words (e.g., "e.g.", "i.e.", "a.m.", "p.m.") may retain periods for clarity.
Use Ampersands Sparingly
Ampersands should be reserved for proper names, established shorthand, and tight spaces. Do not use them as a general substitute for "and" in running text. Many typefaces have beautifully designed ampersand glyphs that can serve as decorative elements in display contexts.
Incorrect (ampersands in prose):
<p>We focus on design & development & strategy.</p>
<p>Read the terms & conditions before signing.</p>Correct (ampersands for proper names and shorthand):
<p>We focus on design, development, and strategy.</p>
<p>Johnson & Johnson reported earnings today.</p>
<p>This is an R&D initiative.</p>Choose Sentence or Title Case Consistently
Pick either sentence case or title case for headings and apply the choice consistently throughout the project. Title case feels more formal; sentence case feels more casual and friendly. Auto-format titles per your chosen style guide.
Incorrect (inconsistent casing):
<h1>Getting Started with Typography</h1> <!-- title case -->
<h2>How to choose the right font</h2> <!-- sentence case -->
<h3>Best Practices For Line Height</h3> <!-- inconsistent title case -->Correct (consistent sentence case):
<h1>Getting started with typography</h1>
<h2>How to choose the right font</h2>
<h3>Best practices for line height</h3>Or consistent title case:
<h1>Getting Started with Typography</h1>
<h2>How to Choose the Right Font</h2>
<h3>Best Practices for Line Height</h3>Always capitalize the first word, proper nouns, and "I" regardless of chosen style. Use text-transform: capitalize with caution as it does not follow title case rules for small words.
Use Daggers and Special Marks Correctly
Use dagger (\u2020) and double dagger (\u2021) for footnotes when asterisks are already in use or when numbering is impractical. Use hedera (\u2767) and manicule (\u261e) sparingly as decorative elements only.
Incorrect (improvised footnote markers):
<p>This claim requires citation.*</p>
<p>*See appendix A. **See appendix B.</p>Correct (proper footnote markers):
<p>This claim requires citation.†</p>
<p>Another point to note.‡</p>
<footer>
<p>† See appendix A.</p>
<p>‡ See appendix B.</p>
</footer>Footnote marker order: \u2020 \u2021 \u00a7 \u2016 \u00b6 (then double up: * \u2020\u2020 etc.)
Em Dash vs En Dash vs Hyphen
Each dash serves a distinct purpose. Never use double hyphens (--) as a substitute for an em dash. Choose either spaced en dashes or unspaced em dashes for parenthetical breaks and apply the choice consistently throughout the project.
If the em dash looks too wide in your chosen typeface, switch to spaced en dashes.
Incorrect (double hyphens and wrong dash types):
<p>Typography--the art of type--matters.</p>
<p>Pages 10-20 cover the basics.</p>
<p>The New York-London flight departs at noon.</p>Correct (proper dash characters):
<!-- Em dash for breaks (unspaced style) -->
<p>Typography—the art of type—matters.</p>
<!-- Or en dash for breaks (spaced style) -->
<p>Typography – the art of type – matters.</p>
<!-- En dash for ranges -->
<p>Pages 10–20 cover the basics.</p>
<!-- En dash for relationships/connections -->
<p>The New York–London flight departs at noon.</p>Key characters:
| Character | Name | HTML Entity | Use |
|---|---|---|---|
| \u2014 | Em dash | — | Parenthetical breaks, attribution |
| \u2013 | En dash | – | Ranges, relationships |
| - | Hyphen | - | Compound words only |
Use em dash for attribution in blockquotes: —Robert Bringhurst
Support Accented Characters and Diacritics
Ensure chosen fonts include all necessary accented characters. Use correct diacritics (caf\u00e9, not cafe; na\u00efve, not naive). Store accents as Unicode characters, not as composed sequences. Avoid over-subsetting fonts, which can strip accented glyphs.
Incorrect (missing diacritics, wrong encoding):
<meta charset="iso-8859-1">
<p>The cafe serves a prix fixe menu with creme brulee.</p>Correct (UTF-8 with proper diacritics):
<meta charset="utf-8">
<p>The café serves a prix fixe menu with crème brûlée.</p>Verify glyph coverage before selecting a font. Test with content that includes characters from all target languages. Avoid font subsetting that removes accented character ranges.
Use Proper Fraction and Math Entities
Use Unicode fraction characters and proper math symbols instead of improvised alternatives. The multiplication sign (\u00d7) is not the letter "x". Standard fractions have dedicated Unicode characters.
Incorrect (improvised fractions and symbols):
<p>The display is 1920x1080 pixels.</p>
<p>Add 1/2 cup of flour and 3/4 tsp salt.</p>Correct (proper entities):
<p>The display is 1920×1080 pixels.</p>
<p>Add ½ cup of flour and ¾ tsp salt.</p>Common fraction entities:
| Fraction | Entity | Unicode |
|---|---|---|
| \u00bd | ½ | U+00BD |
| \u00bc | ¼ | U+00BC |
| \u00be | ¾ | U+00BE |
| \u2153 | ⅓ | U+2153 |
| \u2154 | ⅔ | U+2154 |
Use Midpoint Separators with Thin Spaces
Prefer midpoints (\u00b7) over pipes (|) or bullets (\u2022) for inline horizontal list separators. Use hair spaces ( ) or thin spaces ( ) on either side of the midpoint for correct spacing.
Incorrect (pipes or bullets as separators):
<span>About | Blog | Contact</span>
<span>About • Blog • Contact</span>Correct (midpoints with thin spaces):
<span>About · Blog · Contact</span>Use the correct midpoint character: · (U+00B7), not a period or bullet.
Use Prime Marks for Measurements
Prime (\u2032) and double prime (\u2033) marks are distinct from quotation marks and apostrophes. Use them for feet/inches, minutes/seconds, and coordinates. Never substitute curly quotes or straight quotes for primes.
Incorrect (quotes used as primes):
<p>The board is 6' 2" long.</p>
<p>The coordinates are 40\u00b0 26' 46" N.</p>Correct (true prime characters):
<p>The board is 6′ 2″ long.</p>
<p>The coordinates are 40° 26′ 46″ N.</p>Key characters:
| Character | Name | HTML Entity | Unicode |
|---|---|---|---|
| \u2032 | Prime (single) | ′ | U+2032 |
| \u2033 | Double prime | ″ | U+2033 |
Primes should lean slightly to the right, distinct from the curly shape of apostrophes.
One Space After Periods
Use exactly one space between sentences. Double spacing after periods is a typewriter-era habit with no place in modern typography. HTML collapses multiple spaces by default, but double spaces can appear in <pre> blocks, emails, and CMS content.
Incorrect (double spaces):
<p>Typography matters. Good type improves readability. Every detail counts.</p>Correct (single spaces):
<p>Typography matters. Good type improves readability. Every detail counts.</p>Audit existing copy for double spaces and remove them. Configure your CMS or build pipeline to normalize spacing.
Use Smart Quotes, Never Straight
Smart (curly) quotes are the hallmark of professional typography. Straight quotes are a remnant of typewriters and should only appear in code. Dumb quotes in headlines are especially noticeable.
Ensure your site uses UTF-8 encoding and normalize content at build or render time. Enable smart quote conversion in your CMS or build pipeline.
Incorrect (straight/dumb quotes):
<p>"It's a beautiful day," she said.</p>
<p>The "best" option isn't always obvious.</p>Correct (smart/curly quotes):
<p>“It’s a beautiful day,” she said.</p>
<p>The “best” option isn’t always obvious.</p>Or using UTF-8 directly:
<p>\u201CIt\u2019s a beautiful day,\u201D she said.</p>Key characters:
| Character | Name | HTML Entity | Mac Shortcut |
|---|---|---|---|
| \u201C | Left double quote | “ | Opt+[ |
| \u201D | Right double quote | ” | Opt+Shift+[ |
| \u2018 | Left single quote | ‘ | Opt+] |
| \u2019 | Right single quote / apostrophe | ’ | Opt+Shift+] |
Use single quotes inside double quotes (US convention). Reserve straight quotes exclusively for code contexts.
Copyright, Trademark, and Registered Marks
Use correct Unicode characters or HTML entities for \u00a9, \u2122, and \u00ae. Trademark and registered marks should appear as superscripts. Copyright should be inline at the same size as surrounding text. Do not duplicate the word and symbol (e.g., avoid "Copyright \u00a9").
Use a non-breaking space ( ) between the copyright symbol and the year.
Incorrect (wrong formatting):
<p>Copyright (c) 2025 Acme Corp</p>
<p>Acme(TM) Widget</p>
<p>Copyright © 2025</p> <!-- missing non-breaking space -->Correct (proper symbols and spacing):
<p>© 2025 Acme Corp</p>
<p>Acme<sup>™</sup> Widget</p>
<p>Acme<sup>®</sup> Widget</p>/* Ensure superscript marks are sized appropriately */
sup {
font-size: 0.6em;
vertical-align: super;
}Set Body Text Size by Context
Set body text size first \u2014 it anchors the entire typographic system. Use 16\u201324px for desktop, 15\u201319px for mobile, and 10\u201312pt for print. Adjust based on the typeface's x-height: faces with a large x-height feel bigger at the same pixel size.
Avoid oversized desktop body text (above 24px). Scale headings down proportionally on smaller screens.
Incorrect (one size for all contexts):
body {
font-size: 14px; /* too small for comfortable reading */
}Correct (responsive body sizing):
body {
font-size: 18px; /* desktop default */
line-height: 1.5;
}
@media (max-width: 768px) {
body {
font-size: 16px; /* mobile */
}
}
@media print {
body {
font-size: 11pt;
}
}Find the typeface's "sweet spot" by testing one size up and down. If 19px looks right but 18px feels too small and 20px too large, use 19px even if it is not part of your modular scale.
Use Italics for Emphasis
Use italics (not bold, all caps, or quotation marks) for emphasis in running text. Italics provide gentle emphasis without disrupting reading flow. Bold should be reserved for strong semantic importance. All caps reduces readability and feels like shouting.
Prefer fonts with true italics for body text. Limit the amount of emphasized text \u2014 if everything is emphasized, nothing is.
Incorrect (bold and all caps for emphasis):
<p>Typography is <b>REALLY</b> important for readability.</p>
<p>The <strong>key</strong> is to use "proper" punctuation.</p>Correct (italics for emphasis):
<p>Typography is <em>really</em> important for readability.</p>
<p>Italicize publication titles: <cite>The Elements of Typographic Style</cite>.</p>Use <em> for emphasis and <cite> for publication titles. Avoid <i> unless styling alternate voice (technical terms, foreign phrases).
Use Hanging Punctuation Where Feasible
Hanging punctuation aligns the left edge of text optically by moving quotation marks and other punctuation into the margin. This creates a cleaner visual edge. On the web, support is limited \u2014 implement only for blockquotes and display text where it has the most impact.
Incorrect (punctuation disrupts left alignment):
<blockquote>
<p>"Good typography is invisible."</p>
</blockquote>blockquote {
/* Quote mark pushes text inward */
}Correct (hanging punctuation on blockquotes):
/* CSS hanging-punctuation (Safari support) */
blockquote p {
hanging-punctuation: first last;
}
/* Fallback: negative text-indent */
blockquote p {
text-indent: -0.4em;
}For web, limit hanging punctuation to the left edge of blockquotes and display headings. Full hanging punctuation across all body text is impractical to maintain on the web.
Set Line Height for Comfortable Reading
Set line height to approximately 1.45\u20131.5 for body text. Always use unitless values to ensure proper inheritance. Adjust based on font size, line length, and x-height: sans-serif faces with large x-heights may need slightly more line height. Longer lines need more line height; shorter lines need less.
Avoid excessive leading (above 1.8) which disconnects lines visually.
Incorrect (too tight, units used):
body {
font-size: 18px;
line-height: 20px; /* fixed value, doesn't scale */
}
h1 {
font-size: 48px;
/* inherits 20px line-height, causing overlap */
}Correct (unitless, proportional):
body {
font-size: 18px;
line-height: 1.5; /* unitless, scales with font-size */
}
h1 {
font-size: 48px;
line-height: 1.15; /* tighter for large headings */
}
.caption {
font-size: 14px;
line-height: 1.4; /* slightly tighter for small text */
}Set line height before paragraph spacing. The two work together to establish vertical rhythm.
Keep Line Length Between 45\u201375 Characters
The ideal line length (measure) is approximately 66 characters per line including spaces. Lines shorter than 45 characters create excessive hyphenation or ragged edges. Lines longer than 75 characters cause readers to lose their place when moving to the next line.
Adjust line length per breakpoint. Use ch units or max-width on text containers.
Incorrect (no measure constraint):
.article {
width: 100%; /* lines can stretch to 120+ characters on wide screens */
font-size: 18px;
}Correct (constrained measure):
.article {
max-width: 65ch; /* approximately 65 characters */
font-size: 18px;
margin: 0 auto;
}
@media (max-width: 768px) {
.article {
max-width: 100%;
padding: 0 1rem;
/* Narrower screen naturally constrains measure */
}
}The ch unit equals the width of the "0" character. It approximates character count but varies by typeface. Test with actual content to verify.
Adapt Typography Across Breakpoints
Scale headings down on smaller screens. Swap display faces to text faces on small screens via media queries \u2014 display cuts with fine details lose legibility at small sizes. Limit long light-on-dark text on mobile where screen glare reduces contrast.
Incorrect (desktop type unchanged on mobile):
h1 {
font-family: 'Playfair Display', serif;
font-size: 72px; /* stays huge on mobile */
}Correct (responsive scaling and font swapping):
h1 {
font-family: 'Playfair Display', serif;
font-size: 48px;
}
@media (max-width: 768px) {
h1 {
font-family: 'Source Serif Pro', serif; /* text cut for small screens */
font-size: 28px;
line-height: 1.2;
}
}Identify display faces by their naming: Display, Banner, Headline, Poster, Titling. These should not appear at body sizes on any screen.
Never Underline for Emphasis
On the web, underlines universally signal hyperlinks. Using underlines for emphasis confuses users and creates false affordances. In print, underlines are a typewriter-era convention with no place in modern typography.
Incorrect (underline for emphasis):
.important {
text-decoration: underline; /* looks like a link */
}<p>This is an <span class="important">important</span> point.</p>Correct (italics for emphasis, underlines for links):
a {
text-decoration: underline;
text-decoration-thickness: 1px;
text-underline-offset: 0.15em;
}
em {
font-style: italic;
}<p>This is an <em>important</em> point.</p>
<p>Read more on <a href="/typography">our typography page</a>.</p>Avoid using link color (typically blue) for non-link text, as this creates the same confusion.
Ensure Adequate Column Padding and Gutters
Text needs breathing room. Ensure columns have adequate padding between them, outer margins keep text from hitting screen edges, and mobile layouts include thumb space. Cramped text is harder to read and feels unprofessional.
Incorrect (no gutters, text hits edges):
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0; /* columns touch */
}
.container {
padding: 0; /* text hits screen edges on mobile */
}Correct (proper gutters and margins):
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem; /* comfortable gutter between columns */
}
.container {
padding: 1.5rem;
max-width: 1200px;
margin: 0 auto;
}
@media (max-width: 768px) {
.container {
padding: 1rem; /* minimum thumb space */
}
.grid {
grid-template-columns: 1fr; /* stack on mobile */
}
}Use Hair and Thin Spaces for Fine Adjustments
When a full word space feels too wide and no space feels too tight, use hair spaces ( ) or thin spaces ( ). Common applications: around em dashes, after citations, between nested punctuation marks, and around special characters.
Incorrect (no space or full space around em dashes):
<p>Typography\u2014the art of type\u2014matters.</p> <!-- too tight -->
<p>Typography \u2014 the art of type \u2014 matters.</p> <!-- too loose -->Correct (thin spaces around em dashes):
<p>Typography — the art of type — matters.</p>Common uses:
| Context | Spacing |
|---|---|
| Around em dashes |   or   |
| Between quote and attribution |   |
| Around midpoint separators |   |
| Between units and values |   (e.g., 5 kg) |
Use   (U+200A, thinnest) when   (U+2009) still feels too wide.
Never Letterspace Body Text
Adding letter-spacing to body text destroys the carefully designed spacing built into the font's metrics. Professional typefaces are spaced for optimal readability at text sizes. The only exceptions are tiny captions (below 10px) where slight positive tracking aids legibility, and display-size lowercase.
Incorrect (letterspacing applied to body):
body {
letter-spacing: 0.05em; /* disrupts natural rhythm */
}
p {
letter-spacing: 1px; /* even worse */
}Correct (no letterspacing on body):
body {
letter-spacing: normal;
}
/* Only tiny text may benefit from slight tracking */
.caption {
font-size: 10px;
letter-spacing: 0.02em;
}Remember: kerning adjusts space between specific letter pairs; letterspacing (tracking) adjusts uniform space between all letters. They are different tools.
Add Letterspacing to Uppercase Text
Uppercase letters are designed to sit next to lowercase letters. When set in all caps, they appear too tightly spaced. Add approximately 0.05\u20130.2em of letter-spacing to uppercase text. Increase spacing for smaller uppercase text; decrease for larger.
Avoid setting multi-line blocks in all uppercase \u2014 it significantly reduces reading speed.
Incorrect (uppercase without letterspacing):
.label {
text-transform: uppercase;
/* no letter-spacing adjustment */
}Correct (uppercase with tracking):
.label {
text-transform: uppercase;
letter-spacing: 0.1em;
font-size: 0.75rem;
}
/* Larger uppercase needs less tracking */
.section-title {
text-transform: uppercase;
letter-spacing: 0.05em;
font-size: 1.25rem;
}Do not letterspace or use optical kerning on monospaced fonts or connected script fonts. Keep their spacing at the font's built-in metrics.
Space Navigation Items with CSS Padding
Use CSS padding (not spaces or margin hacks) to space navigation items. Limit the number of navigation items for clarity. Indicate the current page by styling the selected item as active/inactive \u2014 do not gray out navigable items.
Incorrect (spaces for nav padding, grayed navigable items):
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/blog" style="color: gray">Blog</a> <!-- grayed but navigable -->
</nav>Correct (CSS padding, clear selected state):
nav a {
padding: 0.5rem 1rem;
text-decoration: none;
color: var(--text-secondary);
}
nav a[aria-current="page"] {
color: var(--text-primary);
font-weight: 600;
}
nav a:hover {
color: var(--text-primary);
}Never gray out items that are still navigable. Keep the selected item readable and visually prominent.
Apply Indents Correctly
When using indentation for paragraph separation, apply the indent only after the first paragraph \u2014 the opening paragraph needs no indent since there is nothing to separate it from. Size indents to approximately 1\u20131.5em; larger indents (2\u20133em) suit wide columns with ample margins.
Incorrect (every paragraph indented, including first):
p {
text-indent: 1.5em;
}Correct (indent only after first paragraph):
p + p {
text-indent: 1.5em;
}For narrow columns, use a smaller indent. Indents larger than 3em are generally counterproductive.
Add Adequate Margins Around Text
Ensure adequate padding and margins around text blocks, including column padding, outer margins, thumb space on mobile, and print gutters. Keep paragraph spacing modest and responsive \u2014 avoid paragraphs that touch each other or have excessive gaps.
Set line height first, then adjust paragraph spacing. The two values work together.
Incorrect (cramped margins):
.article {
padding: 0;
margin: 0;
}
p {
margin: 0; /* paragraphs run together */
}Correct (generous, responsive margins):
.article {
padding: 2rem;
max-width: 65ch;
margin: 0 auto;
}
p {
margin-bottom: 1em;
}
@media (max-width: 768px) {
.article {
padding: 1rem; /* adequate thumb space */
}
}Break long copy into readable paragraphs. Avoid walls of text. Use subheadings and lists where useful.
Use Line Breaks or Indents, Not Both
Separate paragraphs with either vertical space (line breaks) or indentation \u2014 never both. Line breaks are the web default and aid scanning. Indents feel more formal and classical, making longform content flow more smoothly. An indent after a line break is redundant.
Incorrect (both indent and space):
p {
text-indent: 1.5em;
margin-bottom: 1.5em; /* double separation */
}Correct (line breaks only, the web default):
p {
margin-bottom: 1em;
text-indent: 0;
}Or indents only (longform/editorial):
p {
margin-bottom: 0;
text-indent: 1.5em;
}
p:first-of-type {
text-indent: 0; /* no indent on first paragraph */
}Place Subheadings Closer to Following Content
A subheading should have more space above it (separating from previous content) than below it (connecting to the content it introduces). This proximity principle groups the heading with its section. Use extra spacing above large subheaders.
Incorrect (equal spacing, heading floats between sections):
h2 {
margin-top: 1.5rem;
margin-bottom: 1.5rem;
}Correct (more space above, less below):
h2 {
margin-top: 2.5rem;
margin-bottom: 0.75rem;
}
h3 {
margin-top: 2rem;
margin-bottom: 0.5rem;
}The larger the heading, the more top margin it needs to establish visual separation from the preceding section.
Adjust Word Spacing with Letterspacing
When adding letterspacing to small text or uppercase, also increase word spacing proportionally. Otherwise words start to merge visually as the inter-letter gaps approach the inter-word gaps. If letterspacing small text, prefer increasing the font size instead.
Incorrect (letterspaced without word-spacing adjustment):
.small-caps-label {
font-variant-caps: all-small-caps;
letter-spacing: 0.15em;
/* words blur together at this tracking level */
}Correct (word-spacing increases with letter-spacing):
.small-caps-label {
font-variant-caps: all-small-caps;
letter-spacing: 0.15em;
word-spacing: 0.1em;
}Better yet, if the text is too small to read, increase the font size rather than adding letterspacing.
Related skills
How it compares
Pick typography-audit over ui-audit when the goal is deep typography linting—pairing, OpenType, and display type—rather than broad page-level UI quality checks.
FAQ
How many typography rules does typography-audit check?
typography-audit checks web projects against 90 typography rules organized into ten categories, from critical punctuation and font setup through OpenType features, hierarchy, and typeface pairing.
Does typography-audit redesign my type scale?
typography-audit audits existing CSS typography against best-practice rules and reports concrete fixes with file locations. typography-audit does not propose new scales or redesign brand typography systems.