ESC

Type to search the knowledge base.

Headings and Document Outline

Use h1–h6 as a real outline — one logical h1, no level skips for styling, and how headings power AT navigation and SEO.

beginner3 min read
  • html
  • headings
  • outline

Headings (h1–h6) are the document outline people and tools use to skim. Screen reader users jump by heading. Search engines use them as structure signals. Styling a div to look big is not the same thing.

Docs: MDN headings, WAI headings.

Practical rules

  1. Prefer one clear h1 per page for the main topic (patterns vary for apps; still keep a sensible top-level heading).
  2. Nest levels without skipping for appearance (h2 → h4 just for size is wrong).
  3. Style with CSS — don’t pick h3 because it’s “the right size.”
  4. Don’t use headings only for bold text mid-paragraph.
<main>
  <h1>Billing settings</h1>

  <section aria-labelledby="plan-heading">
    <h2 id="plan-heading">Current plan</h2>
    <p>…</p>
  </section>

  <section aria-labelledby="invoices-heading">
    <h2 id="invoices-heading">Invoices</h2>
    <h3>2026</h3>
    <h3>2025</h3>
  </section>
</main>

Outline vs HTML5 outline algorithm myth

The old HTML5 section outline algorithm (implicit headings from sections) is not how browsers and AT behave in practice. What matters is the actual heading elements in order. Put real h1–h6 in the DOM.

Visual design without breaking structure

.page-title {
  font-size: clamp(1.75rem, 1rem + 2vw, 2.5rem);
}

.card-title {
  font-size: 1.125rem;
  font-weight: 650;
}
<!-- Card inside a section that already has h2 -->
<article class="card">
  <h3 class="card-title">Invoice #1842</h3>
</article>

If the card is the only title on a simple page, h1 might be correct — structure follows content hierarchy, not component name.

Skipping levels

Bad:

<h1>Docs</h1>
<h4>Installation</h4>

Good:

<h1>Docs</h1>
<h2>Installation</h2>

Skips confuse AT users who navigate level-by-level.

Headings in components and SPAs

When routing client-side, update the page h1 (and document.title) to match the view. Stale headings after navigation are a common a11y bug.

Portaled modals should not steal the page’s heading hierarchy without care — dialog titles are often h2 with aria-labelledby on the dialog.

Interview out-loud

“Headings create a navigable outline. I use one primary h1, nest levels without skips, and style with CSS instead of mis-leveling. Screen reader users jump by heading, so fake headings and skipped levels hurt. In SPAs I keep the heading in sync with the route.”

Footguns

  1. Multiple random h1s from nested marketing sections.
  2. Bold paragraphs instead of headings.
  3. Icon fonts / images as titles without text.
  4. Starting every card at h1 inside a list.
  5. CSS display:none on headings still in the tree with misleading text.

Card lists

<section aria-labelledby="latest-h">
  <h2 id="latest-h">Latest posts</h2>
  <ul class="cards">
    <li>
      <article>
        <h3><a href="/p/1">Gap vs margin</a></h3>
        <p>…</p>
      </article>
    </li>
  </ul>
</section>

Each card title is an h3 under the section’s h2. Jumping to “heading level 3” still makes sense. Promoting every card to h2 flattens the outline into noise.

Marketing pages

Hero claims often want a large visual title. That title should usually be the h1. Sub-banners below should step to h2/h3, not another h1 for style. If a design system “Display” style is larger than h1, apply the display class to the h1 — do not demote the element type.

Testing the outline

Use a screen reader heading list or a bookmarklet/extension that dumps heading levels. You should see a tree that matches the visual story of the page. If the first announcement is an h4, something is wrong. Fix the markup, not the reader. Automated axe rules catch missing H1 in some configs; they rarely catch “too many H1s” quality issues — that remains a human review item for marketing pages.

Further reading

Related guides