slop audit
Blog post 20 / Accessibility and structure

HTML heading structure: developer testing guide

Use this HTML heading structure guide to order H1-H6 tags, find skipped levels, test rendered pages, and keep accessible headings in your release checks.

By Slop Audit · 8 min read

An HTML heading structure gives each page a readable outline. Developers use h1 through h6 tags to label the page, its main sections, and any subsections. Before launch, check the rendered outline for a missing H1, repeated H1 tags, skipped levels, and labels that do not describe the content below them.

The MDN heading elements reference recommends one H1 that describes the page and a nested sequence that does not skip levels. Good markup helps people scan the page and lets assistive technology expose a list of headings for navigation. It also gives crawlers and other tools a stable content structure.

HTML heading structure checklist

Use this short pass on each release-critical route:

  1. Give the page one H1 that identifies its main purpose.
  2. Put major page sections under H2 tags.
  3. Use H3 through H6 for subsections of the preceding section.
  4. Do not choose a heading tag for its default font size.
  5. Make each heading describe the content that follows.
  6. Inspect the server HTML and the rendered page.
  7. Add a heading structure test to CI for templates and key routes.

This checklist is a release convention, not a claim that one markup defect causes a search penalty. Search results depend on many signals. The immediate value comes from a page outline that people and tools can understand.

Build the heading hierarchy around the page purpose

Start with the page title as the root of the outline. A pricing page might use this structure:

<main>
  <h1>Acme pricing</h1>

  <section>
    <h2>Plans</h2>
    <article>
      <h3>Starter plan</h3>
      <p>Features and price...</p>
    </article>
    <article>
      <h3>Team plan</h3>
      <p>Features and price...</p>
    </article>
  </section>

  <section>
    <h2>Pricing questions</h2>
    <h3>Can I change plans?</h3>
    <p>Answer...</p>
  </section>
</main>

Read the heading text in order: “Acme pricing, Plans, Starter plan, Team plan, Pricing questions, Can I change plans?” A reader can infer the page and the parent section for each topic. The W3C headings tutorial explains that headings communicate content organization and support in-page navigation in browsers and assistive technology.

Use the same outline test during design review. If a card title must become an H3 to match its visual size, change the CSS. Do not change its semantic rank.

Keep one clear H1

Treat one H1 per page as a practical release rule. The H1 should identify the page rather than the site-wide logo, navigation, or a promotional slogan. It can resemble the HTML title, but each element has a different job: the title names the browser document and search result candidate, while the H1 names the visible page content.

The HTML standard has permitted some multiple-H1 patterns, yet MDN recommends one H1 as the clearest practice. A repeated H1 does not prove that a page is inaccessible or unable to rank. It creates an ambiguous root for reviewers and tools, so use H2 tags for major sections.

Keep the H1 present in the initial HTML when your framework can render it on the server. If the page uses client rendering, verify that the H1 appears after the application loads and remains present after route changes. Pair this check with the title tag and meta description checklist so the visible heading and search metadata describe the same page.

Fix skipped heading levels without adding empty sections

A downward jump from H1 to H3 suggests that the outline lacks an H2 parent. A jump from H2 to H4 has the same problem. The W3C G141 heading technique shows well-nested heading ranks as one way to support document structure.

This sequence skips a level:

<h1>API documentation</h1>
<h3>Authentication</h3>

Choose the fix from the content relationship. Change “Authentication” to H2 if it starts a main section. Add a descriptive H2 parent when a real parent section exists. An empty H2 hides the content problem instead of fixing it.

Heading levels can move back up when a subsection ends. H4 followed by H2 is valid when the new H2 begins another main section. A heading structure test should flag downward skips, such as H2 to H4, and allow upward returns.

Use HTML heading tags for meaning

Heading tags label sections. CSS controls their appearance. A bold paragraph, card label, or oversized div does not enter the document's heading list. A heading used to make text large can add a false section.

Prefer native h1 through h6 elements. The MDN ARIA heading role reference recommends native headings and reserves role="heading" with aria-level for legacy code that cannot use semantic HTML. Native elements require less markup and receive broad browser support.

Descriptive wording matters after the tag sequence passes. W3C's guidance for WCAG 2.4.6 asks headings and labels to describe their topic or purpose. Replace repeated labels such as “Learn more” with the section's subject, such as “Deployment limits” or “Supported browsers.”

Test the rendered HTML heading structure

Source code can show the right component while production renders a different outline. A CMS editor may select H4 for its size. A responsive component may duplicate a hidden heading. A client-side route may retain the previous page's H1.

Run this in the browser console on the deployed route:

const headings = [...document.querySelectorAll("h1,h2,h3,h4,h5,h6")]
  .filter((node) => node.getClientRects().length > 0)
  .map((node) => ({
    level: Number(node.tagName.slice(1)),
    text: node.textContent.trim(),
  }));

console.table(headings);

Review the result as an outline. Confirm that each visible section title appears, decorative text stays out, and no heading is empty. Test a narrow viewport too because responsive variants can change which nodes the browser shows. The broader accessibility checks before launch cover focus, labels, contrast, and target size beyond this heading check.

Add a heading structure test to CI

A short browser test can protect shared templates from missing H1 tags and skipped ranks:

import { expect, test } from "@playwright/test";

test("keeps a logical heading structure", async ({ page }) => {
  await page.goto("https://example.com/pricing");

  const headings = await page
    .locator("h1, h2, h3, h4, h5, h6")
    .evaluateAll((nodes) => nodes
      .filter((node) => node.getClientRects().length > 0)
      .map((node) => ({
        level: Number(node.tagName.slice(1)),
        text: node.textContent?.trim() ?? "",
      })));

  expect(headings.filter(({ level }) => level === 1)).toHaveLength(1);
  expect(headings.every(({ text }) => text.length > 0)).toBe(true);

  for (let index = 1; index < headings.length; index += 1) {
    expect(headings[index].level).toBeLessThanOrEqual(
      headings[index - 1].level + 1,
    );
  }
});

Use a route table if one template serves product pages, articles, and documentation. Store failures as the observed heading sequence so a developer can see whether the defect came from the page content or the shared layout. Add the test to your website regression testing baseline and rerun it after CMS, navigation, or component changes.

Check HTML heading structure with Slop Audit

Run Slop Audit on the public URL before you share it. The Browser Run records the visible H1 through H3 sequence and reports a missing H1, more than one H1, an outline that does not begin with H1, or a skipped rank in those levels. The report keeps the observed structure with the finding so you can compare the evidence after a fix.

Slop Audit checks one public page and limits automated heading evidence to H1 through H3. It does not judge every H4 through H6 relationship, prove that each label describes its section, test a private route, or certify WCAG conformance. Keep the full H1-H6 CI test and a human outline review for those decisions.

Finish with the complete website launch checklist, then save the tested URL and release identifier beside the heading result. A later regression should point to a route and heading sequence that another developer can reproduce.