
How to choose heading sizes for a website?
- Sajjad
- Typography
- 11 Oct, 2026
To choose heading sizes for a website, start from your body text size, decide how many heading levels you actually use, and give each level a size that is clearly larger than the one below it, typically by a ratio of about 1.2 to 1.33. For a site with 16px body text, a common result is roughly 20px for h4, 24px for h3, 30px for h2 and 36 to 40px for h1 on desktop, with the larger headings scaled down on mobile so that an h1 sits around 28 to 32px. Then make the sizes fluid with clamp(), tighten heading line heights to around 1.1 to 1.3, and check them with real headlines at every screen width. The goal is a set of sizes that are distinct at a glance, fit the content, and stay in proportion on every device.
Headings carry more weight than any other text on a page. They're what readers scan first, what search results and social cards often show, and what screen reader users jump between. If they're too similar to body text, the structure disappears; if they're too large, a single headline can fill a phone screen. In this article you'll learn a step-by-step method for choosing heading sizes, sensible starting values for mobile and desktop, how to make them fluid without hurting accessibility, how to tune weight, line height and length, how browser defaults behave, and how to set heading sizes in WordPress.
Step 1: Start From Body Text
Every heading size is relative to body text, so settle that first. Most websites use 16px (1rem) to 18px for body copy, and text-heavy sites sometimes go up to 20px. If you're unsure, start with the browser default of 16px.
The smallest heading needs to be visibly different from body text. A heading only 1 or 2px larger than body text won't register as a heading; it will look like a slightly odd paragraph. If your smallest heading is close to body size, rely on weight to set it apart.
Step 2: Decide How Many Levels You Use
Look at your real content. Most pages use far fewer than six heading levels:
- Marketing pages: Usually
h1andh2, sometimesh3for feature cards. - Blog posts:
h1for the title,h2for sections,h3for subsections. - Documentation: Often four levels, because pages are long and deeply structured.
- Web apps: A page title and one or two levels of panel headings.
Design sizes for the levels you use, and give the rest sensible defaults. If h5 and h6 never appear, they don't need distinct visual sizes; many sites style them the same size as body text and distinguish them with weight or capitals.
Step 3: Pick a Ratio and Calculate Sizes
A consistent ratio between levels keeps the steps even. You can derive sizes from a full modular type scale, or simply multiply up from body text. For a 16px base:
| Level | Ratio 1.2 | Ratio 1.25 | Ratio 1.333 |
|---|---|---|---|
| Body | 16px | 16px | 16px |
h4 | 19.2px | 20px | 21.3px |
h3 | 23px | 25px | 28.4px |
h2 | 27.6px | 31.3px | 37.9px |
h1 | 33.2px | 39.1px | 50.5px |
Round to whole pixels or convenient rem values if you like. A blog might choose the 1.25 column; a landing page with only two heading levels might push h1 further for drama; a dense interface might use 1.2 or less.
Sizes for Mobile and Desktop
Large headings need to shrink on small screens, but small headings and body text should stay about the same. A good pattern is to use a gentler ratio on mobile, so the top of the hierarchy compresses while the bottom barely changes:
| Level | Mobile (about 375px wide) | Desktop (about 1280px wide) |
|---|---|---|
| Body | 16px | 16 to 18px |
h4 | 18px | 20px |
h3 | 20px | 24px |
h2 | 24px | 30 to 32px |
h1 | 28 to 32px | 36 to 48px |
| Hero display | 36 to 40px | 56 to 72px |
These are starting points, not rules. A news site with long headlines needs smaller h1 sizes than a portfolio with a two-word title.
Step 4: Make Heading Sizes Fluid
Instead of jumping between mobile and desktop sizes at a breakpoint, use clamp() to scale smoothly between them. Each value takes a minimum, a preferred value that grows with the viewport, and a maximum:
:root {
--h1: clamp(1.75rem, 1.3333rem + 2.0833vw, 3rem); /* 28px → 48px */
--h2: clamp(1.5rem, 1.3333rem + 0.8333vw, 2rem); /* 24px → 32px */
--h3: clamp(1.25rem, 1.1667rem + 0.4167vw, 1.5rem); /* 20px → 24px */
--h4: clamp(1.125rem, 1.0833rem + 0.2083vw, 1.25rem); /* 18px → 20px */
}
h1 { font-size: var(--h1); }
h2 { font-size: var(--h2); }
h3 { font-size: var(--h3); }
h4 { font-size: var(--h4); }
h5,
h6 { font-size: 1rem; }
With these values, every heading is at its minimum size at a viewport width of 320px and at its maximum at 1280px. In between, each one scales linearly.
Two accessibility details matter here:
- Keep a
remterm in the preferred value: A size defined only invwignores the reader's browser font size setting. WCAG Success Criterion 1.4.4 Resize Text requires that text can be resized up to 200 percent without loss of content, and pure viewport units can stop that from working. - Use
remfor the minimum and maximum: That way the limits also scale with the reader's settings.
To calculate values like these for your own sizes, this small function returns a clamp() expression for any minimum and maximum size:
function fluidSize(minPx, maxPx, minVw = 320, maxVw = 1280) {
const slope = (maxPx - minPx) / (maxVw - minVw);
const intercept = minPx - slope * minVw;
const rem = (px) => `${+(px / 16).toFixed(4)}rem`;
return `clamp(${rem(minPx)}, ${rem(intercept)} + ${+(slope * 100).toFixed(4)}vw, ${rem(maxPx)})`;
}
console.log(fluidSize(28, 48)); // h1
console.log(fluidSize(20, 24)); // h3
clamp(1.75rem, 1.3333rem + 2.0833vw, 3rem)
clamp(1.25rem, 1.1667rem + 0.4167vw, 1.5rem)
Change the default viewport range if you want headings to reach their full size sooner, for example at 1024px instead of 1280px.
Step 5: Tune Line Height, Weight and Spacing
Size is only part of a heading's presence. The same 32px heading can look delicate or heavy depending on how you set it.
Line Height
Large text needs less line height than body text. If your body text uses 1.5 to 1.6, use around 1.1 to 1.3 for headings, getting tighter as size increases:
h1 { line-height: 1.1; }
h2 { line-height: 1.2; }
h3,
h4 { line-height: 1.3; }
A heading at 48px with a line height of 1.5 has 72px lines, which makes a two-line headline look like two separate headings.
Weight
Bigger headings can afford lighter weights, because size already gives them prominence. Smaller headings often need extra weight to stand apart from body text:
h1,
h2 { font-weight: 700; }
h3,
h4 { font-weight: 600; }
h5,
h6 {
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
}
With a variable font, you can fine-tune this, for example using 650 for h3 if 600 looks too light and 700 too heavy.
Letter Spacing
Many fonts look slightly loose at large sizes. A small negative letter spacing on the largest headings can tighten them up:
h1 {
letter-spacing: -0.02em;
}
Keep it subtle, and check it with your font. Some display fonts are already spaced for large sizes and need no adjustment.
Spacing Around Headings
Put more space above a heading than below it, so it groups with the content it introduces. Using em ties the space to the heading's own size:
h2 {
margin-block: 1.75em 0.5em;
}
h3 {
margin-block: 1.5em 0.5em;
}
Step 6: Control Heading Length and Wrapping
Headings should be short enough to read at a glance. Long headings at large sizes wrap onto several lines and lose impact. A few CSS rules help:
h1,
h2,
h3 {
max-width: 30ch;
text-wrap: balance;
overflow-wrap: break-word;
}
max-widthinch: Keeps headings from spanning very wide containers. Around 20 to 35 characters suits most large headings.text-wrap: balance: Evens out line lengths so a heading doesn't end with one word on its own line. It's supported in all current major browsers.overflow-wrap: break-word: Prevents long words, such as a product name or URL, from overflowing on narrow screens.
Test with your longest realistic headline, not a short placeholder. If it takes more than three lines on a phone, your h1 is probably too large for your content.
Browser Defaults and Why to Override Them
Browsers ship default heading sizes defined in em, relative to the parent's font size:
| Element | Default size |
|---|---|
h1 | 2em |
h2 | 1.5em |
h3 | 1.17em |
h4 | 1em |
h5 | 0.83em |
h6 | 0.67em |
Those defaults put h5 and h6 smaller than body text, which rarely makes sense on a modern website. Historically, browsers also shrank an h1 placed inside article, aside, nav or section, which made the same element appear at different sizes depending on where it sat. That legacy rule has been removed from the HTML specification and browsers are dropping it, but it's another reason to set every heading size explicitly rather than relying on defaults.
Because the defaults use em, a heading inside a component with a smaller font size also shrinks. Setting sizes in rem, or with clamp() values built from rem, makes each level the same size everywhere.
Check Contrast and Accessibility
Heading sizes affect contrast requirements. WCAG defines large text as at least 18pt, which is 24px, or at least 14pt bold, which is about 18.66px. Large text needs a contrast ratio of at least 3:1 at level AA, while smaller text needs 4.5:1. If your h4 is 20px and regular weight, it counts as normal text and needs the higher ratio.
Also keep the visual sizes in line with the document outline. Choose h1 to h6 by structure, then style them. If a sidebar needs a smaller heading that is structurally an h2, give it a class rather than switching it to an h4.
Setting Heading Sizes in WordPress
In a block theme, define font size presets in theme.json with fluid minimums and maximums, then assign them to heading elements:
{
"$schema": "https://schemas.wp.org/trunk/theme.json",
"version": 3,
"settings": {
"typography": {
"fluid": true,
"fontSizes": [
{ "slug": "medium", "name": "Medium", "size": "1.25rem", "fluid": { "min": "1.125rem", "max": "1.25rem" } },
{ "slug": "large", "name": "Large", "size": "1.5rem", "fluid": { "min": "1.25rem", "max": "1.5rem" } },
{ "slug": "x-large", "name": "Extra Large", "size": "2rem", "fluid": { "min": "1.5rem", "max": "2rem" } },
{ "slug": "xx-large", "name": "Huge", "size": "3rem", "fluid": { "min": "1.75rem", "max": "3rem" } }
]
}
},
"styles": {
"elements": {
"h1": { "typography": { "fontSize": "var(--wp--preset--font-size--xx-large)", "lineHeight": "1.1" } },
"h2": { "typography": { "fontSize": "var(--wp--preset--font-size--x-large)", "lineHeight": "1.2" } },
"h3": { "typography": { "fontSize": "var(--wp--preset--font-size--large)", "lineHeight": "1.3" } },
"h4": { "typography": { "fontSize": "var(--wp--preset--font-size--medium)", "lineHeight": "1.3" } }
}
}
}
WordPress generates clamp() values from each preset's min and max, and exposes the presets as custom properties such as --wp--preset--font-size--x-large. Editors can also choose these sizes from the block toolbar, so headings stay on your scale.
FAQ: Heading Sizes
On desktop, around 36 to 48px for most content sites, and up to 72px for a hero headline on a landing page. On mobile, around 28 to 32px. Use clamp to scale smoothly between the two.
Each level should be clearly larger than the one below it, typically by a ratio of 1.2 to 1.33. The smallest heading you use should differ from body text by size, weight or both, so it reads as a heading at a glance.
Use rem, or clamp values built from rem. Rem respects the reader's browser font size and keeps each heading level the same size wherever it appears, unlike em, which depends on the parent element.
Rarely. Most sites don't use them, and browser defaults make them smaller than body text. Setting them at body size with bold weight or uppercase is usually enough.
Desktop sizes often don't scale down. Use a smaller ratio on narrow screens, or fluid sizes with clamp, so the largest headings shrink while body text stays readable. Test with your longest real headline.
Using vw alone is a problem because the text won't respond to browser text-size settings. Combining rem and vw inside clamp keeps sizes fluid while still allowing readers to resize text.
Conclusion
Choosing heading sizes is a short, repeatable process. Start from your body text size, work out how many heading levels your content really uses, and space them with a consistent ratio so each level is clearly distinct. Give large headings smaller sizes on mobile, make the transition smooth with clamp() values that keep a rem component, and override browser defaults so every level is set explicitly.
Then refine the details that make sizes work in practice: tighter line heights, weights that balance size, a touch of negative letter spacing at the top of the scale, more space above than below, and balanced wrapping with a sensible maximum width. Test the result with your longest real headlines on a phone and a wide screen, and your headings will guide readers clearly through every page.


