squirrelscan
GitHub

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-hierarchy warns when a level was skipped. Message: Skipped heading levels detected, listing each skip. Severity warning, weight 5.
  • heading-hierarchy passes when nothing was skipped and the page has headings. Message: Heading hierarchy is valid.
  • empty-headings warns 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 the hidden attribute, inline display:none or visibility:hidden, or inside a [hidden] or aria-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-headings reports 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-headings reports 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 IDcontent/heading-hierarchy
CategoryContent
ScopePer-page
Severitywarning
Weight5/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.

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.

Type to search…

↑↓ navigate↵ openesc close