// guide
Headings & Landmarks: Page Structure
Screen reader users almost never read a page top to bottom — they jump between headings and landmarks the way sighted users skim with their eyes, using them as a live table of contents to find what they came for. Getting this structure right is the single highest-leverage accessibility decision you make on any page, and it makes content more scannable for everyone.
// 02 · the heading hierarchy rules
The Heading Hierarchy Rules
Heading levels (<h1> through <h6>) describe the structure of your content, not its visual size. Four rules cover almost every case:
- Exactly one
<h1>per page. The h1 is the page's primary subject — usually the same as the page title or a close variant. Multiple h1s confuse the document outline and make heading navigation noisy. - Never skip levels descending. An h2 can be followed by another h2 or an h3, but never an h4. Skipping a level signals to assistive tech that a section is missing.
- Siblings share a level. Sections of equal importance use the same heading level. If "Pricing" is an h2, "Features" should also be an h2, not an h3.
- Nest by topic, not by visual indent. A subsection's heading is one level deeper than its parent's. The hierarchy reflects what the content is, not how the page looks.
Good hierarchy
<h1>Accessible Forms</h1>
<h2>Labels</h2>
<h3>Visible Labels</h3>
<h3>Hidden Labels</h3>
<h2>Validation</h2>
<h3>Inline Errors</h3>
<h3>Submit Errors</h3>
<h2>Required Fields</h2>
The outline reads like a coherent table of contents. Every level descends one step at a time. Siblings share a level. A screen reader user pressing 2 repeatedly jumps through the three top-level topics in order.
Broken hierarchy
<h1>Accessible Forms</h1>
<h4>Labels</h4> <!-- Skipped h2 and h3 -->
<h2>Visible Labels</h2> <!-- Jumped back up -->
<h3>Validation</h3> <!-- Sibling of "Labels" but at a different level -->
Each line breaks one of the rules. The outline is unreadable. A screen reader user pressing 2 would jump into a subsection, miss the top-level topics, and have no way to know how the content is organized.
h3 followed by h2 is perfectly correct. The rule is only about descending: never skip a level on the way down.
// 03 · choosing heading text
Choosing Heading Text
A heading is a label. Read out of context, it should still describe the content beneath it. Screen reader users hear headings in lists — without prose between them — so vague headings are useless.
Front-load the keyword
The most informative word should come first. Screen reader users skimming a heading list often only hear the first few words before deciding whether to continue.
- Bad: "Introduction to how screen readers navigate by heading"
- Good: "How screen readers navigate by heading"
Avoid generic openers
"Welcome", "Introduction", "Overview", and "Hello" tell the user nothing. They're noise in a heading list.
- Bad: "Welcome"
- Good: "How screen readers navigate by heading"
Don't repeat the page title
The h1 already says "Accessible Forms". The first h2 should not be "Accessible Forms" or "About Accessible Forms". Move into the actual content.
Describe what's beneath, not above
The heading labels the section that follows it. "What we just covered" is wrong; "Common pitfalls" is right.
// 04 · visual vs semantic hierarchy
Visual vs Semantic Hierarchy
Designers often want a "small bold thing" at the top of a paragraph — a label, a category, a tagline. That's not a heading. A heading is a structural anchor that screen readers list in the table of contents. If something is decorative or a label, use the right element for the job.
Use <p><strong> or <b> for emphasis
<!-- Wrong: not a section heading, just bold text -->
<h6>Available now</h6>
<p>Our newest plan launches today.</p>
<!-- Right: emphasis without polluting the heading outline -->
<p><strong>Available now.</strong> Our newest plan launches today.</p>
Don't fake a heading with a styled <div>
If the text genuinely is a section heading, use a real heading element. Screen readers don't see your CSS — they see HTML.
<!-- Wrong: looks like a heading, isn't one -->
<div class="heading-style">Pricing</div>
<!-- Right: real heading, can be styled however you want -->
<h2 class="heading-style">Pricing</h2>
The semantic level must match the visual hierarchy
An h3 should never look bigger than the h2 above it. If your design has a "small section header" that visually comes before a "large section header", the small one is the parent — they're at the wrong levels. Fix the markup, not the CSS.
Conversely, it's fine to use a CSS class to size a heading down or up within the correct level. Visual size doesn't have to match level for level — just the relative hierarchy. <h2 class="h3-size"> is acceptable; <div class="h2"> is not.
<p><strong> for emphasis. Use CSS classes for visual styling. Never substitute one for another.
// 05 · the html5 landmark elements
The HTML5 Landmark Elements
HTML5 introduced semantic elements that map directly to ARIA landmark roles. Use them. They give screen reader users a built-in navigation menu of your page's regions.
| Element | Landmark role | When it counts |
|---|---|---|
<header> |
banner |
Only at the top level of the page. Inside <article>, <section>, or <main>, it's not a landmark — just a structural grouping. |
<nav> |
navigation |
Always a landmark. Use for site nav, page nav, breadcrumbs, table of contents, footer link groups. |
<main> |
main |
Always a landmark. Only one per page. Wraps the unique content of this page (not the chrome). |
<aside> |
complementary |
Always a landmark. Sidebars, related-content panels, pull quotes, ads. |
<footer> |
contentinfo |
Only at the top level of the page. Inside <article> or <section>, it's not a landmark. |
<section> |
region |
Only counts as a landmark if it has an accessible name (aria-label or aria-labelledby). Without a name, it's just a generic block. |
<article> |
— | Not a landmark. Used for self-contained content (a blog post, a news item) but doesn't appear in the landmark list. |
<form> |
form |
Only counts as a landmark when given an accessible name. Most forms don't need to be landmarks — leave them unnamed. |
<search> |
search |
Newer element (2023). Always a landmark. Wraps a site search form. Fall back to <div role="search"> if you need older browser support. |
The pattern: reach for the native HTML element first. <nav> is better than <div role="navigation">. Native elements come with the role for free, and they survive across frameworks, CSS resets, and DOM changes that strip ARIA attributes.
// 06 · aria landmark roles
ARIA Landmark Roles
Every HTML5 landmark element has an equivalent ARIA role. Use the role only when the native element isn't available — for example, in legacy code, framework-generated markup that strips semantic elements, or DOM constraints that force a <div>.
| ARIA role | Native equivalent | Notes |
|---|---|---|
role="banner" |
<header> at top level |
Only one banner per page. |
role="navigation" |
<nav> |
Multiple allowed. Label them if there's more than one. |
role="main" |
<main> |
Only one main per page. |
role="complementary" |
<aside> |
Multiple allowed. Label if more than one. |
role="contentinfo" |
<footer> at top level |
Only one contentinfo per page. |
role="search" |
<search> |
Use the role if you need broader browser support than <search> currently has. |
role="form" |
<form> with name |
Requires an accessible name to register as a landmark. |
role="region" |
<section> with name |
Requires a label. Without aria-label or aria-labelledby, the role does nothing. |
<nav>, <main>, <aside> first. Use the role only when something prevents the native element. Doubling up (<nav role="navigation">) is harmless but redundant — drop the role.
// 07 · labeling multiple landmarks
Labeling Multiple Landmarks
Most pages have more than one navigation region — site nav at the top, footer links at the bottom, maybe a breadcrumb in between. To a screen reader, those are three "navigation" landmarks. Without labels, the user hears "navigation, navigation, navigation" and has no way to tell them apart.
Solve it by labeling each one with aria-label:
<nav aria-label="Main">
<ul>...</ul>
</nav>
<nav aria-label="Breadcrumb">
<ol>...</ol>
</nav>
<nav aria-label="Footer">
<ul>...</ul>
</nav>
The same applies to multiple <aside> elements, multiple labeled <section> regions, or multiple <form> landmarks. Whenever there are two or more of the same landmark type, label every one of them.
Don't include the role in the label
Screen readers already announce the role. Adding it to the label makes the announcement repeat itself.
- Bad:
<nav aria-label="Main navigation">— announces as "Main navigation, navigation" - Good:
<nav aria-label="Main">— announces as "Main, navigation" - Bad:
<section aria-label="About region"> - Good:
<section aria-label="About">
Use aria-labelledby when a visible heading exists
If a section already has a visible heading, point to it instead of duplicating the text:
<section aria-labelledby="pricing-heading">
<h2 id="pricing-heading">Pricing</h2>
...
</section>
This keeps the visible label and the accessible label in sync. If the heading text changes, the landmark name updates automatically.
// 08 · the skip link pattern
The Skip Link Pattern
A skip link is a hidden link at the very top of the page that jumps focus past the navigation directly to the main content. It's invisible until a keyboard user tabs to it, at which point it appears in the top-left corner. Pressing Enter scrolls and moves focus to the main content area.
Why? A keyboard or screen reader user landing on a page shouldn't have to tab through 30 nav links to reach the article they came for. The skip link is the first focusable element on the page, so a single Tab + Enter gets them straight to the content.
The HTML
<body>
<a href="#main-content" class="skip-link">Skip to main content</a>
<header>...</header>
<nav>...</nav>
<main id="main-content">
...
</main>
</body>
The CSS
.skip-link {
position: absolute;
top: -40px;
left: 0;
padding: 0.5rem 1rem;
background: #000;
color: #fff;
z-index: 100;
text-decoration: none;
}
.skip-link:focus {
top: 0;
}
The link is positioned off-screen until it receives focus, then it slides into view. Don't use display: none or visibility: hidden — those remove the link from the tab order entirely, defeating the point.
Make the target a real landmark
The skip link's target should be the <main> element. Real landmarks make the skip target meaningful for screen reader users too — when focus lands inside <main>, the screen reader announces "main, region", confirming the jump worked.
For full implementation details and edge cases (multiple skip targets, focus visibility, persistent visibility variants), see the Skip Links pattern.
// 09 · common structure mistakes
Common Structure Mistakes
<main> elements
Only one <main> per page. The HTML spec is explicit. If you have two, screen readers either pick one arbitrarily or announce both, neither of which is what you want. Wrap the unique page content in a single <main>; everything else (nav, footer, sidebars) goes outside it.
<h2> followed by <h4> breaks the hierarchy. If you want a smaller-looking heading, use CSS to size it down — keep the level correct. Visual size and semantic level are independent: <h3 class="small"> is fine; <h4> in place of an <h3> is not.
<article>
<article> is for self-contained content — a blog post, a news item, a forum reply. It's not a layout wrapper. Putting your page header, nav, and footer inside <article> tells screen readers the entire page chrome is part of one syndicatable piece of content. Use <main> for primary content; reserve <article> for actual articles.
<section> without a label
<section> only becomes a region landmark if it has an accessible name. <section>Sidebar content</section> is just a styled div — no landmark, no benefit. Either add aria-labelledby pointing to a heading, or use a more specific element like <aside>.
<main> even with a skip link
Skip links work better when the target is a real landmark. <div id="main-content"> is a valid skip target, but <main id="main-content"> is announced as "main, region" when focus lands there, confirming to screen reader users that the jump succeeded.
// 10 · anatomy of a well-structured page
Anatomy of a Well-Structured Page
Here's a typical page with every structural element in its right place. Comments explain why each is there.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Pricing — Acme</title>
</head>
<body>
<!-- Skip link: first focusable element so keyboard users
can bypass the nav -->
<a href="#main-content" class="skip-link">Skip to main content</a>
<!-- Top-level <header> = banner landmark.
Holds branding and primary nav. -->
<header>
<a href="/" aria-label="Acme home">Acme</a>
<!-- Labeled because there's another <nav> in the footer -->
<nav aria-label="Main">
<ul>
<li><a href="/features/">Features</a></li>
<li><a href="/pricing/">Pricing</a></li>
</ul>
</nav>
</header>
<!-- <main> = main landmark. Skip link target.
Only one per page. -->
<main id="main-content">
<!-- Breadcrumb is a nav, so label it -->
<nav aria-label="Breadcrumb">
<ol>
<li><a href="/">Home</a></li>
<li><span aria-current="page">Pricing</span></li>
</ol>
</nav>
<!-- Exactly one <h1> -->
<h1>Pricing plans</h1>
<!-- h2 children of the h1 -->
<section aria-labelledby="plans-heading">
<h2 id="plans-heading">Available plans</h2>
<h3>Starter</h3>
<p>...</p>
<h3>Pro</h3>
<p>...</p>
</section>
<section aria-labelledby="faq-heading">
<h2 id="faq-heading">FAQ</h2>
<p>...</p>
</section>
</main>
<!-- <aside> = complementary landmark.
Related content, not primary. -->
<aside aria-labelledby="related-heading">
<h2 id="related-heading">Related</h2>
<ul>...</ul>
</aside>
<!-- Top-level <footer> = contentinfo landmark.
Only one per page. -->
<footer>
<nav aria-label="Footer">
<ul>
<li><a href="/privacy/">Privacy</a></li>
<li><a href="/terms/">Terms</a></li>
</ul>
</nav>
<p>© 2026 Acme</p>
</footer>
</body>
</html>
A screen reader user opening this page sees: skip link first, then a landmark menu of "banner, main, complementary, contentinfo" plus three navigations labeled "Main", "Breadcrumb", "Footer". The heading list reads: "Pricing plans, Available plans, Starter, Pro, FAQ, Related". Both lists describe the page perfectly.
// 11 · testing page structure
Testing Page Structure
You can verify your structure without a screen reader. Run through these in order:
- View the heading outline. Install a browser extension like HeadingsMap, Accessibility Insights, or axe DevTools. The outline panel should read like a coherent table of contents — every level descends one step at a time, no skips, no extras.
- List the landmarks. Use a landmark navigator (NVDA
Dkey, JAWSRkey, VoiceOver rotor → Landmarks). Every major region of the page should be reachable. There should be exactly one banner, one main, and one contentinfo. Multiple navigations and complementaries should each have distinct labels. - Disable CSS. In Firefox: View → Page Style → No Style. In Chrome: use the Web Developer extension or DevTools. The page should still be readable in source order. If content collapses into nonsense, the DOM order doesn't match the visual order, and screen reader users are reading the wrong thing.
- Tab from the top. Open the page, press Tab once. The skip link should be the first focusable element, even if it's invisible until focused. Press Tab again — focus should land on the first nav link.
- Run an automated audit. Lighthouse, axe DevTools, and WAVE all flag missing landmarks, multiple
<main>elements, and skipped heading levels. Automation can't catch bad headings (the text is opaque to it), but it catches the structural mistakes reliably.
For deeper testing methodology with screen readers, see the Screen Reader Testing guide and the Keyboard Testing guide.
// 12 · wcag success criteria
WCAG Success Criteria
| Criterion | Level | How structure satisfies it |
|---|---|---|
| 1.3.1 Info and Relationships | A | Structural relationships between content (sections, headings, regions) must be programmatically determinable. Real heading elements and HTML5 landmarks satisfy this; visual styling alone does not. |
| 2.4.1 Bypass Blocks | A | Users must be able to skip repeated content (like nav). Skip links and landmarks are the two standard techniques. A page with a <main> landmark and a working skip link satisfies this. |
| 2.4.6 Headings and Labels | AA | Headings (and form labels) must describe the topic or purpose of the content they introduce. "Welcome" fails; "How to install" passes. |
| 2.4.10 Section Headings | AAA | Where content is organized into sections, those sections must have headings. Long-form pages should not have unlabeled blocks of content. |
| 1.3.6 Identify Purpose | AAA | The purpose of regions, links, and form fields can be programmatically determined. Landmark roles identify the purpose of each region (main, navigation, complementary, etc.). |