Semantic HTML · 3 / 8
lesson 3

Headings done right

h1 through h6 build a table of contents the page never shows. Get the order wrong and the TOC breaks.

~ 15 min read·lesson 3 of 8
0 / 8

A page's headings are not just big bold text. They are the table of contents that screen-reader users browse with one keystroke. They are the structure search engines use to figure out what your page is about. They are the outline a "reader mode" article rebuilds.

Get the headings right, and a blind user can hop to the section they want in two seconds. Get them wrong — by skipping levels, by using <h2> because it "looks the right size", by sprinkling <h1>s everywhere — and that whole experience breaks.

Headings as a TOC

Imagine a screen reader's "headings list" command. The user presses a key, the software pops up a list of every heading on the page indented by level, and the user clicks one to jump there.

h1  Daily Bread Bakery
  h2  Sourdough season is back
    h3  How the starter works
    h3  Where to buy
  h2  Visit us
    h3  Hours
    h3  Address

That's the page. No prose, no images, no nav — just the heading skeleton. If the skeleton tells a coherent story, your page is well-structured. If it looks like a jumble, your page is too.

This is also roughly what search engines see. They cannot read your CSS, but they read your headings.

Why level order matters

Heading levels are not font sizes. They are depth in the outline. <h1> is the page topic. <h2> is a major section of that topic. <h3> is a subsection of that section. And so on, down to <h6>.

A useful picture: headings are like the bullets in a nested list. h1 is the top bullet. h2s are sub-bullets under it. h3s are sub-sub-bullets under the most recent h2. The browser does not care about the indentation visually, but the meaning is the same — depth.

h1 — Page titleh2 — Section Ah3 — Subsectionh3 — Subsectionh2 — Section Bh3 — Subsection
Heading levels are nesting depth. Use them in order so the outline reads top-down.

The picture above is the same as the screen-reader headings list. Indentation = depth = heading number.

article.html
<article>
<h2>Sourdough season is back</h2>
<p>Our wild-yeast loaf returns next Friday...</p>

<h3>How the starter works</h3>
<p>We feed it twice a day...</p>

<h3>Where to buy</h3>
<p>The Saturday market on Bridge Street...</p>
</article>

The article has one major topic (h2) with two sub-topics (h3s). That is the whole structure. A reader navigating by headings sees three entries: the section title and its two sub-headings. They can jump straight to "Where to buy" without reading the rest.

How many h1s?

The traditional rule was "one <h1> per page". The rule was easy to follow and made sense when every page was hand-built.

Modern web pages built from components blur this. A blog post page often has the post title as the page-level <h1>, and individual cards on a "related posts" sidebar may also each have their own heading. With nested <article>s, each article can technically have its own <h1> and the outline still works — heading levels reset inside an article.

In practice, the safe advice is still: use one <h1> per page, and make it the page's topic. Inside <article> elements, a second <h1> is permitted by the spec but understood by fewer tools. If you are building a typical content page, one <h1> is the right call.

Tip

The page's <title> (in the head, used for the browser tab) and the page's <h1> (in the body, the visible title) are usually similar but not identical. The title might say "Sourdough season — Daily Bread Bakery"; the h1 might say "Sourdough season is back". Both are fine; both serve different audiences.

check your understanding
A landing page has a hero with the company name, a "features" section, a "pricing" section, and a "FAQ" section. The right heading skeleton is roughly:

Don't skip levels

The rule that catches people: do not skip from <h2> to <h4> in the same outline. Going down by more than one level confuses screen-reader users (they expect to navigate by hierarchy) and breaks the implicit table of contents.

broken.html
<!-- broken: jumps from h2 to h4, no h3 between -->
<h1>Pricing</h1>
<h2>Plans</h2>
<h4>Free tier</h4>
<h4>Pro tier</h4>

A screen reader will still announce the headings, but the outline is missing a rung. Was there meant to be an <h3> "Tiers" between them? The user can't tell.

fixed.html
<h1>Pricing</h1>
<h2>Plans</h2>
<h3>Free tier</h3>
<h3>Pro tier</h3>

The fix is rarely complicated: pick the next level down. Going back up (from <h3> to <h2>) to start a new sibling section is fine — that is exactly what the outline is for. Only the down-skip is the bug.

check your understanding
You inherit a page with this skeleton: h1, h2, h3, h2, h4, h3, h2. Where is the bug?

Style is a separate concern

The biggest reason people pick the wrong heading level is that they are picking by appearance. "I want medium-sized text, so I'll use <h3>." That logic produces broken outlines.

The right move: pick the level by depth, then style it however you want.

style-not-level.css
/* The h2 that opens an article gets the "hero" look. */
article > h2:first-child {
font-size: 2.5rem;
font-weight: 600;
}

/* But it stays an h2 — its level is the page-outline's level,
 independent of how big it looks. */

In CSS, font-size can be anything. In HTML, the heading level has to match the structure. They are independent dials.

If you ever feel the urge to write <h2 style="font-size: small">, take that as a sign you have picked the wrong level. The level is for the outline; the size is for the design.

Watch out

Tools like Lighthouse, axe, and most browser DevTools include an "outline" or "headings" check. Run it on your page and read the result aloud. If the read-out does not match the page, your headings are wrong.

check your understanding
Your designer hands you a mockup where the FAQ section's question titles look like the smallest body text — about 14px. The questions are the third level of the page's outline. What do you write?
check your understanding
A page has 30 product cards on a listing. Each card has a product name, a price, and an image. Which is the best markup for the product names?
← prevnext lesson →
KeepLearningcertificate
for completing
Semantic HTML
0 of 8 read