HTML Landmarks for accessibility

Why we need landmarks

Without landmarks, a screen reader user has one way to get through a page: listen to it top to bottom. Landmarks give them a shortcut - press a key and jump straight to the navigation, the main content, or the footer, the same way a sighted user’s eye jumps straight to the header when they land on a page. That’s the whole point of <header>, <nav>, <main>, <aside>, and <footer>: each one marks out a region a screen reader can announce and let the user skip to directly, and avoiding skipped heading levels does something similar one level down, for jumping between sections within the content itself.

Primary Landmark Elements

  • <header> - defines your header
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <link rel="stylesheet" href="./main.css" />
    </head>
    <body>
        <header>Define your complete header including navigation etc.</header>
    </body>
</html>
  • <nav> - defines your navigation
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <link rel="stylesheet" href="./main.css" />
    </head>
    <body>
        <header>
            <nav>
                <ul>
                    <li></li>
                    <li></li>
                    <li></li>
                    <li></li>
                </ul>
            </nav>
        </header>
    </body>
</html>
  • <footer> - defines your footer in which we can add the copyrights
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <link rel="stylesheet" href="./main.css" />
    </head>
    <body>
        <footer>Your footer content goes here...</footer>
    </body>
</html>
  • <main> - it is the main container in which we can place our content
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <link rel="stylesheet" href="./main.css" />
    </head>
    <body>
        <main>
            <div>
                <h1>Heading</h1>
                <p>Lorem ipsum, dolor sit amet consectetur adipisicing elit. At, dolores.</p>
            </div>
        </main>
    </body>
</html>
  • <aside>
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <link rel="stylesheet" href="./main.css" />
    </head>
    <body>
        <aside>
            <h1>This is heading text in aside Tag</h1>
            <p>This is paragraph text in aside Tag</p>
        </aside>
    </body>
</html>
  • <section> - it is the container in which we can place our content and make them separate
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <link rel="stylesheet" href="./main.css" />
    </head>
    <body>
        <header>
            <nav>Your navigation structure goes here...</nav>
        </header>
        <main>
            <section>
                <div>
                    <h1>Heading</h1>
                    <p>Lorem ipsum, dolor sit amet consectetur adipisicing elit. At, dolores.</p>
                </div>
            </section>
            <section>Second content here</section>
            <section>Third content here</section>
        </main>
        <footer>Your footer content goes here...</footer>
    </body>
</html>

Primary Landmark Roles:

ElementsImplicit ARIA Role
<header>role="banner"
<nav>role="navigation"
<main>role="main"
<aside>role="complementary"
<footer>role="contentinfo"
<section>role="region" (when labeled)
<form>role="form" (when labeled)

Note: <header> maps to banner and <footer> maps to contentinfo only when they are direct children of <body>. When nested inside <article>, <section>, or other sectioning elements, they do not carry those implicit roles.

For a broader look at when to reach for these elements versus a plain <div>, see semantic HTML vs div soup.

Understanding it with the example

As showing in the image below is the example of current page without using a landmark

Missing landmark

Now let’s fix the issue showing in the axeDevtools - Document should have one main landmark. To fix this issue we have added one role="main" to the container of the page where our main content lies and notice that now we are not getting the error.

Fixed missing landmark

Scenarios and Edge Cases

Multiple identical landmarks on a page

When a page contains more than one instance of the same landmark (e.g., two <nav> elements), screen readers list them identically, making it impossible for users to distinguish between them.

Solution: Use aria-label or aria-labelledby to give each landmark a unique name:

<nav aria-label="Main navigation">
    <ul>
        <li><a href="/">Home</a></li>
        <li><a href="/about">About</a></li>
    </ul>
</nav>

<nav aria-label="Footer navigation">
    <ul>
        <li><a href="/privacy">Privacy</a></li>
        <li><a href="/terms">Terms</a></li>
    </ul>
</nav>

Landmarks in Single Page Applications (SPAs)

  • In SPAs, the page does not fully reload on navigation. Screen readers may not announce the new content automatically.

  • Best practices:

  • Move focus to the new page’s <h1> or <main> element after a route change.

  • Use an aria-live="polite" region to announce the page title change.

  • Ensure the <title> element is updated on each route change.

// After route change
document.title = 'New Page Title - My App';
const mainHeading = document.querySelector('h1');
if (mainHeading) {
    mainHeading.setAttribute('tabindex', '-1');
    mainHeading.focus();
}

Hidden and off-canvas landmarks

For skip links, mega menus, and hamburger-menu patterns in full detail, see accessible navigation and skip links - the landmark rules below cover the piece specific to visibility state.

  • Off-canvas navigation (hamburger menus) must use aria-hidden="true" when closed so screen readers skip them.

  • When the menu opens, remove aria-hidden and manage focus appropriately.

  • Content behind an open modal or overlay should have aria-hidden="true" or inert applied to prevent screen reader access.

<!-- Closed state -->
<nav id="mobile-nav" aria-label="Mobile navigation" aria-hidden="true">
    <!-- nav items -->
</nav>

<!-- Open state (via JavaScript) -->
<nav id="mobile-nav" aria-label="Mobile navigation" aria-hidden="false">
    <!-- nav items -->
</nav>

Nested landmarks

  • Landmarks should not be unnecessarily nested. For example, placing a <nav> inside an <aside> inside a <section> creates a deeply nested structure that screen readers must enumerate verbosely.

  • Keep the landmark tree as flat and meaningful as possible.

Using <section> as a landmark

  • <section> only becomes a landmark when it has an accessible name via aria-label or aria-labelledby. Without a label, it is just a generic grouping element.
<!-- This IS a landmark -->
<section aria-labelledby="faq-heading">
    <h2 id="faq-heading">Frequently Asked Questions</h2>
</section>

<!-- This is NOT a landmark -->
<section>
    <h2>Frequently Asked Questions</h2>
</section>

Resources

If you found this helpful, share it with someone who's building for the web.

- Vinay Ranjan