Skipped heading levels: h1 to h3 and other outline problems
Headings are the page's outline, and a skipped level breaks it for anyone navigating by structure. squirrel checks order, emptiness, length and repeats.
What heading hierarchy is
Headings from h1 to h6 describe the document’s structure. Screen reader users navigate by them, jumping from heading to heading rather than reading linearly, and search engines use them to understand how a page is organised.
The order carries the meaning. h1 to h2 to h3 says each section is inside the last; h1 straight to h3 says a level is missing, and the reader is left to guess what. The common cause is choosing a heading level for how it looks rather than for where it sits, which is what CSS is for.
What squirrel checks
The rule runs on every crawled page and reads the heading analysis the parser produced. It emits up to four checks:
heading-hierarchywarns when a level was skipped. Message:Skipped heading levels detected, listing each skip. Severity warning, weight 5.heading-hierarchypasses when nothing was skipped and the page has headings. Message:Heading hierarchy is valid.empty-headingswarns when a heading has no text. Message:2 empty heading(s) found. A heading the markup hides from every reader does not count: one with thehiddenattribute, inlinedisplay:noneorvisibility:hidden, or inside a[hidden]oraria-hidden="true"ancestor. The same test decides which empty headings a11y/empty-heading reports. Hiding through a stylesheet class cannot be seen from the markup, so it still counts.long-headingsreports info for headings over 70 characters. Message:3 heading(s) over 70 characters, each item naming the level with the first 50 characters of its text.duplicate-headingsreports info for repeated heading text. Message:4 duplicate heading(s), listing the repeated text.
A page with no headings at all emits nothing from the first check, since the pass branch requires at least one heading.
How to fix it
<h1>URL structure</h1>
<h2>Word separators</h2>
<h3>Hyphens versus underscores</h3>Pick the level from the position in the outline and style it with CSS. Reading only the headings should give a sensible summary of the page.
An empty heading is usually a styling wrapper or a decorative element that ended up as an h2. Use a div and a class instead.
| Rule ID | content/heading-hierarchy |
| Category | Content |
| Scope | Per-page |
| Severity | warning |
| Weight | 5/10 |
Enable / disable
Disable this rule
[rules]
disable = ["content/heading-hierarchy"]Disable all Content rules
[rules]
disable = ["content/*"]Enable only this rule
[rules]
enable = ["content/heading-hierarchy"]enable replaces the default ["*"], so this is the only rule selected. Leave disable out: a rule that matches any disable pattern is skipped even when enable names it. Only an enabled setting under a rule’s own [rule_options] table overrides both lists.
Related rules
- core/h1: whether the page has exactly one top-level heading.
- a11y/heading-order: the accessibility check over the same structure.
- content/word-count: how much content sits under those headings.
- images/figure-figcaption: the other structural markup on a content page.
Content findings ship in every audit next to the SEO, performance and agent experience rules. See Fix SEO issues with AI for how an agent works through a report.
References
Check your site
Run squirrel audit https://example.com and open the Content section of the report. Each page is listed with its skipped levels, empty headings and repeats. Signed-out local audits need no account and cost nothing. Signed-in audits spend credits.