Headings are the outline a machine builds of your page before it reads a word of the body. An agent that fetches your HTML and turns it into text keeps the heading levels, because they are the only structural information a page carries about which paragraph belongs to which topic. Two h1 elements, or an h2 followed by an h4, hands that agent an outline that does not describe the page, and it will act on the outline it was given. Check B3 spends half of its four points on this, one for exactly one h1 on every crawled page and one for no skipped level anywhere, and both are lost by a single page.
This post covers the heading half of B3. The other two points, for main, nav and footer landmarks, are a separate post. Both sit in the understanding pillar, the second of the four questions described in what agent readiness means.
What the scanner measures
The check runs on raw HTML, the response to a plain GET, not on a rendered DOM. It parses each crawled page, the homepage plus up to five more, with a real HTML parser and collects every h1 to h6 element in document order as a sequence of numbers. From that sequence it derives two facts per page.
The first is the count of h1 elements, which has to be exactly one. Zero fails in the same way as two.
The second is whether any heading sits more than one level deeper than the heading immediately before it in the document. An h2 followed by an h3 is fine. An h2 followed by an h4 is a skip. Moving back up the outline is never a skip, so an h4 followed by an h2 is fine, because a new section has started. The comparison is always against the previous heading in source order, not against the nearest ancestor section, so it is a linear test that anyone can reproduce by listing the tags.
Each point is all or nothing across the crawl. If five pages have one h1 and the sixth has two, the h1 point is lost. The evidence block on the report records pagesCrawled, singleH1 and noSkippedLevels as counts, plus a problems list naming the first ten offending URLs with the reason, for example 2 h1 elements or skipped heading level. That list is what you hand to whoever owns the templates.
Two consequences of the method are worth knowing. The parser does not evaluate CSS, so a visually hidden h1 counts as an h1, and a heading styled to look like body text is still a heading. And only the elements count: a div with role="heading" and aria-level is not collected, so a page whose headings are entirely ARIA-based reports zero h1 elements.
What a skipped level does to extraction
Consider what an agent does with a page it has fetched. Most pipelines convert the HTML to a lighter form, often Markdown, in which each heading becomes a line prefixed with one to six hash characters. The body text between two headings is attributed to the heading above it. Sections nest by level, so the pricing table under ## Pricing and the note under ### Annual billing are understood as one topic with a sub-topic.
Now skip a level. ## Pricing is followed by #### Annual billing. The outline has an orphan. Some converters treat it as a child of the h2 anyway, some create an empty implied h3, and some flatten everything to one level and give up on hierarchy altogether. The page is still readable in a browser, but three different tools now produce three different outlines of it, and the one an agent is using may not be the one you would have chosen.
Two h1 elements are worse, because an h1 answers the question "what is this page". A single h1 gives the agent a name for the page that agrees with the title element and the og:title. Two h1 elements, typically the site name and the page name, give it two candidates and no rule for choosing. An agent building a summary, a citation or a list of pages to visit picks one, and it is frequently the logo.
None of this is specific to agents. Screen reader users navigate by heading, moving from one to the next and using the level to understand the structure, which is why WCAG 2.2 treats headings as the primary way a page's organisation is exposed to assistive technology. A skipped level or a duplicate h1 is a defect under that standard and under this rubric for exactly the same reason: the page says one thing visually and another structurally, and the machine only gets the second.
A wrong outline and a right one
This is the pattern the scanner reports most often. The header component wraps the logo in an h1, the page adds its own, and a card component renders an h4 because that was the font size the design called for.
<header>
<h1><a href="/">Acme Analytics</a></h1>
</header>
<main>
<h1>Pricing</h1>
<section>
<h2>Plans</h2>
<article class="card">
<h4>Starter</h4>
</article>
<article class="card">
<h4>Team</h4>
</article>
</section>
</main>
The sequence is 1, 1, 2, 4, 4. Two h1 elements, and a jump from h2 to h4. Both heading points are gone, on every page that uses this header and this card.
The same page, corrected, changes three tags and no design.
<header>
<a href="/" class="logo">Acme Analytics</a>
</header>
<main>
<h1>Pricing</h1>
<section>
<h2>Plans</h2>
<article class="card">
<h3>Starter</h3>
</article>
<article class="card">
<h3>Team</h3>
</article>
</section>
</main>
The sequence is 1, 2, 3, 3. The logo is a link, which is what it was doing anyway. The cards are h3, and a CSS class carries the size the designer wanted. The outline now reads as a person would describe the page: Pricing, then Plans, then the plans themselves.
Common causes in component-based sites
Almost every failure here is a template decision made once and repeated on every page, which is also why the fix is cheap.
The logo in an h1. An old SEO habit that lived on in component libraries. On the homepage it is usually the only h1, so the homepage passes, and every interior page then has two. The scanner crawls interior pages, so the report catches it even when a spot check of the homepage does not.
A card with a fixed heading level. A Card component that always renders an h4, or an h2, regardless of where it is placed. Dropped under an h2 section it produces a skip; dropped under an h3 it flattens the outline in the other direction, which the scanner does not flag but which still misreports the structure. The fix is a level prop, or a component that takes the heading element as a child rather than owning it.
Heading tags used for size. A marketing section whose headline is an h5 because the small size looked right, followed by an h2 for the next section. Sizes belong in CSS. Heading levels belong to the outline.
Two sources of the page title. A layout that renders the page title as an h1 and a rich text body that starts with another h1 because the editor typed one. A page builder that offers editors a "Heading 1" option in a block has the same effect. Strip or demote h1 in body content at render time; the layout owns it.
No headings at all. A landing page built entirely from styled div and p elements has zero h1 elements and fails the same point. It also gives an agent nothing to build an outline from, so the whole page is one undifferentiated block of text.
A component that carries its level as a prop, and a rule that layouts render the h1 and nothing else does, settles all five.
type HeadingProps = { level: 2 | 3 | 4 | 5 | 6; children: React.ReactNode };
export function Heading({ level, children }: HeadingProps) {
const Tag = `h${level}` as const;
return <Tag>{children}</Tag>;
}
Checking a page by hand
The scanner's test is simple enough to reproduce in a terminal. Fetch the raw HTML as the scanner would, list the heading tags in order, and read the sequence.
curl -s https://example.com/pricing | grep -oE '<h[1-6]' | tr -d '<h' | tr '\n' ' '
Print it for the homepage and for four or five interior pages, since the failure is nearly always on a template the homepage does not use. If the sequence contains more than one 1, or any step of two or more downwards, that page is the one the report will name. Because the points are all or nothing, the last page you fix is the one that returns them.
If your headings only exist after JavaScript runs, the raw fetch will show none at all, and you have a larger problem than B3, one that check A7 measures directly and that the implementation service exists to fix.
Check your own site
The free scan lists every crawled page with a duplicate h1 or a skipped level under B3, alongside the landmark result, and you can compare the finding with the definition on the understanding pillar of the methodology page. The heading points are two of the cheapest in the rubric to recover, because the fix is a template edit applied once and inherited by every page.