top of page
logo-2-color_2x.png

Responsive Typography

Points: 

5

Due By:

February 20, 2026 at 5:45:00 AM

Overview

You will build a blog article page with a fully working modular type system — no media queries. By the end of the session, type will scale fluidly across any screen size, and the article will adapt its heading sizes based on its own container width, not the browser window.




Learning Objectives

By the end of this lesson, you will be able to:

  • Explain why viewport-based media queries are often the wrong tool for typography

  • Calculate a modular type scale using a ratio

  • Use clamp() to create fluid font sizes that scale between a minimum and maximum

  • Define and apply a type system using CSS custom properties

  • Write container queries that respond to an element's own width




Deliverable

👉 Please uploaded completed, fully functional HTML and CSS to Canvas.





Starting Files

Please download and extract the following files to proceed:





Snippets



Quick Reference — Scale at a Glance

Step

Approx. Size

Use It For

--step-5

~4rem

Site title / hero heading

--step-4

~3rem

Article title (h2)

--step-3

~2.4rem

Section heading (h3)

--step-2

~1.8rem

Sub-heading (h4), blockquote

--step-1

~1.1rem

Byline, tags, captions, footer

--step-0

~1rem

Body text, list items





1 - Import Your Fonts


Paste this inside the <head> of your HTML, above your <style> tag. This loads two fonts from Google Fonts — one for body text (Lora, a serif) and one for UI elements (DM Sans, a sans-serif).

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Lora:ital,wght@0,400;0,600;1,400&family=DM+Sans:wght@400;500&family=DM+Mono&display=swap" rel="stylesheet">

2 - Build The Type Scale

This goes inside :root. These are your custom properties — the entire type system lives here. Every font size in the rest of the file will reference one of these steps.


:root {
  font-size: 1rem; /* Fixed anchor. Do not change. */

  /* Modular scale — Perfect Fourth (1.333) */
  --step-0: 1rem;
  --step-1: clamp(0.889rem,  0.3vw  + 0.814rem, 1.111rem);
  --step-2: clamp(1.185rem,  0.65vw + 1rem,      1.481rem);
  --step-3: clamp(1.481rem,  1.1vw  + 1.2rem,    2.369rem);
  --step-4: clamp(1.777rem,  2vw    + 1.25rem,   3.157rem);
  --step-5: clamp(2.369rem,  3.5vw  + 1.5rem,    4.209rem);

  /* Font families */
  --font-body:  'Lora', Georgia, serif;
  --font-ui:    'DM Sans', system-ui, sans-serif;
  --font-mono:  'DM Mono', 'Courier New', monospace;
}

Each clamp() has three parts: a minimum, a fluid middle value, and a maximum. The middle value combines vw (grows with the viewport) and rem (prevents it from ever collapsing to zero).



3 - Header Type

This targets the h1 and subtitle p inside the <header>.


header h1 {
  font-family: var(--font-body);
  font-size: var(--step-5);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.02em;
}

header p {
  font-family: var(--font-ui);
  font-size: var(--step-1);
  color: var(--color-rule);
  margin-top: var(--space-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

Large headings need a tighter line-height (around 1.1) because the optical gap between lines grows with type size. Negative letter-spacing counteracts the optical looseness that happens at large sizes.



4 - Article Headings

h2 {
  font-family: var(--font-body);
  font-size: var(--step-4);
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.02em;
  margin-top: var(--space-lg);
  margin-bottom: var(--space-sm);
}

h3 {
  font-family: var(--font-body);
  font-size: var(--step-3);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.015em;
  margin-top: var(--space-md);
  margin-bottom: var(--space-xs);
}

h4 {
  font-family: var(--font-ui);
  font-size: var(--step-2);
  font-weight: 500;
  line-height: 1.3;
  margin-top: var(--space-md);
  margin-bottom: var(--space-xs);
}

Notice h4 switches to --font-ui. Mixing a serif and sans-serif across heading levels creates a clear hierarchy without relying on size alone.



5 - Body Text & Drop Cap

p {
  font-family: var(--font-body);
  font-size: var(--step-0);
  max-width: var(--measure-narrow);
  margin-bottom: var(--space-sm);
}

/* Bonus: drop cap on the first paragraph */
article > p:first-of-type::first-letter {
  font-size: 3.5em;
  font-weight: 600;
  line-height: 0.8;
  float: left;
  margin-right: 0.1em;
  margin-top: 0.08em;
  color: var(--color-accent);
}

max-width: var(--measure-narrow) keeps lines at roughly 52 characters — the sweet spot for comfortable reading. The drop cap uses em units so it scales relative to the paragraph's own font size.




6 - Byline & Tags

.byline {
  font-family: var(--font-ui);
  font-size: var(--step-1);
  color: var(--color-ink-muted);
  margin-bottom: var(--space-xs);
  max-width: none;
}

.tags li a {
  font-family: var(--font-ui);
  font-size: var(--step-1);
  letter-spacing: 0.04em;
  text-decoration: none;
  background-color: var(--color-tag-bg);
  color: var(--color-tag-ink);
  padding: 0.25em 0.75em;
  border-radius: 2px;
  transition: background-color 0.15s ease, color 0.15s ease;
}

Both byline and tags sit one step below body text (--step-1). Supporting text should never compete with the content hierarchy.




7 - Blockquote

blockquote {
  font-family: var(--font-body);
  font-size: var(--step-2);
  font-style: italic;
  line-height: 1.5;
  color: var(--color-ink-muted);
  border-left: 3px solid var(--color-accent);
  padding: var(--space-sm) var(--space-md);
  margin: var(--space-md) 0;
  background-color: var(--color-accent-bg);
  max-width: var(--measure-narrow);
}


The blockquote steps up to --step-2 so it has presence as a pulled quote. font-style: italic reinforces that it's a voice distinct from the body.




8 - List Items

ul:not(.tags) li {
  font-size: var(--step-0);
  margin-bottom: 0.4em;
  max-width: var(--measure-narrow);
}

List items match body text size (--step-0). The :not(.tags) selector ensures this rule doesn't accidentally apply to the tag links at the top of the article.



9 - Inline Code

code {
  font-family: var(--font-mono);
  font-size: 0.875em;
  background-color: var(--color-tag-bg);
  padding: 0.1em 0.4em;
  border-radius: 3px;
  color: var(--color-accent);
}


font-size: 0.875em is relative to the parent — not a --step-* value. Monospace fonts read visually larger than proportional fonts at the same size, so we nudge them down slightly. Using em here means it works correctly whether the code is inside a heading or a paragraph.




10 - Figcaption

figcaption {
  font-family: var(--font-ui);
  font-size: var(--step-1);
  font-style: italic;
  color: var(--color-ink-muted);
  margin-top: var(--space-xs);
  padding-left: 0.5em;
  border-left: 2px solid var(--color-rule);
}

Captions use --step-1 — the same as bylines and tags. Grouping supporting text on the same scale step creates visual consistency across the page.



11 - Container Queries

These rules fire based on the article element's own width — not the browser window. The three blocks handle narrow, mid, and wide containers.



/* Narrow container — drop headings one scale step */
@container article (max-width: 480px) {
  h2 { font-size: var(--step-3); }
  h3 { font-size: var(--step-2); }
  h4 { font-size: var(--step-1); }

  blockquote {
    font-size: var(--step-1);
    padding: var(--space-sm);
  }
}

/* Mid-width — soften h2 and blockquote slightly */
@container article (min-width: 481px) and (max-width: 680px) {
  h2 { font-size: var(--step-3); letter-spacing: -0.015em; }
  blockquote { font-size: var(--step-1); }
}

/* Wide — let the drop cap breathe */
@container article (min-width: 681px) {
  article > p:first-of-type::first-letter {
    font-size: 4.5em;
  }
}

clamp() scales type relative to the viewport. Container queries make decisions relative to the component itself. They work together — clamp() handles the smooth scaling, container queries handle the contextual adjustments.




12 - Footer

footer p {
  font-family: var(--font-ui);
  font-size: var(--step-1);
  max-width: none;
  margin-bottom: 0;
}


Footer text sits at --step-1 — readable but clearly subordinate to the main content.

Locked Message

bottom of page