Fix Heading Order for Better Accessibility
Screen reader users rely on headings to navigate your page. Skipped levels break this navigation and make your content structure unclear.
What's Happening
Headings (<h1> through <h6>) should form a logical hierarchy, increasing by only one level at a time. When you skip from <h2> to <h4>, screen reader users wonder if they missed a section. They cannot tell if the skipped <h3> represents missing content or just poor markup.
Properly ordered headings let users jump between sections, understand the page structure, and orient themselves quickly.
Diagnose
Chrome DevTools
- Open DevTools (F12)
- Press Ctrl+Shift+P (Cmd+Shift+P on Mac)
- Type "Show headings" and select the option
- Review the heading outline - look for skipped levels
Quick console check:
const headings = [...document.querySelectorAll('h1, h2, h3, h4, h5, h6')]
let lastLevel = 0
headings.forEach((h) => {
const level = Number.parseInt(h.tagName[1])
if (level > lastLevel + 1 && lastLevel !== 0) {
console.warn(`Skipped heading level: ${h.tagName} after H${lastLevel}`, h)
}
lastLevel = level
})
Accessibility Tree
- DevTools > Elements > Accessibility pane
- Expand the tree and look at heading structure
- Levels should descend sequentially: 1 > 2 > 3, not 1 > 3
Fix
1. Restructure Heading Levels
Fix the hierarchy by adjusting heading levels:
<!-- Before: Skipped level -->
<h1>Product Catalog</h1>
<h3>Electronics</h3> <!-- Wrong: skipped h2 -->
<h3>Clothing</h3>
<!-- After: Sequential levels -->
<h1>Product Catalog</h1>
<h2>Electronics</h2> <!-- Correct: h2 follows h1 -->
<h2>Clothing</h2>
For nested sections, continue the hierarchy:
<h1>Product Catalog</h1>
<h2>Electronics</h2>
<h3>Phones</h3>
<h3>Laptops</h3>
<h2>Clothing</h2>
<h3>Shirts</h3>
<h3>Pants</h3>
2. Style Independently from Semantics
Use CSS to achieve desired visual sizes without breaking semantics:
<!-- Keep correct heading level, style differently -->
<h2 class="text-3xl font-bold">Main Section</h2>
<h3 class="text-2xl font-semibold">Subsection</h3>
<h4 class="text-xl">Nested Item</h4>
/* Override default heading styles */
.section-title {
font-size: 1.5rem;
font-weight: 600;
}
h2.section-title,
h3.section-title,
h4.section-title {
/* Same visual appearance, correct semantic level */
}
3. Fix Component Headings
Components often hardcode heading levels. Make them configurable:
<!-- Bad: Component always uses h3 -->
<div class="card">
<h3>Card Title</h3>
</div>
<!-- Good: Heading level is contextual -->
<div class="card">
<h2>Card Title</h2> <!-- or h3, h4 depending on context -->
</div>
In component systems, pass the heading level as a prop.
Framework Examples
<script setup>
defineProps({
level: {
type: Number,
default: 2,
validator: v => v >= 1 && v <= 6
}
})
</script>
<template>
<component :is="`h${level}`" class="card-title">
<slot />
</component>
</template>
<Card>
<CardHeading :level="2">Products</CardHeading>
<Card>
<CardHeading :level="3">Electronics</CardHeading>
</Card>
</Card>
function Heading({ level = 2, children, className }) {
const Tag = `h${level}`
return <Tag className={className}>{children}</Tag>
}
// Usage with context for automatic levels
function Section({ children }) {
const parentLevel = useHeadingLevel()
return (
<HeadingLevelProvider value={parentLevel + 1}>
{children}
</HeadingLevelProvider>
)
}
SEO Impact: Passage Ranking
Correct heading structure is critical for Google Passage Ranking.
Google can now index and rank individual passages from a page, independently of the whole page. It relies heavily on <h> tags to understand where one topic ends and another begins.
- Broken Hierarchy: Confuses the crawler about the relationship between sections.
- Logical Hierarchy: Allows Google to snip a specific section (e.g., "How to fix header order") and serve it as a Featured Snippet or direct answer.
Fixing heading order isn't just about tidiness; it's about maximizing your content's surface area in search results.
Verify the Fix
- Re-run Lighthouse - The "Heading elements appear in a sequentially-descending order" audit should pass
- Check heading outline - In DevTools, run:
[...document.querySelectorAll('h1,h2,h3,h4,h5,h6')]
.map(h => `${h.tagName}: ${h.textContent.trim().slice(0, 50)}`)
.join('\n')
The output should show logical nesting without skips.
- Screen reader test - Use heading navigation (H key in NVDA/JAWS) to move through headings. The structure should make sense.
Common Mistakes
- Using headings for visual styling — If you want big bold text that isn't a section heading, use a
<p>or<span>with CSS. Reserve headings for actual document structure. - Multiple h1 elements — While technically allowed in HTML5 with sectioning elements, most screen readers and SEO best practices recommend one
<h1>per page representing the main topic. - Starting at h2 or h3 — The first heading on a page should typically be
<h1>. Starting at a lower level implies missing parent sections. - Using divs with heading roles —
<div role="heading" aria-level="2">works but is more fragile than native<h2>. Use real heading elements. - Hiding headings for visual design — If a section needs a heading for structure but you don't want it visible, use visually-hidden CSS, not
display: nonewhich hides it from screen readers too.
Related Issues
Heading order issues often appear alongside:
- Document Title — The title and h1 should describe the same topic
- Bypass — Proper headings enable skip navigation
- HTML Lang — Both affect how screen readers interpret structure
Test Your Entire Site
Heading structure issues often creep in through reusable components, templates, or CMS content. A card component using <h3> might be correct on one page but break hierarchy on another. Unlighthouse scans every page and flags heading order violations across your entire site, so you can fix them at the source.