← All articles
Technical SEO10 min read

Heading Structure: The Rules Search Engines Actually Apply

Multiple H1s are fine, the HTML5 outline algorithm never shipped, and skipped levels hurt screen readers more than rankings. What headings really do, and why.


Heading advice has calcified into a set of rules that nobody can source: exactly one H1, never skip a level, put your keyword in the H2. Some of that is genuinely useful, some of it is accessibility guidance that got relabelled as SEO, and at least one piece is a myth Google has explicitly corrected — more than once. Meanwhile the actual reason to care about heading discipline has quietly changed: headings are now the primary structural signal that both assistive technology and AI retrieval systems use to decide what a chunk of your page is about. Here is what each layer actually requires, where the three disagree, and what breaks in real component libraries.

Three different rulebooks

Almost every heading argument is people quoting different specifications at each other.

The HTML specification allows h1 through h6 in any order, anywhere in the document, any number of times. There is no validation error for six H1s or for jumping from h2 to h5. The spec advises that heading rank should reflect nesting, but advice is not a constraint.

The accessibility rulebook — WCAG plus how screen readers actually behave — treats headings as a navigation structure. WCAG 1.3.1 (Info and Relationships) requires that visual structure be conveyed programmatically; WCAG 2.4.6 requires that headings describe their topic. Neither literally forbids skipping a level, but skipping produces exactly the confusing structure those criteria exist to prevent, and it's a standard failure in every audit methodology.

Search engines use headings as one signal among many for understanding page structure and topic. Google's documentation describes headings as helping to understand the structure of a page. That is it. Headings are not a ranking lever with a dial on it.

When people argue about heading rules, they are usually applying rulebook two while claiming the authority of rulebook three.

The multiple-H1 myth

Let's kill this one properly, because it is repeated in almost every SEO checklist.

Multiple H1s do not hurt your rankings. Google's own search relations team, John Mueller most prominently, has stated this repeatedly and unambiguously in public: having several H1 elements on a page is fine, and it's a common pattern on the web. HTML5 explicitly anticipated multiple H1s inside sectioning elements. Google's own documentation notes that using multiple H1 headings is acceptable.

The nuance is worth keeping, though, because "doesn't hurt rankings" is not the same as "is a good idea":

  • A page with one H1 has an unambiguous answer to "what is this page about". A page with four has four candidate answers, and something downstream — a screen reader summary, a retrieval chunker, a SERP title rewrite — has to pick.
  • Screen reader users conventionally treat the H1 as the page title. Several H1s of equal rank make the top-level structure flat and harder to skim.
  • Multiple H1s are usually a symptom. When you find three, they are almost always a site logo wrapped in an H1, a page title, and a sidebar widget heading — which means the real problem is that structure is being generated by three unrelated components with no shared contract.

So the honest guidance is: use one H1 because it produces a cleaner document, not because a second one costs you rankings. If a design genuinely calls for two, ship it and don't lose sleep.

Skipped levels: who actually cares

Going h1h3 with no h2 between them is the other perennial. Does it matter?

For ranking: very little. Search engines are robust to messy heading order — they have to be, because most of the web is messy. Nobody at Google has ever described a skipped level as a problem.

For screen readers: yes, meaningfully. Heading level is the only thing that communicates nesting depth to a non-visual user. JAWS, NVDA and VoiceOver all offer a headings list and level-by-level navigation (16 in NVDA, the rotor in VoiceOver). WebAIM's screen reader user surveys have consistently found that headings are the most commonly used way of finding information on a page. When you jump from level 2 to level 4, the user hears a level 4 and reasonably assumes there is a level 3 they missed — so they go back and look for it. It's a small, repeated tax on comprehension.

The direction matters too. Skipping down the tree (2 → 4) is the problem. Coming back up is always legal: after an h4, going straight to the next h2 is correct and expected.

<!-- Legal and correct: you may jump back up any number of levels -->
<h2>Installation</h2>
  <h3>macOS</h3>
    <h4>Homebrew</h4>
<h2>Configuration</h2>   <!-- fine: back up to 2 -->
<!-- Broken: h2 is missing entirely -->
<h1>API Reference</h1>
  <h3>Authentication</h3>   <!-- level 3 with no level 2 parent -->

The HTML5 outline algorithm never existed

This one causes real damage because it was in the spec for years and people built patterns around it.

The idea was that <section>, <article>, <nav> and <aside> would create nested outline scopes, so an <h1> inside a nested <section> would automatically be demoted to an effective level 2, 3, and so on. It was elegant. It let you write generic components that always used h1 and let the document nesting sort out the rank.

No browser ever implemented it. No screen reader ever implemented it. The WHATWG eventually removed the outline algorithm from the spec and replaced it with plain advice to use h1h6 levels that reflect actual document structure.

So this is broken:

<!-- The outline algorithm would have made these h2, h3.
     In every real browser and screen reader, they are all h1. -->
<h1>Blog</h1>
<section>
  <h1>Heading Structure</h1>
  <section>
    <h1>Skipped Levels</h1>
  </section>
</section>

If your component library was built on that assumption — and a surprising number of 2015-era design systems were — every page it renders has a flat heading structure regardless of how carefully you nested the markup. Sectioning elements are still valuable for landmarks and for <article> semantics. They just don't change heading rank.

Headings as an extraction contract

Here's the part that has changed in the last few years, and the part worth actually optimising for.

When an AI answer engine or a RAG pipeline ingests your page, it doesn't consume the whole document. It splits it into chunks and retrieves the chunks that match the query. Almost every widely used chunking implementation — LangChain's MarkdownHeaderTextSplitter, LlamaIndex's node parsers, the header-aware splitters in most vector-DB starter kits — splits on heading boundaries and attaches the heading path as metadata on each chunk. You can read the source; this isn't speculation.

What that means concretely: a chunk of your page arrives at the retrieval layer labelled something like ["Heading Structure", "Skipped levels: who actually cares"]. That path is often the only context the model has about where the text came from. If your headings are accurate and self-describing, the chunk is retrievable and correctly attributed. If your headings are Overview, More, Details, and Wrapping Up, the chunk arrives labelled with nothing.

Two important honesty caveats. First, we do not know how the major AI search products rank sources internally — Google's AI Overviews, ChatGPT search and Perplexity have not published their retrieval pipelines, and anyone telling you the exact weighting is guessing. Second, the chunking behaviour above is what open-source tooling does, which is strong evidence about the shape of the problem but not proof that any specific commercial system does the same thing.

What survives both caveats is the structural argument: text that is unambiguously scoped by a descriptive heading is easier to retrieve, quote and attribute than text that isn't, under any plausible implementation. That's the same property that makes a page navigable by screen reader. The accessibility case and the AI-extraction case are the same case, arrived at from different directions, and they reward exactly the same discipline — which is unusual enough in this field to be worth exploiting. If you're working on citation specifically, getting cited by AI search engines goes further into the retrieval side.

Practically, this changes how you write headings:

<!-- Weak: heading carries no information once separated from the page -->
<h2>Overview</h2>
<h2>How it works</h2>
<h2>Conclusion</h2>

<!-- Strong: each heading survives extraction as a standalone label -->
<h2>What a redirect chain costs in crawl budget</h2>
<h2>How 301, 302 and 307 differ in signal handling</h2>
<h2>Auditing chains with curl</h2>

The second set is longer, and that's fine — headings have no length limit and no truncation surface. The one place brevity matters is the <title>, which is a separate element with a real pixel budget; see title tags and meta descriptions for that constraint.

Headings are semantics; CSS is appearance

The single most common source of broken hierarchy is choosing a heading level for its font size. Someone needs smaller text, h4 looks right, h4 ships.

The rule is absolute: pick the level from the document structure, then style it.

<!-- Wrong: h4 chosen because the design wants 16px -->
<h2>Pricing</h2>
<h4>Enterprise</h4>

<!-- Right: correct level, visual size from a class -->
<h2>Pricing</h2>
<h3 class="text-base font-semibold">Enterprise</h3>

The corollary is equally important: text that is bold and large but is not a section heading should not be a heading element. A pull quote, a stat, a card label — those are <p> or <div> or <strong> with styling. Every decorative heading you add is a false entry in the screen reader's headings list and a spurious chunk boundary in a retrieval pipeline.

A useful design-system pattern is to decouple the two axes entirely:

type Level = 1 | 2 | 3 | 4 | 5 | 6;

export function Heading({ level, size, children }: {
  level: Level;              // semantics — required, no default
  size?: "xl" | "lg" | "md" | "sm";  // appearance
  children: React.ReactNode;
}) {
  const Tag = `h${level}` as const;
  return <Tag className={sizeClass[size ?? "md"]}>{children}</Tag>;
}

The important detail is that level has no default. A default is how every component ends up rendering h3 everywhere.

Where CMSs and component libraries break hierarchy

Real-world heading structures are rarely authored by one person. They're assembled at render time from a theme, a page template, several components, and an editor's body content — none of which know about each other. The recurring failures:

Logo wrapped in an H1 on every page. Classic WordPress theme behaviour. Every page's H1 is the site name, and the actual page title is an H2 or, worse, a styled div. Fix in the theme: H1 for the site name on the homepage only, <p> or <div> elsewhere.

Card and teaser components with a hardcoded level. A <Card> renders <h3> internally. Drop three cards under an <h2> and the structure is correct. Drop the same component into a page where the parent context is h3 and you've produced siblings that should be children. Take the level as a prop.

Widget titles bleeding into the main flow. Sidebar and footer widget headings often render at h3 or h4. When the sidebar is a sibling of main content in the DOM, the screen reader's headings list interleaves "Recent Posts" and "Newsletter" with your article's sections. Wrap them in <aside> with an aria-label so they at least sit inside a distinct landmark.

Accordions and tabs. A common mistake is either using a <button> with no heading (invisible in the headings list) or a heading with no button (not operable). The correct pattern is a heading wrapping the control:

<h3>
  <button aria-expanded="false" aria-controls="p1" id="t1">Shipping and returns</button>
</h3>
<div id="p1" role="region" aria-labelledby="t1" hidden>...</div>

Block editors that start at H2 — or don't. Gutenberg and most block editors let an author insert any heading level. If your template already emits the H1, authors adding another H1 in the body is a matter of editorial policy, not code. Constrain the editor's allowed levels if you can.

Markdown pipelines with a leading H1. If your CMS renders the post title as <h1> from frontmatter and the markdown body starts with # Title, you ship two H1s on every post. The convention that avoids this is: title lives in frontmatter, body starts at ##. (It's the rule this blog follows.)

Client-rendered headings. Headings injected after hydration exist for Google, which renders JavaScript, but not for every crawler and not for social scrapers. If a page's entire heading structure is client-side, it's fragile — see Next.js SEO problems for the wider version of that issue.

A five-minute audit

Paste this in the console on any page:

const hs = [...document.querySelectorAll("h1,h2,h3,h4,h5,h6")];
let prev = 0;
console.table(hs.map(h => {
  const level = +h.tagName[1];
  const skip = prev && level > prev + 1 ? `SKIP ${prev}->${level}` : "";
  prev = level;
  return {
    level,
    text: h.textContent.trim().slice(0, 70),
    issue: [skip, h.textContent.trim() ? "" : "EMPTY"].filter(Boolean).join(" ")
  };
}));
console.log("H1 count:", hs.filter(h => h.tagName === "H1").length);

You are looking for four things: how many H1s, any downward skips, any empty headings (a real bug — icon-only or image-only headings with no text alternative), and whether reading the heading text alone gives you an accurate summary of the page. That last check is the one that matters most and the only one no tool can do for you.

SEO Fix Agent includes heading checks — H1 presence and count, hierarchy order, empty headings — in its ~30-check page audit, alongside title, meta and structured-data checks, and its Prompt Studio compiles the findings into a fix prompt ordered by severity. Heading fixes are usually component-level rather than page-level, so the useful workflow is to audit one page per template and fix the component.

What to actually do

Use one H1 per page because it produces a cleaner document, and don't panic if a design forces two. Don't skip levels downward — not because Google minds, but because screen reader users do and because a clean level sequence is what makes a page chunk well. Never pick a heading level for its font size. Write headings that describe their section specifically enough to be understood in isolation, since that's what both a screen reader's headings list and a retrieval chunk get. And check the structure at the component level, because that's where it actually breaks.

#headings#accessibility#semantic-html#ai-search#technical-seo

Audit your page, then ship the fix

SEO Fix Agent runs 30+ technical, content and AI-search checks on a page, then compiles every finding into a severity-ordered prompt your coding agent can execute. 75 free credits, no card.

Start free →

Keep reading