Page must have a level-one heading
The page does not contain a level-one heading (`<h1>`). Screen reader users rely on heading structure to understand page content and navigate efficiently.
Rule Description
This rule ensures that every page has exactly one <h1> element that clearly identifies the page's main content or purpose.
What This Rule Checks
- Presence of at least one
<h1>element - The
<h1>is not empty - The
<h1>contains actual text content (not just whitespace or hidden characters)
What This Rule Does Not Check
- Whether there are multiple
<h1>elements (separate check) - Quality or appropriateness of the H1 text
- Visual styling of the heading
- Complete heading structure/hierarchy
Best Practices
- One H1 per page - Establishes the main topic
- Descriptive text - Should clearly describe the page content
- Early in the DOM - Should appear near the top of the page
- Visible to all - Should be visible unless there's a strong design reason
- Updates with content - In SPAs, should change with route/content changes
Why It Matters
Impact on Users
- Screen reader users cannot quickly understand the page's main topic
- Keyboard navigation users who use heading navigation shortcuts lose the ability to jump to main content
- Users with cognitive disabilities benefit from clear document structure
- SEO - Search engines use H1 to understand page content
- All users benefit from clear visual hierarchy
Real-World Scenario
A screen reader user lands on a page and presses the "H" key to navigate by headings. The page has multiple H2 and H3 headings but no H1. The user hears:
- "Heading level 2, Welcome section"
- "Heading level 3, Our services"
- "Heading level 2, Contact us"
Without an H1, they don't know the main purpose of the page. Is this a homepage? A product page? A blog post?
How to Fix
Solution 1: Add an H1 to the Page
Every page should have exactly one <h1> that describes the page's main content.
Bad Example:
<!-- No H1 --> <body> <header> <div class="logo">Company Name</div> </header> <main> <h2>Welcome to our site</h2> <p>Content here...</p> </main> </body>
Good Example:
<body> <header> <div class="logo">Company Name</div> </header> <main> <h1>Welcome to Company Name</h1> <h2>Our Services</h2> <p>Content here...</p> </main> </body>
Solution 2: Ensure Proper Heading Hierarchy
Headings should follow a logical sequence without skipping levels.
Bad Example:
<!-- Skips from H1 to H3 --> <h1>Main Title</h1> <h3>Subsection</h3> <h2>Section Title</h2>
Good Example:
<!-- Proper hierarchy --> <h1>Main Title</h1> <h2>Section Title</h2> <h3>Subsection</h3> <h3>Another Subsection</h3> <h2>Another Section</h2>
Solution 3: Single H1 Per Page
Use only one H1 per page to establish the main topic.
Bad Example:
<!-- Multiple H1s --> <h1>Company Name</h1> <nav> <h1>Navigation</h1> </nav> <main> <h1>Main Content</h1> </main>
Good Example:
<header> <div class="logo">Company Name</div> <nav aria-label="Main navigation"> <!-- Navigation items --> </nav> </header> <main> <h1>Main Content Title</h1> <h2>Section Heading</h2> </main>
Solution 4: Visually Hidden H1 (Use Sparingly)
In rare cases where design requires, you can visually hide the H1 while keeping it accessible.
Use Case Example:
<!-- Homepage with logo as visual H1 --> <h1 class="visually-hidden">Company Name - Your Solution Provider</h1> <div class="logo" aria-hidden="true"> <img src="logo.png" alt=""> </div> <!-- CSS for visually-hidden --> <style> .visually-hidden { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; } </style>
Solution 5: Dynamic Content Pages
For single-page applications or dynamic content, ensure H1 updates with page changes.
Good Example (React):
function ProductPage({ product }) { return ( <main> <h1>{product.name}</h1> <h2>Description</h2> <p>{product.description}</p> <h2>Specifications</h2> <ul>...</ul> </main> ); }
Good Example (Vue):
<template> <main> <h1>{{ pageTitle }}</h1> <section> <h2>Content</h2> <!-- ... --> </section> </main> </template>
Common Mistakes
1. No H1 on Page
<!-- FAIL --> <body> <header> <img src="logo.png" alt="Company Name"> </header> <main> <h2>Welcome</h2> <p>Content...</p> </main> </body>
2. Using Logo Image Instead of H1
<!-- FAIL - Image alone is not a heading --> <header> <img src="logo.png" alt="Company Name"> </header> <!-- PASS - Logo can complement H1 --> <header> <h1> <img src="logo.png" alt="Company Name"> </h1> </header>
3. Empty H1
<!-- FAIL --> <h1></h1> <h1> </h1> <h1><span></span></h1>
4. H1 with Only Icon
<!-- FAIL --> <h1><i class="icon-home"></i></h1> <!-- PASS --> <h1> <i class="icon-home" aria-hidden="true"></i> Home Page </h1>
5. Wrong Semantic Level
<!-- FAIL - Using H2 styled to look like H1 --> <h2 class="h1-styled">Main Title</h2> <!-- PASS - Use actual H1 and style it --> <h1>Main Title</h1>
Examples by Page Type
Homepage
<body> <header> <img src="logo.png" alt="Company Logo"> <nav>...</nav> </header> <main> <h1>Welcome to [Company Name] - [Main Value Proposition]</h1> <section> <h2>Our Services</h2> <!-- ... --> </section> </main> </body>
Product Page
<main> <h1>Product Name - Professional Widget 3000</h1> <section> <h2>Product Description</h2> <p>...</p> </section> <section> <h2>Specifications</h2> <h3>Technical Details</h3> <h3>Dimensions</h3> </section> </main>
Blog Post
<article> <header> <h1>How to Build Accessible Web Applications</h1> <p class="meta">Published on January 15, 2024</p> </header> <section> <h2>Introduction</h2> <p>...</p> </section> <section> <h2>Key Principles</h2> <h3>Semantic HTML</h3> <h3>ARIA Labels</h3> </section> </article>
Dashboard/Application
<body> <header> <nav>...</nav> </header> <main> <h1>Dashboard Overview</h1> <section> <h2>Recent Activity</h2> <!-- ... --> </section> <section> <h2>Statistics</h2> <h3>This Month</h3> <h3>This Year</h3> </section> </main> </body>
Contact/Form Page
<main> <h1>Contact Us</h1> <form> <fieldset> <legend>Your Information</legend> <!-- form fields --> </fieldset> <h2>How can we help?</h2> <textarea>...</textarea> </form> </main>
Heading Structure Best Practices
Outline Structure
<body> <header> <!-- Site-wide header, no heading needed --> </header> <main> <h1>Page Title</h1> <!-- Level 1 --> <section> <h2>Main Section</h2> <!-- Level 2 --> <p>Content...</p> <h3>Subsection</h3> <!-- Level 3 --> <p>Content...</p> <h3>Another Subsection</h3> <!-- Level 3 --> <p>Content...</p> </section> <section> <h2>Another Main Section</h2> <!-- Level 2 --> <p>Content...</p> </section> </main> <aside> <h2>Related Content</h2> <!-- Level 2 --> </aside> <footer> <!-- Footer content --> </footer> </body>
Landmark Regions with Headings
<body> <header role="banner"> <img src="logo.png" alt="Company"> <nav role="navigation" aria-labelledby="main-nav-heading"> <h2 id="main-nav-heading" class="visually-hidden">Main Navigation</h2> <!-- nav items --> </nav> </header> <main role="main"> <h1>Page Main Title</h1> <!-- main content --> </main> <aside role="complementary" aria-labelledby="sidebar-heading"> <h2 id="sidebar-heading">Related Articles</h2> <!-- sidebar content --> </aside> <footer role="contentinfo"> <h2>Company Information</h2> <!-- footer content --> </footer> </body>
Testing
Manual Testing
- View the page outline using a browser extension (HeadingsMap, WAVE)
- Navigate using heading shortcuts (H key in screen readers)
- Verify one H1 exists and describes the page
- Check that heading levels don't skip (1→2→3, not 1→3)
Screen Reader Testing
NVDA/JAWS: Press H to navigate by headings
Expected: First heading should be H1 announcing page title
Insert+F6: List all headings
Expected: H1 at top of list
Automated Testing
// Check for H1 const h1Elements = document.querySelectorAll('h1'); if (h1Elements.length === 0) { console.error('No H1 found on page'); } else if (h1Elements.length > 1) { console.warn('Multiple H1 elements found'); } // Check H1 has content h1Elements.forEach(h1 => { if (!h1.textContent.trim()) { console.error('Empty H1 element'); } }); // Using axe-core const results = await axe.run(); const h1Violations = results.violations.filter( v => v.id === 'page-has-heading-one' );
Browser DevTools
// Find all heading elements $$('h1, h2, h3, h4, h5, h6') // Generate heading outline Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6')) .map(h => ({ level: h.tagName, text: h.textContent.trim() }));
External Resources
Automate Your Accessibility Testing
Our tool automatically checks for this rule and hundreds of other accessibility issues.
Start Your Free Trial