Screen reader users do not scroll. They jump. Press D in NVDA or open the VoiceOver rotor and the page collapses into a short list of regions: banner, navigation, main, complementary, contentinfo. If your theme does not expose those regions, that list is empty and the only way through your page is the Tab key, one link at a time.
This guide is a practical walkthrough of ARIA landmarks in a WordPress theme: where each role belongs in classic template files, how to do the same thing in a block theme, and how to fix the landmark errors that audit tools flag most often (duplicate main regions and unlabeled navigation menus top the list). Anyone digging further should read Correcting Missing or Incorrectly Labeled Landmarks.
What ARIA Landmarks Actually Do
Landmarks mark the major regions of a document so assistive technology can navigate them directly. They do not change how the page looks. They change how fast a non-visual user can reach your content.
The important detail that trips up most theme developers: HTML5 sectioning elements already carry implicit landmark roles. You rarely need to write role="banner" by hand. You need to use the right element in the right place, and label it when there is more than one of a kind.
Landmark Reference Table
| HTML element | Implicit ARIA role | Use it for | Needs a label? |
|---|---|---|---|
<header> |
banner (only when not inside article, aside, main, nav or section) | Site header: logo, tagline, primary nav | No (one per page) |
<nav> |
navigation | Menus, breadcrumbs, pagination | Yes, if more than one |
<main> |
main | The unique content of the page | No (exactly one per page) |
<aside> |
complementary | Sidebars, widget areas | Yes, if more than one |
<footer> |
contentinfo (same nesting rule as header) | Site footer: copyright, legal, credits | No (one per page) |
<form role="search"> |
search | Site search form | Yes, if more than one |
<section> |
region only when it has an accessible name | Named content blocks you want in the landmark list | Yes, always |
Rule of thumb: use the native element, add aria-label only to disambiguate, and never add a redundant role such as <nav role="navigation">.

The 6 Landmark Mistakes Audit Tools Flag Most Often
- Two or more
mainregions. Usually the theme template outputs one and a page builder, WooCommerce template override or a plugin outputs another. - Unlabeled navigation menus. Three
<nav>elements all announced as “navigation” is useless to a screen reader user. - No
mainat all. Content sits in<div id="content">, so the skip link jumps to a non-landmark and the rotor shows nothing. - Content outside landmarks. Breadcrumbs, notification bars or cookie banners floating between
</header>and<main>are invisible in landmark navigation. - Nested banner or contentinfo. A
<footer>inside a post<article>is not a contentinfo landmark, but a<footer>placed inside a wrapperdivthat is itself inside<main>loses its landmark status too. - Landmark soup. Twelve
role="region"wrappers on one homepage. Landmarks are a table of contents, not a fence around every block.

Part 1: ARIA Landmarks in a Classic WordPress Theme
Classic themes give you direct control because you are editing PHP templates. Here is the correct placement, file by file. For the wider picture, see What are ARIA Landmarks: The Guide You Need.
header.php: skip link, banner and primary nav
<body <?php body_class(); ?>>
<?php wp_body_open(); ?>
<a class="skip-link screen-reader-text" href="#primary">
<?php esc_html_e( 'Skip to content', 'ppp' ); ?>
</a>
<header id="masthead" class="site-header">
<div class="site-branding">
<?php the_custom_logo(); ?>
</div>
<?php
wp_nav_menu(
array(
'theme_location' => 'primary',
'container' => 'nav',
'container_id' => 'site-navigation',
'container_class' => 'main-navigation',
'container_aria_label' => __( 'Primary', 'ppp' ),
'menu_id' => 'primary-menu',
)
);
?>
</header><!-- #masthead -->
Notes that matter:
container_aria_labelhas been available inwp_nav_menu()since WordPress 5.5 and outputsaria-labelon the<nav>wrapper. Use it instead of hand-rolling a nav element.- Do not write “Primary navigation” as the label. Screen readers already append the role, so the user hears “Primary navigation navigation”. Label it Primary.
- The skip link target must be the
mainelement and it needstabindex="-1"for reliable focus in Safari and older browsers.
index.php, single.php, page.php, archive.php: exactly one main
<?php get_header(); ?>
<main id="primary" class="site-main" tabindex="-1">
<?php if ( have_posts() ) : ?>
<?php while ( have_posts() ) : the_post(); ?>
<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
<header class="entry-header">
<?php the_title( '<h2 class="entry-title">', '</h2>' ); ?>
</header>
<div class="entry-content"><?php the_content(); ?></div>
</article>
<?php endwhile; ?>
<?php
the_posts_pagination(
array(
'aria_label' => __( 'Posts', 'ppp' ),
)
);
?>
<?php endif; ?>
</main>
<?php get_sidebar(); ?>
<?php get_footer(); ?>
The <header class="entry-header"> inside the article is intentionally not a banner landmark, because a header nested inside article loses its implicit role. That is correct behaviour, leave it alone.
the_posts_pagination() outputs a <nav class="navigation pagination">, so pass aria_label or you get a second unlabeled navigation landmark. Same for the_post_navigation().
sidebar.php: complementary
<?php if ( ! is_active_sidebar( 'sidebar-1' ) ) { return; } ?>
<aside id="secondary" class="widget-area" aria-label="<?php esc_attr_e( 'Blog sidebar', 'ppp' ); ?>">
<?php dynamic_sidebar( 'sidebar-1' ); ?>
</aside>
One sidebar needs no label. Two or more (main sidebar plus a shop filter sidebar, for example) must be distinguished. The subject deserves a deeper dive than we can give it here.
footer.php: contentinfo plus a labeled footer nav
<footer id="colophon" class="site-footer">
<?php
wp_nav_menu(
array(
'theme_location' => 'footer',
'container' => 'nav',
'container_class' => 'footer-navigation',
'container_aria_label' => __( 'Footer', 'ppp' ),
'depth' => 1,
)
);
?>
<div class="site-info">
<p>© <?php echo esc_html( date_i18n( 'Y' ) ); ?> <?php bloginfo( 'name' ); ?></p>
</div>
</footer>
<?php wp_footer(); ?>
</body>
</html>
Keep <footer> as a direct child of <body> or of a plain layout wrapper. If it ends up inside <main>, the contentinfo landmark disappears.
searchform.php: the search landmark
<form role="search" method="get" class="search-form" action="<?php echo esc_url( home_url( '/' ) ); ?>">
<label for="search-field-header" class="screen-reader-text">
<?php esc_html_e( 'Search this site', 'ppp' ); ?>
</label>
<input type="search" id="search-field-header" name="s" value="<?php echo get_search_query(); ?>" />
<button type="submit"><?php esc_html_e( 'Search', 'ppp' ); ?></button>
</form>
If the same form appears in the header and in the footer, give each one an aria-label (“Header” and “Footer”), and make sure the id values stay unique. It is the sort of thing a careful web development studio handles once and never revisits.
Turn on HTML5 markup support
Without this, core widgets and forms fall back to older markup that skips <nav> wrappers entirely:
add_action( 'after_setup_theme', function () {
add_theme_support( 'html5', array(
'search-form',
'navigation-widgets',
'comment-form',
'comment-list',
'gallery',
'caption',
'style',
'script',
) );
} );
Part 2: ARIA Landmarks in a Block Theme
Block themes build the same structure from HTML template files and block markup. The key is the tagName attribute on Group and Template Part blocks, and the ariaLabel attribute on the Navigation block.
parts/header.html
<!-- wp:group {"tagName":"header","className":"site-header","layout":{"type":"constrained"}} -->
<header class="wp-block-group site-header">
<!-- wp:site-logo /-->
<!-- wp:site-title /-->
<!-- wp:navigation {"ariaLabel":"Primary","layout":{"type":"flex"}} /-->
</header>
<!-- /wp:group -->
In the editor, the ariaLabel value is set in the Navigation block sidebar under Advanced > Label (older releases called it “Menu name”). Set it on every navigation you add.
templates/index.html
<!-- wp:template-part {"slug":"header","area":"header"} /-->
<!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} -->
<main class="wp-block-group">
<!-- wp:query {"queryId":1,"query":{"inherit":true}} -->
<div class="wp-block-query">
<!-- wp:post-template -->
<!-- wp:post-title {"isLink":true,"level":2} /-->
<!-- wp:post-excerpt /-->
<!-- /wp:post-template -->
<!-- wp:query-pagination {"layout":{"type":"flex"}} -->
<!-- wp:query-pagination-previous /-->
<!-- wp:query-pagination-numbers /-->
<!-- wp:query-pagination-next /-->
<!-- /wp:query-pagination -->
</div>
<!-- /wp:query -->
</main>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer","area":"footer"} /-->
The block theme duplicate-landmark trap
This is the single most common block theme error, and it produces exactly the “duplicate banner” and “nested contentinfo” warnings that axe and WAVE report:
- You set
{"tagName":"header"}on the Group inside parts/header.html, and{"tagName":"header"}on the Template Part block in the template. Result:<header><header>...</header></header>. - Pick one place. Either the template part wrapper carries the tag, or the group inside does. Using
"area":"header"on the template part without atagNamerenders a plain<div>wrapper, which is safe when the inner group is already a<header>. - The same applies to footers: never let a Query Loop or a Post Content block sit inside a group tagged
mainwhile a page template also outputsmain.
parts/footer.html
<!-- wp:group {"tagName":"footer","className":"site-footer","layout":{"type":"constrained"}} -->
<footer class="wp-block-group site-footer">
<!-- wp:navigation {"ariaLabel":"Footer","overlayMenu":"never"} /-->
<!-- wp:paragraph -->
<p>© 2026 Pixel Perfect Portfolios</p>
<!-- /wp:paragraph -->
</footer>
<!-- /wp:group -->
Naming a region in a block theme
If you genuinely need a named region (a pricing section users should be able to jump to), use a Group with tagName set to section and add an aria-label through Advanced > Additional attributes in a custom block, or wrap it with a block variation. A <section> without an accessible name is not a landmark at all, so do not count on it.

Part 3: Labeling Multiple Navigation Menus Correctly
A typical WordPress page ends up with four or five navigation landmarks. Here is a labeling scheme that works:
| Menu | Recommended label | How to set it |
|---|---|---|
| Main menu | Primary | container_aria_label / ariaLabel |
| Utility or top bar | Utility | container_aria_label |
| Breadcrumbs | Breadcrumb | Hand-coded <nav aria-label="Breadcrumb"> |
| Post pagination | Posts | the_posts_pagination( array( 'aria_label' => ... ) ) |
| Next / previous post | Post | the_post_navigation( array( 'aria_label' => ... ) ) |
| Footer links | Footer | container_aria_label / ariaLabel |
If a visible heading already names the region, reference it instead of duplicating text:
<nav aria-labelledby="footer-nav-heading">
<h2 id="footer-nav-heading" class="screen-reader-text">Footer</h2>
<!-- menu markup -->
</nav>
Part 4: Hunting Down a Duplicate main Landmark
When an audit reports two main regions, work through this list in order:
- View source and search for
<main. Count the matches. - Check child theme templates that were copied before you refactored the parent, especially
page.php,single.phpandwoocommerce.php. - Check plugin template overrides in
/wp-content/themes/your-theme/woocommerce/or/templates/. - Check page builder settings. Several builders wrap their canvas in a
<main>element by default and offer a “content wrapper tag” option you can set back todiv. - In block themes, check whether both the Template Part and an inner Group carry
tagName. - Check header or footer scripts pasted into the Customizer or a code snippets plugin.
If two main areas genuinely exist in your layout, keep one <main> and turn the second into a named <section aria-label="..."> or <aside>.

Part 5: How to Test Your Landmarks
Keyboard and screen reader checks
- NVDA + Firefox (Windows): press
Dto cycle landmarks, orInsert + F7for the elements list. - VoiceOver + Safari (macOS):
VO + Uopens the rotor, then arrow to the Landmarks list. - TalkBack (Android): set the reading control to Landmarks and swipe.
Automated and visual tools
- axe DevTools browser extension: catches duplicate banner, duplicate contentinfo, and content outside landmarks.
- WAVE by WebAIM: the Structure tab draws landmark boundaries directly on the page.
- Landmarks extension for Firefox and Chrome: lists every landmark and highlights it, ideal for spotting the nesting bugs.
- Lighthouse in Chrome DevTools: quick pass/fail signal, not exhaustive.
- Accessibility Checker or similar WordPress plugins: useful for scanning every post at scale, but plugins cannot restructure your template files. Fix the theme, then scan.
The one-minute manual test
- Open any page and run
document.querySelectorAll('main').lengthin the console. It must return 1. - Run
document.querySelectorAll('nav').length, then check that every one has anaria-labeloraria-labelledby. - Confirm
headerandfooterare not nested insidemain,articleorsection. - Tab once from the address bar. The skip link should appear and point at your
main.
Copy-Ready Landmark Checklist
- Exactly one
<main>per page, withtabindex="-1"and a matching skip link - One top-level
<header>and one top-level<footer>, both outside<main> - Every
<nav>labeled, with no word “navigation” inside the label - Site search wrapped in
role="search"with a labeled input - Sidebars in
<aside>, labeled when there is more than one - No redundant roles on native elements
- All visible content sits inside some landmark
add_theme_support( 'html5', ... )declared in classic themes- Verified with one screen reader and one automated tool before release

Why This Matters More in 2026
The European Accessibility Act has applied to new consumer-facing digital products since June 2025, WCAG 2.2 AA is now the reference level in most public procurement rules, and landmark structure is one of the first things any manual auditor checks because it takes thirty seconds to evaluate. Getting ARIA landmarks right in your WordPress theme is one of the cheapest accessibility wins available: a handful of tags, no design change, no performance cost.
FAQ
Do I still need role=”banner” if I use the header element?
No. A <header> that is not nested inside article, aside, main, nav or section already exposes the banner role. Adding role="banner" is harmless but redundant, and it can mask a nesting problem you should fix instead.
Can a page have two main landmarks?
No. The HTML specification allows only one main element to be visible and exposed per document. Additional ones are reported as errors by axe, WAVE and screen readers, and they break landmark navigation because users cannot tell which region holds the real content.
Does the WordPress Navigation block add an aria-label automatically?
It renders a <nav> element, but the label is empty unless you set it. Open the block, go to the Advanced panel, and fill in the Label field. Do this for every navigation instance in your header, footer and any template part.
Can a plugin add ARIA landmarks without editing my theme?
Plugins can inject attributes and flag issues, and that is genuinely useful for auditing. They cannot reliably move a <footer> out of <main> or delete a duplicate <main> generated by a page builder. Treat plugins as a detection layer and fix the structure in the theme templates.
Should I use section with role=”region” for homepage blocks?
Only for a small number of genuinely important areas, and always with an accessible name. Too many regions turn the landmark list into noise, which defeats the purpose. Three to seven landmarks per page is a healthy range.
Do ARIA landmarks help SEO?
Not directly as a ranking factor, but clean semantic HTML helps crawlers identify primary content versus boilerplate, improves the odds of useful featured snippets, and reduces the legal and reputational risk of an inaccessible site. The real return is usability.
How do I fix landmarks in a theme I bought and cannot edit safely?
Create a child theme, copy only the template files you need to change (usually header.php, footer.php, sidebar.php and one or two content templates), and apply the markup above. For block themes, edit the templates and template parts in the Site Editor, which stores your changes in the database and leaves the parent theme files untouched.
Need a second pair of eyes on your theme? Our team at Pixel Perfect Portfolios audits WordPress themes for landmark structure, keyboard operability and WCAG 2.2 AA conformance, then ships the corrected templates back to you ready to deploy.
