AIScan fix guide cover: Heading order, fixed without a redesign, with a broken page marked by a cross turning into a fixed page marked by a check.
AIScan fix guide cover: Heading order, fixed without a redesign, with a broken page marked by a cross turning into a fixed page marked by a check.
AI Readiness

Fix "Heading elements are not in a sequentially-descending order" without breaking your design

Fix the Lighthouse heading-order warning: find every skipped level with a console snippet, then change the tag and keep the look with a CSS class instead.

29 Oct 2026 7 min read

This guide covers E3 · Content, C3 · Content.

Table of contents

Lighthouse prints "Heading elements are not in a sequentially-descending order" when a heading jumps more than one level, for example an <h4> straight after an <h2>. The fix is almost never to delete the heading. Change the tag to the level the outline needs and keep the look with a CSS class, which takes a few minutes per template. This guide finds every skip with a console snippet and then fixes the four sources that cause most of them.

It covers check E3 (heading hierarchy and server-rendered text) and the single-<h1> rule of C3, and it says plainly what AIScan scores and what only Lighthouse flags.

Quick summary

If you want to...Do thisTime
See your outline and every skipped levelPaste the snippet below into the browser console1 minute
Fix a theme logo or site title that is an <h1>Make it a <p> or <div> on every page except the home page5 minutes
Fix a widget or dialog with a hard-coded <h4>Change the tag to the level its position needs, keep the size with a class5 minutes per widget
Keep the design while changing the tag<h2 class="h4">: the tag carries meaning, the class carries the look0
Confirm the fixnpx aiscan-cli yoursite.com, then read check E3 and the C3 <h1> count2 minutes

What the audit checks, and what AIScan checks

According to the HTML Living Standard, each heading after another one "must have a heading level that is less than, equal to, or 1 greater than" the previous heading's level (HTML Standard, headings and outlines). Going down is fine: an <h3> after an <h5> is not a skip. Going up by two or more is. According to Deque, its axe-core rule heading-order, which is the rule behind the Lighthouse audit (web.dev's old heading-order page now redirects to it), says the same: heading levels should only increase by one. Assistive technology users navigate by heading, and according to WCAG 2.2 SC 1.3.1 structure shown visually has to be available in the markup too.

AIScan's checks are narrower. According to our E3 study of 699 sites (9 October 2026): E3 counts words and <h2>/<h3> headings in the served HTML and does not read the <h1>; the <h1> belongs to C3, which wants exactly one. So an <h2> followed by an <h4> raises the Lighthouse audit and not an E3 finding, as long as the page still has its <h2> sections. | Case | Lighthouse (axe heading-order) | AIScan | |---|---|---| | <h2> then <h4>, with <h2> sections present | Flags the skip | No E3 finding | | 150+ words, <h3> but no <h2> | Reads the order | E3 finding | | Two <h1> elements | Outside the rule | C3 finding (wants exactly one) | | Headings added by script | Reads the rendered DOM | Reads the served HTML |

A page with 150 or more words and <h3> headings but no <h2> does get an E3 finding. I did not re-scan a skipped-level test page for this guide: that behaviour comes from the study, so re-run your own scan to confirm it. Fix both anyway.

Step 1: print the outline and flag every skip

Open the page, press F12, choose Console, and paste:

(()=>{let p=0;const r=[...document.querySelectorAll('h1,h2,h3,h4,h5,h6')].map(h=>{const l=+h.tagName[1],f=p&&l>p+1?`  <- skips from h${p}`:'';p=l;return`${'  '.repeat(l-1)}h${l} ${h.textContent.trim().slice(0,40)}${f}`});console.log(r.join('\n')+`\nh1 count: ${document.querySelectorAll('h1').length}`)})()

I ran it in headless Chromium on a test page that had a logo <h1>, a cookie dialog <h4>, a second <h1> for the real title, an <h2> followed by an <h4>, and an <h5> under an <h3> (29 October 2026). Output:

h1 Acme Store
      h4 Cookie settings  <- skips from h1
h1 Blue widgets
  h2 Specs
      h4 Weight  <- skips from h2
  h2 Fixed
    h3 ok
    h3 ok2
        h5 jump  <- skips from h3
    h3 Newsletter
h1 count: 2

Three skips and two <h1> elements, found in one pass. The snippet reads the rendered page. What a crawler that does not run JavaScript receives is the served HTML, so compare the two with curl -s https://yoursite.com/ | grep -o '<h[1-6]' | tr '\n' ' '. Headings that appear in the console but not in the curl output are added by script, which is the E3 problem in our server-rendered text study, not an ordering problem. Fetching your site the way an agent does covers that test in full.

Step 2: fix the four usual sources

What you see in the outlineCauseFix
<h1> before the real <h1>, h1 count 2Theme wraps the logo or site title in <h1>Make it a <p> or <div> on inner pages
<h4> or <h3> right after the first <h1>Cookie banner, header widget or mega-menu with a fixed tagChange the tag to match its place, or take it out of the heading outline
Levels chosen by size: <h4> used because it looks smallPage builder picks the tag from the font sizeSet the tag and the size separately
Several jumps in the body textEditors picked headings for their lookRe-level the headings, then style with a class

The theme logo. A site title belongs in an <h1> only on pages where it is the page's subject. On WordPress block themes, select the Site Title block and change its level in the block toolbar or the Settings sidebar; its block.json in the Gutenberg repository defaults the level to 1 and lists 0 to 6 as options, and the paragraph option renders it as a <p> (core/site-title block.json). Check the template for the page type that raised the count. On classic themes, change the wrapper tag in the theme's header template and keep a conditional so the home page can keep the <h1>.

The hard-coded dialog heading. A cookie dialog, popup or newsletter box often ships an <h4> because the vendor never knew where it would sit. A dialog that sits in the DOM before your <h1> makes the first heading on the page a level 4, as in the console output above. Options, in order: edit the template and change the tag, set role="dialog" with aria-labelledby pointing at a <p> title so it is not a heading, or load the dialog late so it never enters the first response.

Style it with a class, not a tag. Once the tag is right, the size is a styling job. According to Bootstrap's documentation, its .h1 through .h6 classes exist "for when you want to match the font styling of a heading but cannot use the associated HTML element" (Bootstrap typography). In any other stack, write the same two rules yourself:

<h2 class="h4">Specs</h2>
.h4 { font-size: 1.25rem; line-height: 1.3; }

The <h2> joins the outline at the right level and looks like the old <h4>. The class has no effect on the outline.

Builders that pick the tag from the size. In the page builder's heading widget, look for an HTML tag or semantic level setting next to the font size. Set the tag from the outline and the size from the design, separately. If the builder ties them together, use its text widget with the class above for the cosmetic cases.

Step 3: verify

  1. Paste the snippet again. There should be no <- lines and h1 count: 1.
  2. Run the curl line from step 1 and confirm the served HTML shows the same order.
  3. Scan the page with AIScan: npx aiscan-cli yoursite.com, and read the E3 evidence line and the C3 <h1> count. The curl equivalent is curl -s https://yoursite.com/ | grep -c '<h1', expecting 1.
  4. Run Lighthouse's accessibility audit in Chrome DevTools and confirm the heading-order item is gone.

Maintenance

Widgets and theme updates bring skips back. Re-run the snippet on one page of each template after a theme or plugin update. The AIScan CI gate can fail a build when a check regresses. For the per-platform <h1> ownership table, see one h1, title, meta description and JSON-LD by platform. For writing the sections themselves, see making a page answerable by AI.

Next step

Scan the page you just fixed at aiscan.site, read check E3 and the <h1> count under C3, and browse the rest of the fix guides in /guides.

Frequently asked questions

Lighthouse says "Heading elements are not in a sequentially-descending order". What do I change first?

Run the console snippet in this guide and find the first line marked with an arrow. Change that heading's tag to the level its position needs (one more than the heading before it), then restyle it with a class if the size changed.

My cookie banner is an h4 and appears before my h1. How do I fix it?

Change its tag in the template, or give the dialog a title that is not a heading and point aria-labelledby at it. A dialog placed early in the DOM otherwise becomes the first heading on the page.

The audit still fails after I changed the tag. Why?

Check the served HTML with curl as well as the rendered page. A theme, plugin or script can output a second copy of the heading, or a widget can add its own after your edit. Re-run the console snippet and the curl line on the live URL.

My page builder ties the font size to the heading tag. What now?

Look for a separate HTML tag or semantic level option in the heading widget. If there is none, use a text widget with a class such as h4 for the cosmetic cases and keep real headings for the outline.

Does AIScan E3 fail a page for skipping from h2 to h4?

According to our E3 study, E3 counts words and h2 and h3 headings in the served HTML, so a skip alone is not what it scores. A page with enough text and h3 headings but no h2 does get a finding. Lighthouse flags the skip itself.

Does a skipped heading level hurt my Google ranking?

This guide has no ranking evidence either way, so it makes no claim. The documented reasons to fix it are the HTML Standard's outline rule and accessibility under WCAG 2.2 SC 1.3.1.

I have two h1 elements and the scan reports a C3 finding. Is that the same problem?

No, it is a different rule. C3 wants exactly one h1 in the served HTML. A logo or site title wrapped in an h1 is the usual second one: change it to a paragraph or div on inner pages.

Is it fine to use h2 with an h4 class?

Yes. The tag sets the outline and the class sets the look, and the class does not change the outline. Bootstrap documents its .h1 to .h6 classes for exactly this case.

M Asif Rahman

About the author

Entrepreneur, curious about AI

I have been building companies for a long time, and lately most of my curiosity goes to AI. AIScan is a small personal project I built on a flight from Dubai to Narita. I use it to understand how ready the web is for AI agents, and I share what the scans teach me.

Related guides

Browse every guide by check and platform