Skip to main content
Beta: Front-End Checklist is currently in beta. Some issues are still being fixed. Thanks for your patience.

Use logical heading hierarchy

Headings follow a sequential structure (h1 to h6) that reflects the content hierarchy without skipping levels.

Utilities
Quick take
Typical fix time 15 min
  • Use exactly one h1 per page for the main title
  • Never skip heading levels (h1 → h2 → h3, not h1 → h3)
  • Heading text should describe the content that follows
  • Don't use headings for visual styling—use CSS instead
Why it matters: Screen reader users navigate by headings like a table of contents—skipping levels or poor hierarchy makes content impossible to understand and navigate.

Rule Details

Headings create a document outline that screen readers use for navigation—proper hierarchy is essential.

Code Example

<!-- ✅ Good: Logical hierarchy -->
<h1>Web Accessibility Guide</h1>
 
<h2>Getting Started</h2>
<p>Introduction content...</p>
 
<h3>Prerequisites</h3>
<p>What you need to know...</p>
 
<h3>Tools</h3>
<p>Recommended tools...</p>
 
<h2>Core Principles</h2>
<p>Main principles content...</p>
 
<h3>Perceivable</h3>
<p>Content about perceivability...</p>

Why It Matters

Screen reader users navigate by headings like a table of contents—skipping levels or poor hierarchy makes content impossible to understand and navigate.

Common Mistakes

<!-- ❌ Bad: Multiple h1 elements -->
<h1>Company Name</h1>
<h1>Product Page</h1>
 
<!-- ❌ Bad: Skipping heading levels -->
<h1>Main Title</h1>
<h3>This should be h2</h3>
 
<!-- ❌ Bad: Using headings for styling -->
<h4>I just want this text to be smaller</h4>
 
<!-- ❌ Bad: Non-descriptive headings -->
<h2>Section 1</h2>
<h2>Click Here</h2>

Framework Examples

React Component

interface SectionProps {
  title: string
  level?: 1 | 2 | 3 | 4 | 5 | 6
  children: React.ReactNode
}
 
function Section({ title, level = 2, children }: SectionProps) {
  const Heading = `h${level}` as keyof JSX.IntrinsicElements
 
  return (
    <section aria-labelledby={`heading-${title.toLowerCase().replace(/\s/g, '-')}`}>
      <Heading id={`heading-${title.toLowerCase().replace(/\s/g, '-')}`}>
        {title}
      </Heading>
      {children}
    </section>
  )
}

Next.js Page Structure

export default function BlogPost({ post }) {
  return (
    <article>
      <h1>{post.title}</h1>
 
      <h2>Introduction</h2>
      <p>{post.intro}</p>
 
      {post.sections.map((section, index) => (
        <section key={index}>
          <h2>{section.title}</h2>
          <div dangerouslySetInnerHTML={{ __html: section.content }} />
 
          {section.subsections?.map((sub, subIndex) => (
            <div key={subIndex}>
              <h3>{sub.title}</h3>
              <p>{sub.content}</p>
            </div>
          ))}
        </section>
      ))}
    </article>
  )
}

Visual Styling Without Headings

/* Style text without semantic heading */
.section-label {
  font-size: 1.25rem;
  font-weight: bold;
  color: #333;
}
<!-- Use CSS class, not heading level for styling -->
<p class="section-label">This is styled text, not a heading</p>

Exceptions

  • Evaluate the rendered experience before treating a static-code smell as a blocker; interaction timing, browser behavior, and assistive technology output often determine severity.
  • Not every secondary accessibility issue deserves equal weight; prioritize the issue that most directly blocks perception, operation, or understanding.
  • Avoid adding redundant markup or ARIA solely to satisfy a rule when a simpler semantic implementation would eliminate the issue entirely.

Standards

  • Align the implementation with W3C WAI: WCAG Overview and verify the rendered experience, not only the source code.
  • Align the implementation with MDN: Accessibility and verify the rendered experience, not only the source code.

Verification

Automated Checks

  • Use browser DevTools to list all headings (HeadingsMap extension)

Manual Checks

  • Verify outline shows logical structure without skipped levels
  • Check there's exactly one h1
  • Screen reader: navigate headings with H key to verify order

Use with AI

Copy these prompts to use with your AI assistant, or install the MCP server to use directly from Claude, Cursor, or Windsurf.

Check

Verify implementation

Verify pages have exactly one h1, headings follow sequential order without skipping levels (h1 → h2 → h3), and heading text clearly describes the content that follows. Use accessibility tools to generate a heading outline.

Fix

Auto-fix issues

Structure headings to reflect content hierarchy. Start with h1 for the page title, use h2 for major sections, h3 for subsections. Never skip heading levels or use headings purely for visual styling.

Explain

Learn more

Explain how screen reader users navigate by headings like a table of contents, and why skipped levels or missing structure forces users to work harder to understand page organization.

Review

Code review

Review the rendered markup and interactive states that affect Use logical heading hierarchy. Flag exact elements, roles, labels, focus behavior, or keyboard interactions that violate the rule, and note how to verify the fix with browser accessibility tooling or assistive tech.

Sources

References used to support the guidance in this rule.

Further Reading

Tools and supplementary material for exploring the topic in more depth.

axe DevTools
deque.comTool

Rules that often go hand-in-hand with this one.

Maintain logical heading order

Heading levels should follow a sequential, hierarchical order.

Accessibility
Ensure headings contain text

All heading elements (h1-h6) must have visible, descriptive content.

Accessibility
Use semantic table markup for screen readers

Data tables use proper semantic markup with headers, captions, and scope attributes for screen reader accessibility.

Accessibility
Use correct list structure

Lists (ul, ol) should only contain list item elements (li) to ensure they are correctly interpreted by assistive technology.

Accessibility

Was this rule helpful?

Your feedback helps improve rule quality. This stays internal for now.

Loading feedback...
0 / 385