Sighted people skim a page by its headings without thinking about it: big bold text tells you what each block is about, and your eyes jump to the part you need. Screen reader users do the same thing, using the markup instead of the visuals. In surveys run by WebAIM, a large majority of screen reader respondents say headings are the first thing they use to find their way around a long page. In NVDA and JAWS, pressing H jumps to the next heading and the digits 1 to 6 jump to the next heading of that level; VoiceOver’s rotor (VO+U on macOS) shows a list of every heading on the page.
That list is the page’s outline. If the headings are missing, the list is empty and the user has to read the page top to bottom. If the levels are wrong, the list is a jumble. This is why headings come up in accessibility interviews: it’s a small piece of markup with a large effect, and it’s easy to get wrong when styling drives the choice.
Levels describe structure, not size
The mistake interviewers listen for is choosing a heading level for its default font size. A designer wants a small grey label above a card, so someone writes <h5>. Or a sidebar title needs to be modest, so it becomes <h4> on a page that has only an <h1> above it.
Heading levels are a nesting structure, like the numbered sections of a report: an <h3> is a subsection of the <h2> before it. The size is CSS’s job:
.section-label {
font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 0.05em;
}<h2 class="section-label">Related articles</h2>That renders as small uppercase text and still announces as “heading level 2, Related articles”. Conversely, if something looks like a heading but isn’t one (a big promotional “Save 20%” banner), it shouldn’t use a heading element at all; a styled <p> is fine.
A page, before and after
Here’s a typical article page as it was first built, written as the heading list a screen reader would show:
h1 Stacktrick (the logo)
h4 Search
h1 Understanding CSS Grid (the article title)
h3 How fr works
h3 minmax()
h5 Note
h2 Comments
h4 Newsletter
h4 Footer linksProblems, in the order a user meets them:
- Two
<h1>elements, one of which is the logo. The list starts with the site’s name, which is the same on every page, before getting to what this page is about. - “Search” is a level 4 heading before anything at levels 2 or 3, so it appears to be a sub-sub-section of the logo.
- The article’s sections jump from
<h1>to<h3>. A user pressing 2 to move between main sections finds nothing in the article. - “Note” is a heading for a callout box. It adds noise to the list without helping navigation.
- The footer and newsletter headings sit at level 4, making them look like subsections of “Comments”.
The fixed version:
h1 Understanding CSS Grid
h2 How fr works
h2 minmax()
h2 Comments
h2 Newsletter
h2 Footer links (or remove; see below)And the markup for the main structure:
<header>
<a href="/" class="logo">
<img src="/logo.svg" alt="Stacktrick home">
</a>
<form role="search" action="/search">
<label for="q">Search</label>
<input id="q" name="q" type="search">
</form>
</header>
<main>
<article>
<h1>Understanding CSS Grid</h1>
<h2>How fr works</h2>
<!-- … -->
<h2>minmax()</h2>
<!-- … -->
<aside class="callout">
<p><strong>Note:</strong> Subgrid is covered in a separate article.</p>
</aside>
</article>
<section aria-labelledby="comments-heading">
<h2 id="comments-heading">Comments</h2>
</section>
</main>The logo becomes a link with an image whose alt text names the destination, not a heading. Search is a labelled form inside a search landmark, so it’s reachable through landmark navigation without being in the heading list. The callout’s “Note” becomes bold text; its content belongs to the section it sits in.
The rules that matter
One <h1> describing the page. HTML doesn’t forbid several, but one <h1> matching the page’s topic (usually close to the <title>) gives users a reliable “where am I” anchor: pressing 1 always goes to the start of the content. On the home page, the <h1> can be the site name or its tagline.
Don’t skip levels on the way down. After an <h2>, the next deeper heading is <h3>, not <h4>. Going back up any number of levels is fine: an <h4> can be followed by an <h2> when a new major section starts. WCAG doesn’t strictly fail a skipped level, but it makes the outline harder to interpret, and audit tools like axe flag it as a best-practice issue.
Headings must have text. An empty <h2></h2> used for spacing, or a heading containing only an icon with no accessible name, appears in the list as a blank entry.
Write headings that make sense out of context. In the heading list, users see only the headings. Five sections called “Overview” across a long page, or “Click here to learn more”, tell them nothing. “How fr works” does.
Sections don’t renumber headings
A persistent myth says you can use <h1> in every <section> or <article> and the browser will work out the real levels from the nesting. That was the “outline algorithm” in early HTML5 drafts. No browser or screen reader ever implemented it, and it was removed from the HTML Living Standard in 2022. A page full of <h1> elements inside sections is announced as a flat list of level 1 headings.
Browsers did once shrink the default font size of <h1> inside sections (a leftover from that era), and Firefox and Chrome have since been removing even that styling. Either way, the level announced was always 1. Use explicit levels.
This matters for component libraries. A Card component that hard-codes <h3> will be wrong on some pages. Let the caller pass the level (headingLevel={2}) or render a slot, so the heading fits wherever the card lands.
Checking the outline
The quickest checks:
- Use a headings bookmarklet or browser extension (for example, the HeadingsMap extension for Chrome and Firefox) to list the headings with their levels.
- In VoiceOver, open the rotor with VO+U and arrow to “Headings”. In NVDA, press Insert+F7 for the elements list.
- Read only the heading list and ask whether it tells you what’s on the page and where to find it. If you can’t tell which heading is the article’s title, neither can a screen reader user.
Interview practice
A marketing page uses <h1> for the hero slogan, <h1> again for the product name further down, <h3> for each feature card title (because the design system’s <h3> style looked right), and <div class="title"> for the pricing section’s title. What would you change?
Answer
Decide what the page is about and make that the single <h1>. Usually that’s the product name, with the slogan as a paragraph styled large, or the slogan as the <h1> if it names the product; either way, one of them stops being a level 1 heading. The pricing title becomes a real heading, an <h2>, since right now it’s invisible in the heading list even though users look for pricing first. The features section gets an <h2> too (“Features”), and the feature card titles become <h3> beneath it, which happens to match what they were, but now for a structural reason. Keep the visual design by applying the existing classes to the new elements rather than picking levels by their default styles. Finally, list the headings with a tool or VoiceOver’s rotor to confirm the outline reads as: product name, Features (with each feature under it), Pricing.