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.
- 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
- Prefer one clear
h1per page for the main topic (patterns vary for apps; still keep a sensible top-level heading). - Nest levels without skipping for appearance (
h2→h4just for size is wrong). - Style with CSS — don’t pick
h3because it’s “the right size.” - 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
- Multiple random
h1s from nested marketing sections. - Bold paragraphs instead of headings.
- Icon fonts / images as titles without text.
- Starting every card at
h1inside a list. - CSS
display:noneon 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.
Related
Further reading
Related guides
- Accessibility Tree OverviewHow browsers build the accessibility tree from DOM and CSS — roles, names, states, what’s pruned, and how to inspect it in DevTools.
- Audio and Video ElementsNative audio/video — controls, sources, captions, autoplay policies, and accessibility requirements for media on the web.
- Autocomplete and Name Attributesname and autocomplete on form fields — password managers, autofill tokens, and why missing names break real users more than demos.
- Base Element and Relative URLsHow <base href> rewrites relative URLs for links, scripts, and forms — powerful for static hosts, dangerous when set accidentally.
- contenteditable Basicscontenteditable surfaces — what the browser gives you, sanitization, keyboard and a11y gaps, and when to pick a real editor library.