CSS3 & Responsive Design

Responsive Typography

25 min Lesson 34 of 60

Why Typography Needs to Be Responsive

Typography is not just about choosing a nice font. On the web, text must be readable on a 4-inch phone screen, a 13-inch laptop, and a 34-inch ultrawide monitor. A heading that looks perfect at 48px on a desktop might overflow or dominate the entire screen on a phone. A body text size that feels comfortable on mobile might appear tiny and strained on a large monitor viewed from arm's length.

Responsive typography means your text automatically adjusts its size, spacing, and proportions based on the viewport and reading context. Done well, it creates a comfortable reading experience everywhere without requiring the user to pinch-zoom or squint. Done poorly -- or not at all -- it is one of the most common reasons websites feel broken on certain devices.

In this lesson, you will learn every technique for making typography responsive, from basic relative units to modern fluid typography with clamp(). By the end, you will be able to build a complete responsive type system that works seamlessly across all screen sizes.

Using Relative Units: rem and em

The foundation of responsive typography is using relative units instead of fixed pixel values. The two most important relative units for typography are rem and em.

rem: Root Em

A rem is relative to the root element's font size (the <html> element). By default, browsers set the root font size to 16px, so 1rem = 16px. The key advantage of rem is consistency: it always refers back to the same root value regardless of nesting.

Example: Using rem for Font Sizes

/* Browser default: html font-size is 16px */
/* So 1rem = 16px */

body {
    font-size: 1rem;      /* 16px */
    line-height: 1.6;
}

h1 {
    font-size: 2.5rem;    /* 40px */
    line-height: 1.2;
}

h2 {
    font-size: 2rem;      /* 32px */
    line-height: 1.3;
}

h3 {
    font-size: 1.5rem;    /* 24px */
    line-height: 1.4;
}

small {
    font-size: 0.875rem;  /* 14px */
}

/* Spacing with rem keeps proportions consistent */
.card {
    padding: 1.5rem;      /* 24px */
    margin-bottom: 2rem;  /* 32px */
}

/* If you change the root font size, everything scales */
html {
    font-size: 18px;  /* Now 1rem = 18px, all sizes adjust */
}

em: Relative to Parent

An em is relative to the font size of the element itself (or its parent, for properties other than font-size). This makes em perfect for component-internal spacing that should scale with the component's text size:

Example: Using em for Component-Relative Sizing

/* em is relative to the element's own font-size */
.button {
    font-size: 1rem;
    padding: 0.5em 1.5em;   /* Padding scales with font-size */
    border-radius: 0.25em;  /* Radius scales with font-size */
}

/* Same button, different sizes -- padding auto-adjusts */
.button--small {
    font-size: 0.875rem;    /* 14px -- padding becomes 7px 21px */
}

.button--large {
    font-size: 1.25rem;     /* 20px -- padding becomes 10px 30px */
}

/* Warning: em compounds when nested! */
.parent {
    font-size: 1.2em;       /* 1.2 * 16 = 19.2px */
}
.parent .child {
    font-size: 1.2em;       /* 1.2 * 19.2 = 23.04px (compounding!) */
}
.parent .child .grandchild {
    font-size: 1.2em;       /* 1.2 * 23.04 = 27.65px (keeps growing!) */
}
Common Mistake: Using em for font sizes in deeply nested elements causes compounding -- each level multiplies the parent's size. This is why rem is preferred for font sizes (it always refers to the root, no compounding). Use em for padding, margin, and border-radius within components where you want sizes to scale with the component's font size.

Setting a Base Font-Size on the HTML Element

The <html> element's font size is the anchor for all rem calculations. Setting it thoughtfully gives you a powerful control lever for your entire type system. There are several strategies:

Example: Base Font-Size Strategies

/* Strategy 1: Use the browser default (recommended starting point) */
html {
    font-size: 100%;  /* = 16px in most browsers */
}

/* Strategy 2: The 62.5% trick for easier math */
html {
    font-size: 62.5%;  /* 10px base: now 1rem = 10px */
}
body {
    font-size: 1.6rem;  /* Reset body to 16px equivalent */
}
h1 {
    font-size: 3.2rem;  /* 32px -- easy to calculate! */
}

/* Strategy 3: Slightly larger base for readability */
html {
    font-size: 112.5%;  /* 18px base */
}

/* Strategy 4: Adjust base at breakpoints */
html {
    font-size: 100%;     /* 16px on mobile */
}

@media (min-width: 768px) {
    html {
        font-size: 106.25%;  /* 17px on tablets */
    }
}

@media (min-width: 1024px) {
    html {
        font-size: 112.5%;   /* 18px on desktop */
    }
}

@media (min-width: 1400px) {
    html {
        font-size: 125%;     /* 20px on large screens */
    }
}
Pro Tip: Always use a percentage for the root font-size, never a fixed pixel value. Using html { font-size: 16px; } overrides the user's browser font-size setting, which many users with visual impairments rely on. Using html { font-size: 100%; } respects their preference while still giving you a predictable base for rem calculations.

Fluid Typography with clamp()

The clamp() function is the modern solution for fluid typography. It lets you set a font size that scales smoothly with the viewport while enforcing minimum and maximum boundaries. The syntax is:

clamp(minimum, preferred, maximum)

The browser uses the preferred value but never goes below the minimum or above the maximum. The preferred value typically includes a viewport unit (vw) so it scales with the viewport width.

Example: Fluid Typography with clamp()

/* Basic fluid font-size */
h1 {
    /* Min: 2rem (32px), Preferred: 5vw, Max: 4rem (64px) */
    font-size: clamp(2rem, 5vw, 4rem);
}

h2 {
    font-size: clamp(1.5rem, 3.5vw, 2.5rem);
}

h3 {
    font-size: clamp(1.25rem, 2.5vw, 1.75rem);
}

p {
    font-size: clamp(1rem, 1.2vw + 0.5rem, 1.25rem);
}

/* Complete fluid type scale */
:root {
    --text-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem);
    --text-sm: clamp(0.875rem, 0.8rem + 0.35vw, 1rem);
    --text-base: clamp(1rem, 0.9rem + 0.5vw, 1.125rem);
    --text-lg: clamp(1.125rem, 1rem + 0.6vw, 1.375rem);
    --text-xl: clamp(1.25rem, 1rem + 1.2vw, 1.75rem);
    --text-2xl: clamp(1.5rem, 1rem + 2vw, 2.25rem);
    --text-3xl: clamp(1.875rem, 1rem + 3.5vw, 3rem);
    --text-4xl: clamp(2.25rem, 1rem + 5vw, 4rem);
}

/* Use the custom properties */
body { font-size: var(--text-base); }
h1 { font-size: var(--text-4xl); }
h2 { font-size: var(--text-3xl); }
h3 { font-size: var(--text-2xl); }
h4 { font-size: var(--text-xl); }
.lead { font-size: var(--text-lg); }
.small { font-size: var(--text-sm); }
.caption { font-size: var(--text-xs); }
Note: The preferred value in clamp() works best when it combines a viewport unit with a fixed unit, like 1rem + 2vw. Using a viewport unit alone (like just 5vw) means the middle value has no minimum floor from the rem component, making the transition between the clamp boundaries less smooth.

The Old Approach: Media Queries for Font Sizes

Before clamp() was widely supported, developers used media queries to change font sizes at specific breakpoints. This approach still works and is useful when you need precise control at each breakpoint, but it creates "jumps" in size rather than smooth scaling:

Example: Breakpoint-Based Typography (Old Approach)

/* Mobile first: base sizes */
h1 { font-size: 1.75rem; }   /* 28px */
h2 { font-size: 1.5rem; }    /* 24px */
h3 { font-size: 1.25rem; }   /* 20px */
p  { font-size: 1rem; }      /* 16px */

/* Tablet */
@media (min-width: 768px) {
    h1 { font-size: 2.25rem; }   /* 36px */
    h2 { font-size: 1.75rem; }   /* 28px */
    h3 { font-size: 1.5rem; }    /* 24px */
    p  { font-size: 1.0625rem; } /* 17px */
}

/* Desktop */
@media (min-width: 1024px) {
    h1 { font-size: 3rem; }      /* 48px */
    h2 { font-size: 2.25rem; }   /* 36px */
    h3 { font-size: 1.75rem; }   /* 28px */
    p  { font-size: 1.125rem; }  /* 18px */
}

/* Large desktop */
@media (min-width: 1400px) {
    h1 { font-size: 3.5rem; }    /* 56px */
    h2 { font-size: 2.5rem; }    /* 40px */
    h3 { font-size: 2rem; }      /* 32px */
}

The disadvantage of this approach is clear: at 767px the heading is 28px, and at 768px it suddenly jumps to 36px. There is no smooth transition. The clamp() approach eliminates these jumps entirely.

Modular Type Scales

A modular type scale uses a consistent ratio to generate a harmonious set of font sizes. Instead of picking arbitrary sizes, you multiply a base size by a ratio to get each step in the scale. This creates visual rhythm and hierarchy that feels intentional and balanced.

Example: Implementing a Modular Type Scale

/* Common type scale ratios:
   Minor Second:  1.067
   Major Second:  1.125
   Minor Third:   1.2
   Major Third:   1.25
   Perfect Fourth: 1.333
   Augmented Fourth: 1.414
   Perfect Fifth: 1.5
   Golden Ratio:  1.618
*/

/* Example: Major Third scale (ratio 1.25) with base 1rem */
/* Each step: previous * 1.25 */
:root {
    --scale-ratio: 1.25;
    --text-sm: 0.8rem;          /* 1 / 1.25 */
    --text-base: 1rem;           /* base */
    --text-md: 1.25rem;          /* 1 * 1.25 */
    --text-lg: 1.563rem;         /* 1.25 * 1.25 */
    --text-xl: 1.953rem;         /* 1.563 * 1.25 */
    --text-2xl: 2.441rem;        /* 1.953 * 1.25 */
    --text-3xl: 3.052rem;        /* 2.441 * 1.25 */
}

/* Apply the scale */
body { font-size: var(--text-base); }
h1 { font-size: var(--text-3xl); }
h2 { font-size: var(--text-2xl); }
h3 { font-size: var(--text-xl); }
h4 { font-size: var(--text-lg); }
h5 { font-size: var(--text-md); }
small { font-size: var(--text-sm); }

/* Responsive: use a tighter ratio on small screens */
/* Minor Third (1.2) for mobile, Major Third (1.25) for desktop */
:root {
    --text-sm: 0.833rem;
    --text-base: 1rem;
    --text-md: 1.2rem;
    --text-lg: 1.44rem;
    --text-xl: 1.728rem;
    --text-2xl: 2.074rem;
    --text-3xl: 2.488rem;
}

@media (min-width: 1024px) {
    :root {
        --text-sm: 0.8rem;
        --text-base: 1rem;
        --text-md: 1.25rem;
        --text-lg: 1.563rem;
        --text-xl: 1.953rem;
        --text-2xl: 2.441rem;
        --text-3xl: 3.052rem;
    }
}
Pro Tip: You can combine modular scales with clamp() for the best of both worlds. Use the mobile scale values as the minimum, the desktop scale values as the maximum, and a viewport-based calculation as the preferred value. This gives you harmonious sizing at every viewport width.

Line Length (Measure): The 45-75 Character Rule

Line length, also called "measure" in typographic terms, is one of the most overlooked aspects of responsive design. Research consistently shows that lines of text between 45 and 75 characters (including spaces) provide the most comfortable reading experience. Lines shorter than 45 characters cause the eye to jump back too frequently. Lines longer than 75 characters make it difficult to find the beginning of the next line.

Example: Controlling Line Length with ch Units

/* The ch unit = width of the "0" character in the current font */
/* It provides an approximation of character count per line */

/* Ideal reading width */
.prose {
    max-width: 65ch;  /* Approximately 65 characters per line */
    margin: 0 auto;
}

/* Wider for technical content with code examples */
.technical-content {
    max-width: 75ch;
}

/* Narrower for large text like pull quotes */
blockquote.pull-quote {
    max-width: 45ch;
    font-size: var(--text-xl);
    margin: 2rem auto;
}

/* Responsive line length */
.article-body {
    max-width: 65ch;
    padding: 0 1rem;
    margin: 0 auto;
}

/* On very wide screens, center the content */
@media (min-width: 1200px) {
    .article-body {
        max-width: 70ch;
        padding: 0;
    }
}

/* Different widths for different elements */
.card p {
    max-width: 45ch;  /* Cards are narrower */
}

.hero p {
    max-width: 55ch;  /* Hero text slightly wider */
    font-size: var(--text-lg);
}
Note: The ch unit is based on the width of the "0" glyph, not an average character width. In proportional fonts, the actual character count per line will vary. Use ch as a practical approximation rather than an exact character counter. For most body fonts, 65ch produces lines of roughly 60-70 characters, which falls well within the ideal range.

Responsive Line-Height Adjustments

Line height (leading) affects readability just as much as font size. The ideal line height changes based on the font size, line length, and screen size. Larger text needs proportionally less line height, while smaller text on narrow screens needs more breathing room.

Example: Responsive Line-Height

/* Base line-height guidelines:
   Body text: 1.5 - 1.7
   Headings: 1.1 - 1.3
   Small text: 1.6 - 1.8
   Large display text: 1.0 - 1.15
*/

body {
    line-height: 1.6;  /* Good default for body text */
}

h1 {
    font-size: clamp(2rem, 5vw, 4rem);
    line-height: 1.1;  /* Tight for large headings */
}

h2 {
    font-size: clamp(1.5rem, 3.5vw, 2.5rem);
    line-height: 1.2;
}

h3 {
    font-size: clamp(1.25rem, 2.5vw, 1.75rem);
    line-height: 1.3;
}

p {
    font-size: clamp(1rem, 1.2vw + 0.5rem, 1.25rem);
    line-height: 1.6;
}

/* Fluid line-height using clamp */
.article-body {
    /* Tighter line-height on large screens (where lines are longer),
       more spacious on small screens */
    line-height: clamp(1.5, 1.3 + 0.8vw, 1.7);
}

/* Adjust line-height for different contexts */
.narrow-column p {
    line-height: 1.5;  /* Shorter lines need less leading */
}

.wide-column p {
    line-height: 1.8;  /* Longer lines need more leading */
}

/* Compact line-height for UI elements */
.nav-item {
    line-height: 1.2;
}

.button {
    line-height: 1;  /* Vertically center single-line button text */
}

Heading Hierarchy Across Screen Sizes

A clear heading hierarchy is essential for both readability and accessibility. On large screens, you can afford dramatic size differences between heading levels. On small screens, the differences need to be subtler because there is less room. The key is maintaining a visible hierarchy at every breakpoint while never letting headings become too large for the viewport.

Example: Responsive Heading Hierarchy

/* Using clamp for smooth responsive headings */
h1 {
    font-size: clamp(1.875rem, 1.5rem + 2.5vw, 3.5rem);
    font-weight: 800;
    line-height: 1.1;
    letter-spacing: -0.02em;
    margin-bottom: 0.5em;
}

h2 {
    font-size: clamp(1.5rem, 1.2rem + 1.8vw, 2.5rem);
    font-weight: 700;
    line-height: 1.2;
    letter-spacing: -0.01em;
    margin-top: 2em;
    margin-bottom: 0.5em;
}

h3 {
    font-size: clamp(1.25rem, 1.1rem + 1vw, 1.875rem);
    font-weight: 600;
    line-height: 1.3;
    margin-top: 1.5em;
    margin-bottom: 0.4em;
}

h4 {
    font-size: clamp(1.125rem, 1rem + 0.6vw, 1.5rem);
    font-weight: 600;
    line-height: 1.4;
    margin-top: 1.25em;
    margin-bottom: 0.3em;
}

h5 {
    font-size: clamp(1rem, 0.95rem + 0.3vw, 1.25rem);
    font-weight: 600;
    line-height: 1.4;
    margin-top: 1em;
    margin-bottom: 0.3em;
}

h6 {
    font-size: clamp(0.875rem, 0.85rem + 0.15vw, 1rem);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    line-height: 1.4;
    margin-top: 1em;
    margin-bottom: 0.3em;
}

/* Display headings for hero sections */
.display-1 {
    font-size: clamp(2.5rem, 2rem + 4vw, 5rem);
    font-weight: 900;
    line-height: 1.05;
    letter-spacing: -0.03em;
}

.display-2 {
    font-size: clamp(2rem, 1.5rem + 3vw, 4rem);
    font-weight: 800;
    line-height: 1.1;
    letter-spacing: -0.02em;
}

Viewport Units for Typography and the Accessibility Problem

Viewport units (vw, vh) can be used directly for font sizes, creating text that scales linearly with the viewport. However, using viewport units alone creates a serious accessibility problem.

Example: Viewport Units and Their Problems

/* Using vw alone -- DON'T do this */
h1 {
    font-size: 5vw;
    /* At 320px viewport: 16px (too small!) */
    /* At 1920px viewport: 96px (too large!) */
    /* PROBLEM: Cannot be zoomed by the user! */
    /* Browser text zoom only affects px, em, rem -- not vw */
}

/* Why this is an accessibility failure: */
/* Users who set a larger font size in browser settings */
/* will see NO change because vw ignores their preference */
/* WCAG 1.4.4 requires text to be resizable up to 200% */

/* The correct way: combine vw with a relative unit */
h1 {
    font-size: calc(1.5rem + 3vw);
    /* The rem part responds to user zoom/settings */
    /* The vw part adds viewport-based scaling */
    /* At 320px: 24px + 9.6px = 33.6px */
    /* At 1920px: 24px + 57.6px = 81.6px */
    /* User can still zoom the rem part */
}

/* Even better: use clamp to set boundaries */
h1 {
    font-size: clamp(2rem, 1.5rem + 3vw, 4.5rem);
    /* Accessible: the rem values respond to zoom */
    /* Bounded: never too small or too large */
}
Accessibility Warning: Never use viewport units (vw, vh) as the sole unit for font sizes. When text is sized purely in viewport units, users cannot resize it using browser zoom or font-size settings. This violates WCAG Success Criterion 1.4.4 (Resize Text). Always combine viewport units with rem or em, or use clamp() with rem-based minimums and maximums.

calc() for Fluid Typography (The Pre-clamp Approach)

Before clamp() was supported, developers used calc() to create fluid typography. This technique is sometimes called "CSS Locks" because it locks the font size between a minimum and maximum value. Understanding it is valuable for maintaining legacy code and for grasping the math behind fluid typography.

Example: Fluid Typography with calc()

/* The "CSS Locks" formula:
   font-size: calc(minSize + (maxSize - minSize) *
              ((100vw - minViewport) / (maxViewport - minViewport)));
*/

/* Example: Scale from 16px at 320px viewport to 24px at 1200px viewport */
h2 {
    /* Below 320px: fixed at 1rem */
    font-size: 1rem;
}

@media (min-width: 320px) {
    h2 {
        /* Fluid between 320px and 1200px */
        font-size: calc(1rem + (1.5 - 1) * ((100vw - 20rem) / (75 - 20)));
        /* Simplified: */
        font-size: calc(1rem + 0.5 * ((100vw - 20rem) / 55));
    }
}

@media (min-width: 1200px) {
    h2 {
        /* Above 1200px: fixed at 1.5rem */
        font-size: 1.5rem;
    }
}

/* The modern equivalent with clamp -- much simpler! */
h2 {
    font-size: clamp(1rem, 0.727rem + 0.909vw, 1.5rem);
    /* Same result, one line, no media queries needed */
}

/* Generating the clamp preferred value:
   preferred = minSize - (minViewport * slope) + slope * 100vw
   where slope = (maxSize - minSize) / (maxViewport - minViewport)
   slope = (24 - 16) / (1200 - 320) = 8/880 = 0.00909
   0.00909 * 100 = 0.909vw
   intercept = 16 - (320 * 0.00909) = 16 - 2.909 = 13.09px = 0.818rem
   Result: clamp(1rem, 0.818rem + 0.909vw, 1.5rem)
*/

System Font Stacks for Performance

Font loading directly impacts typography responsiveness. Custom web fonts require downloads that can cause layout shifts and delayed text rendering. System font stacks use fonts already installed on the user's device, providing instant rendering with zero download cost.

Example: System Font Stacks

/* Modern system font stack -- used by GitHub, Bootstrap, etc. */
body {
    font-family:
        system-ui,            /* Modern browsers: OS default */
        -apple-system,        /* Safari on macOS and iOS */
        BlinkMacSystemFont,   /* Chrome on macOS */
        "Segoe UI",           /* Windows */
        Roboto,               /* Android */
        "Helvetica Neue",     /* Older macOS */
        Arial,                /* Universal fallback */
        sans-serif;           /* Generic fallback */
}

/* System monospace stack for code */
code, pre, kbd {
    font-family:
        ui-monospace,           /* Modern browsers */
        SFMono-Regular,         /* Safari */
        "SF Mono",              /* macOS */
        Menlo,                  /* macOS older */
        Consolas,               /* Windows */
        "Liberation Mono",     /* Linux */
        "Courier New",          /* Universal */
        monospace;              /* Generic fallback */
}

/* System serif stack */
.article-body {
    font-family:
        ui-serif,
        Georgia,
        Cambria,
        "Times New Roman",
        Times,
        serif;
}

/* If you must use custom fonts, always include a system fallback */
body {
    font-family: "Inter", system-ui, -apple-system, sans-serif;
}

h1, h2, h3 {
    font-family: "Playfair Display", ui-serif, Georgia, serif;
}

Font Loading and Layout Shift (CLS)

When using custom web fonts, the browser must download the font file before it can render text. During this loading period, text can either be invisible (FOIT -- Flash of Invisible Text) or display in a fallback font (FOUT -- Flash of Unstyled Text). Both cause Cumulative Layout Shift (CLS), a key Core Web Vital metric that affects user experience and SEO.

Example: Optimizing Font Loading

/* 1. Use font-display to control loading behavior */
@font-face {
    font-family: "Inter";
    src: url("/fonts/inter-regular.woff2") format("woff2");
    font-weight: 400;
    font-style: normal;
    font-display: swap;  /* Show fallback immediately, swap when loaded */
}

@font-face {
    font-family: "Inter";
    src: url("/fonts/inter-bold.woff2") format("woff2");
    font-weight: 700;
    font-style: normal;
    font-display: swap;
}

/* font-display values:
   auto     -- browser decides (usually FOIT)
   block    -- invisible text for up to 3s, then fallback
   swap     -- fallback immediately, swap when font loads (recommended)
   fallback -- very short invisible period, then fallback permanently
   optional -- tiny invisible period, may skip custom font entirely
*/

/* 2. Match fallback font metrics to reduce layout shift */
/* Use size-adjust and other descriptors */
@font-face {
    font-family: "Inter Fallback";
    src: local("Arial");
    size-adjust: 107%;
    ascent-override: 90%;
    descent-override: 22%;
    line-gap-override: 0%;
}

body {
    font-family: "Inter", "Inter Fallback", sans-serif;
}

Example: Preloading Critical Fonts in HTML

/* In your HTML <head>, preload fonts used above the fold */
/* <link rel="preload" href="/fonts/inter-regular.woff2" */
/*       as="font" type="font/woff2" crossorigin> */

/* Only preload 1-2 critical font files (regular and bold). */
/* Preloading too many fonts wastes bandwidth. */

/* Use <link rel="preconnect"> for third-party font services */
/* <link rel="preconnect" href="https://fonts.googleapis.com"> */
/* <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> */
Pro Tip: The best font performance comes from self-hosting WOFF2 files and using font-display: swap with carefully matched fallback fonts. This combination gives users instant readable text with minimal layout shift when the custom font loads. Use tools like Fontaine or Capsize to automatically calculate the size-adjust and ascent-override values for your fallback font.

Practical Responsive Typography System

Let us bring everything together into a complete, production-ready responsive typography system. This system uses CSS custom properties for the type scale, clamp() for fluid sizing, proper line lengths, and sensible defaults.

Example: Complete Responsive Typography System

/* ========================================
   RESPONSIVE TYPOGRAPHY SYSTEM
   ======================================== */

/* 1. Root Configuration */
html {
    /* Respect user preferences, use percentage */
    font-size: 100%;

    /* Enable font smoothing */
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;

    /* Prevent text inflation on mobile */
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
}

/* 2. Fluid Type Scale using clamp() */
:root {
    /* Font families */
    --font-sans: system-ui, -apple-system, BlinkMacSystemFont,
                 "Segoe UI", Roboto, sans-serif;
    --font-serif: ui-serif, Georgia, Cambria, serif;
    --font-mono: ui-monospace, SFMono-Regular, "SF Mono",
                 Menlo, Consolas, monospace;

    /* Type scale: fluid sizes (mobile min -> desktop max) */
    --step--2: clamp(0.694rem, 0.66rem + 0.17vw, 0.8rem);
    --step--1: clamp(0.833rem, 0.78rem + 0.27vw, 1rem);
    --step-0:  clamp(1rem, 0.92rem + 0.39vw, 1.25rem);
    --step-1:  clamp(1.2rem, 1.09rem + 0.57vw, 1.563rem);
    --step-2:  clamp(1.44rem, 1.28rem + 0.8vw, 1.953rem);
    --step-3:  clamp(1.728rem, 1.5rem + 1.14vw, 2.441rem);
    --step-4:  clamp(2.074rem, 1.75rem + 1.62vw, 3.052rem);
    --step-5:  clamp(2.488rem, 2.04rem + 2.24vw, 3.815rem);

    /* Line heights */
    --leading-tight: 1.1;
    --leading-snug: 1.3;
    --leading-normal: 1.6;
    --leading-relaxed: 1.75;

    /* Spacing based on type */
    --space-xs: 0.25em;
    --space-sm: 0.5em;
    --space-md: 1em;
    --space-lg: 1.5em;
    --space-xl: 2em;

    /* Measure (line length) */
    --measure-narrow: 45ch;
    --measure-normal: 65ch;
    --measure-wide: 80ch;
}

/* 3. Base Typography */
body {
    font-family: var(--font-sans);
    font-size: var(--step-0);
    line-height: var(--leading-normal);
    color: var(--text-dark, #1a1a1a);
}

/* 4. Headings */
h1, h2, h3, h4, h5, h6 {
    font-weight: 700;
    text-wrap: balance;  /* Better line breaks for headings */
}

h1 {
    font-size: var(--step-5);
    line-height: var(--leading-tight);
    letter-spacing: -0.02em;
    margin-bottom: var(--space-sm);
}

h2 {
    font-size: var(--step-4);
    line-height: var(--leading-tight);
    letter-spacing: -0.015em;
    margin-top: var(--space-xl);
    margin-bottom: var(--space-sm);
}

h3 {
    font-size: var(--step-3);
    line-height: var(--leading-snug);
    letter-spacing: -0.01em;
    margin-top: var(--space-lg);
    margin-bottom: var(--space-xs);
}

h4 {
    font-size: var(--step-2);
    line-height: var(--leading-snug);
    margin-top: var(--space-lg);
    margin-bottom: var(--space-xs);
}

h5 {
    font-size: var(--step-1);
    line-height: var(--leading-snug);
    margin-top: var(--space-md);
}

h6 {
    font-size: var(--step-0);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-top: var(--space-md);
}

/* 5. Body Text */
p {
    max-width: var(--measure-normal);
    margin-bottom: var(--space-md);
}

.lead {
    font-size: var(--step-1);
    line-height: var(--leading-relaxed);
    max-width: var(--measure-narrow);
}

small, .text-sm {
    font-size: var(--step--1);
}

.text-xs {
    font-size: var(--step--2);
}

/* 6. Prose Container */
.prose {
    max-width: var(--measure-normal);
    margin-inline: auto;
}

.prose p + p {
    margin-top: var(--space-md);
}

.prose h2 + p,
.prose h3 + p {
    margin-top: var(--space-sm);
}

/* 7. Code */
code {
    font-family: var(--font-mono);
    font-size: 0.9em;  /* Slightly smaller than surrounding text */
}

pre {
    font-family: var(--font-mono);
    font-size: var(--step--1);
    line-height: 1.5;
    max-width: var(--measure-wide);
    overflow-x: auto;
}

/* 8. Blockquotes */
blockquote {
    font-size: var(--step-1);
    font-style: italic;
    line-height: var(--leading-relaxed);
    max-width: var(--measure-narrow);
    margin: var(--space-xl) auto;
    padding-left: var(--space-md);
    border-left: 3px solid currentColor;
}

/* 9. Responsive Adjustments */
@media (prefers-reduced-motion: reduce) {
    * {
        transition: none !important;
    }
}

/* 10. Print Typography */
@media print {
    body {
        font-size: 12pt;
        line-height: 1.5;
        font-family: Georgia, serif;
    }

    h1 { font-size: 24pt; }
    h2 { font-size: 20pt; }
    h3 { font-size: 16pt; }
    h4 { font-size: 14pt; }

    p, li {
        max-width: none;
        orphans: 3;
        widows: 3;
    }

    h1, h2, h3 {
        page-break-after: avoid;
    }
}
Note: The text-wrap: balance property is a newer CSS feature that distributes text more evenly across lines in headings, preventing awkward single-word last lines. It is supported in Chrome, Edge, and Firefox. Always apply it to headings rather than body text, as it can be computationally expensive on long paragraphs.

Practice Exercise

Build a complete responsive typography system for a blog. Start by defining a fluid type scale using clamp() with at least 6 size steps stored in CSS custom properties. Set the root font-size to 100% to respect user preferences. Apply your type scale to all heading levels (h1 through h6) with appropriate line-heights -- tighter for large headings, more relaxed for body text. Set a max-width of 65ch on your prose container for optimal line length. Add a system font stack as the default and a monospace system stack for code elements. Create a .lead class for introductory paragraphs that is slightly larger than body text. Test your system by viewing the page at 320px, 768px, 1024px, and 1920px widths -- the text should scale smoothly without any jarring size jumps. Verify accessibility by using your browser's zoom feature to increase text size to 200%; all text should remain readable and no content should overflow or be cut off. Finally, add a print stylesheet that uses point-based sizes appropriate for paper.