# Heading elements not in sequentially-descending order: what it means and how to fix it

**Audit ID:** `heading-order` · **Category:** Accessibility

This Lighthouse audit fails when your page skips heading levels, for example, an `<h1>` followed by an `<h3>` with no `<h2>` between them. Screen readers expose the heading hierarchy as a document outline; skipped levels break that outline.

## TL;DR

- **What:** Heading levels (h1 → h2 → h3 → h4...) must not skip a level downward.
- **Why it matters:** Screen reader navigation by heading is broken when levels jump.
- **Fix:** Use heading tags by semantic level, not by visual size. Style with CSS, not with which tag you pick.

## What the audit checks

Lighthouse walks the rendered DOM in source order and inspects every heading (`<h1>`–`<h6>`). It flags pages where a heading is more than one level lower than the previous one.

```html
<h1>Page title</h1>
<h3>Subheading</h3>  <!-- ❌ skipped h2 -->
```

Going back up is fine, `<h3>` followed by `<h2>` is OK. The audit only fails on downward jumps.

## Why it matters

Screen readers expose page structure as a navigable outline. Users press `H` (or NVDA: 1, 2, 3 keys for specific levels) to jump between sections. When levels are skipped, the outline reads as: "Page title (level 1), Subheading (level 3, level 2 missing)." It implies there's a level 2 they missed, which is confusing.

It also hurts SEO subtly: Google's structured understanding of your page relies in part on the heading outline.

## The fix

Use heading levels semantically, not visually. Pick the tag for what the content *means*, then size it with CSS.

```html
<!-- BEFORE: tag chosen for visual size -->
<h1>Our pricing plans</h1>
<h3>Free tier</h3>
<h3>Pro tier</h3>

<!-- AFTER: semantic hierarchy -->
<h1>Our pricing plans</h1>
<h2>Free tier</h2>
<h2>Pro tier</h2>
```

If you want `<h2>` to look smaller than the original `<h3>`, do that in CSS:

```css
h2 { font-size: 1.5rem; font-weight: 600; }
```

## Common scenarios

### Scenario: Card grids inside a section

```html
<section>
  <h2>Features</h2>           <!-- correct level for the section -->
  <article>
    <h3>Real-time sync</h3>   <!-- h3 because it's nested in h2's section -->
    <p>...</p>
  </article>
  <article>
    <h3>Offline mode</h3>     <!-- also h3, sibling article -->
    <p>...</p>
  </article>
</section>
```

### Scenario: Sidebar with its own headings

The page's main `<main>` outline doesn't include `<aside>` content. You can restart heading levels inside an `<aside>` if you want, but most accessibility experts recommend continuing the document hierarchy.

### Scenario: Component libraries (e.g. design systems)

If your design system exports a `<Card>` component that hard-codes `<h3>` internally, you'll break heading order whenever you use it inside a section that doesn't have an h2. Solution: make the heading level a prop.

```jsx
<Card heading="h2" title="Feature name" />
```

## Pitfalls to avoid

- **Don't have more than one `<h1>`.** HTML5 technically allows it via sectioning, but Lighthouse and screen readers expect one h1 per page.
- **Don't use `<h1>` for branding/logo.** That's what the `<header>` element + alt text is for.
- **Don't pick a heading tag because of its default size.** Use CSS for sizing.

## Verification

1. Re-run Lighthouse. The `heading-order` audit should pass.
2. View the document outline with a screen reader's heading list (VoiceOver: VO+U → Headings; NVDA: NVDA+F7).
3. Use a browser extension like "Headings Map" or axe DevTools to visualize the heading tree.

## Related audits

- [Document does not have a main landmark](/audits/document-main-landmark), landmark structure
- [Image alt attributes](/audits/image-alt), screen reader content
- [Meta description](/audits/meta-description), SEO

---

Audit your URL at https://lighthouse-md.com.
