Fix

Landmark structure problems

Landmarks are the signposts of a page — banner, navigation, main, contentinfo. A shop with none, or with three navigations all announced the same way, forces a screen-reader customer to read everything to find anything.

Review queue WCAG 1.3.1

Who this locks out

Missing or duplicated landmarks remove the shortcuts screen-reader users navigate by.

  • Screen-reader customers who navigate by landmark
  • Shoppers using the VoiceOver rotor on iPhone
  • Keyboard users skimming past a long header
  • Anyone reaching checkout through a screen reader

The transform

Before

<nav class="menu">…</nav><nav class="menu">…</nav>

After

<nav class="menu" aria-label="Main menu">…</nav><nav class="menu" aria-label="Shop by category">…</nav>

Detection

How this is tested here

Every scan runs three independent engines and nine custom probes against your rendered pages, and records which engine flagged what. These are the real rules behind this fix family.

WCAG success criteria this family covers, and the engine rules that detect them. Generated from the engine's own tables — never edited by hand.
Criterion Level Detected by What we do
1.3.1 Info and Relationships A axe-core (aria-hidden-body, aria-required-children, aria-required-parent, definition-list) · IBM Equal Access (aria_banner_label_unique, aria_banner_single, aria_child_valid) auto

What Klarvo Access does

Klarvo detects the landmark problem, generates the attribute-level change — a unique, distinguishing label on each repeated navigation — and queues it for a human to approve. A missing main wrapper is a structural theme change, so it is reported with the exact template edit it needs rather than patched from outside.

Review queue

Generated with a proposed value, then queued for you to approve, edit or reject. It is never applied silently, because this one is a judgement call.

An automated finding is evidence, not a certificate. Conformance is certified only by the professional audit — how that works.

Do it yourself

Fixing this by hand

What goes wrong in a shop with no landmarks

Every page of your store is built from the same few areas: the strip at the top with logo, search and basket count; the menus; the block in the middle that changes page to page; the small print at the bottom. A sighted shopper separates those in a glance. A screen reader has no glance to work with — only the markup. If those areas are generic div elements they are not areas at all, and the page becomes one long ribbon of text, links and prices with nothing to navigate by.

A regular customer arrives on your “Kitchenware” category page using NVDA on Windows. With landmarks, one keypress takes her past the header into the product grid. Without them, every page load starts the same way: logo, account, wishlist, basket, then forty items of mega menu before the first product is announced. Multiply that by six categories and the shop becomes exhausting rather than impossible — which is how most customers are lost, quietly, without complaining.

Repetition does its own damage. A typical Woo theme renders four nav elements per page: primary menu, category menu, breadcrumb, footer links. If none is labelled, the rotor on an iPhone lists “navigation, navigation, navigation” and the shopper must open each one to find the size filters.

The worst case is checkout. When the payment fields sit outside every landmark — common when a page builder or checkout plugin renders its own wrapper — someone moving landmark to landmark goes straight from header to footer. The order form is on screen; to them the page looks empty. That is a basket abandoned for a reason analytics will never show you.

How this is detected here

Klarvo runs three independent rule engines over each page’s rendered HTML: axe 4.12.0, HTML_CodeSniffer, and the vendored IBM Equal Access engine (ace). Landmark findings map to WCAG 2.1 Success Criterion 1.3.1 Info and Relationships (Level A).

It matters which engine says what here, because this family differs from the others. The rules that produce landmark findings come from the ace ruleset: aria_content_in_landmark for content sitting outside every landmark; aria_banner_single and aria_contentinfo_single where a page declares more than one of each; aria_contentinfo_misuse where the role sits somewhere it does not belong; aria_landmark_name_unique, aria_main_label_unique, aria_navigation_label_unique, aria_complementary_label_unique, aria_region_label_unique, aria_banner_label_unique and aria_form_label_unique for repeated landmarks that cannot be told apart; aria_complementary_labelled and aria_region_labelled where a region has no accessible name at all; and aria_main_label_visible and aria_complementary_label_visible where the name heard does not match the heading on screen.

axe tests 1.3.1 too, but through different rules — list, listitem, aria-required-parent, aria-required-children, p-as-heading, td-has-header, th-has-data-cells — which cover lists, tables and required ARIA relationships rather than page regions. HTML_CodeSniffer references the same criterion again. So on this family one engine usually names the defect rather than two agreeing, and we say so rather than dressing that up as consensus. No bespoke Klarvo probe covers landmarks: the evidence is the engine result, the failing selector and the markup we captured.

What Klarvo does about it

Landmarks are a review-tier family, detected and remediated deterministically — no model guesses at your page structure. Klarvo works out the change from the DOM it rendered: which repeated landmark needs a name, with a distinct suggested label for each — derived from its position on the page — for you to reword in review. The proposed markup then goes to your review queue, before and after side by side. Where the page has no main at all, Klarvo does not manufacture one: wrapping your content is a structural theme change, so it is reported with the exact template edit it needs — the section below — rather than patched from outside.

A human approves it. That is the point of the tier, not a limitation: page structure is a fix where a confident wrong answer is worse than the defect. Nothing in this family is applied to your store silently. Once you approve, the change is made server-side in the HTML your store serves — never a client-side overlay — and the page is scanned again so you can watch the rule stop firing. An automated finding is evidence, not a certificate.

How to fix it yourself

You do not need us for this: landmarks are the cheapest structural fix in accessibility, and most of the work is deleting div elements.

The map

HTML gives you landmarks for free, provided the right element is in the right place:

ElementLandmark
<header> at page levelbanner
<nav>navigation
<main>main
<aside>complementary
<footer> at page levelcontentinfo
<section> with an accessible nameregion
<form role="search">search

Four rules govern them. One main per page. header and footer only count at page level — inside article or section they are ordinary elements. All visible content belongs inside some landmark. Any landmark type used twice needs a unique label.

Classic themes

In header.php, make the top strip a real banner and name the menu:

<header class="site-header">
    <?php get_template_part( 'template-parts/branding' ); ?>

    <nav class="primary-menu" aria-label="Main menu">
        <?php wp_nav_menu( array( 'theme_location' => 'primary', 'container' => false ) ); ?>
    </nav>
</header>

Keep “navigation” out of the label — the role is announced already, so “Main menu navigation” is heard twice. Same in footer.php: <footer class="site-footer"> with <nav aria-label="Customer service"> around the link columns.

Woo templates are wrapped by two hooks the theme fills. If yours emits a div, replace it in functions.php:

remove_action( 'woocommerce_before_main_content', 'woocommerce_output_content_wrapper', 10 );
remove_action( 'woocommerce_after_main_content', 'woocommerce_output_content_wrapper_end', 10 );

add_action( 'woocommerce_before_main_content', static function () {
    echo '<main id="primary" class="site-main">';
}, 10 );

add_action( 'woocommerce_after_main_content', static function () {
    echo '</main>';
}, 10 );

The product search form needs the search role:

<form role="search" method="get" class="woocommerce-product-search"
      action="<?php echo esc_url( home_url( '/' ) ); ?>">
    <label class="screen-reader-text" for="product-search">Search products</label>
    <input type="search" id="product-search" name="s" />
    <input type="hidden" name="post_type" value="product" />
    <button type="submit">Search</button>
</form>

Block themes

Block themes hold the same structure in template HTML. Template parts and Group blocks take a tagName, exposed in the editor under Advanced as “HTML Element”:

<!-- wp:template-part {"slug":"header","tagName":"header"} /-->

<!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} -->
<main class="wp-block-group"><!-- wp:post-content /--></main>
<!-- /wp:group -->

<!-- wp:template-part {"slug":"footer","tagName":"footer"} /-->

Check every template that renders differently — index, archive-product, single-product, page, search, 404 — since a main in one is not in the rest. If a navigation block gives you no label field, add one:

add_filter( 'render_block_core/navigation', static function ( $html ) {
    if ( str_contains( $html, 'aria-label' ) ) {
        return $html;
    }
    return preg_replace( '/<nav /', '<nav aria-label="Shop by category" ', $html, 1 );
} );

Cart and checkout

The Woo Cart and Checkout blocks render inside the page content, so the landmark must come from the page template — never add a second main around the checkout block. If an off-canvas basket slides over the page, leave landmark roles off it: a drawer is a dialog, not a region. Two own-goals to undo: role="main" left on a wrapper that now holds a real main, and role="banner" inside an article, where it means nothing.

Resist labelling everything. Fifteen labelled section elements are as hard to navigate as none; the rule of thumb is a handful of landmarks per page, not one per component.

How you know it worked

Open a category page in Chrome and read the Accessibility pane in DevTools: the landmark tree should read like a short table of contents, no repeated names, nothing important outside it. Then use a screen reader, the real test — in NVDA press D to move landmark to landmark, or on macOS open the VoiceOver rotor with VO-U and step to the landmarks list. You should reach the product grid in one keypress and tell the menus apart by name.

Then re-scan the pages that matter: home, a category archive, a product, the basket and checkout. Check checkout twice, as a guest and signed in with items in the basket: the markup differs, and the guest path is where the money is. The rules named above should stop appearing against those URLs.

Finally, re-check after theme and plugin updates. Template files get replaced and page builders regenerate their wrappers; landmarks are the first thing to vanish when they do, which is why we keep scanning rather than treating one clean result as permanent.

Sources: W3C WAI — Understanding SC 1.3.1: Info and Relationships · W3C WAI — ARIA11: Using ARIA landmarks to identify regions of a page · W3C WAI Tutorials — Page Regions · W3C — Accessible Rich Internet Applications (WAI-ARIA) 1.2 · WordPress Documentation — Template Part block

FAQ

Questions this raises

My theme already has a skip link. Do I still need landmarks?

Yes. A skip link is one shortcut to one place, usually the top of the content. Landmarks are the map: they let someone jump to the search, to the category filters, to the small print, and back again, on any page. The two solve different halves of the same problem, which is why our skip-link and landmark findings are separate.

Will wrapping my content in a main element change how the shop looks?

Not on its own — main, header, footer and nav carry no styling of their own. The one thing to check is your stylesheet: if a rule is written as div#content or div.site-header, it stops matching once the element changes. Keep the same id and class values, then search your CSS and JavaScript for selectors that name the old tag.

Why does Klarvo ask me to approve this rather than just applying it?

Because what each menu should be called is a judgement about your shop that only you can make, every label waits for your approval. Deciding which wrapper is your main content is beyond even that: a main element placed around the wrong wrapper can hide the product grid from the very people it was meant to help — which is why a missing main is reported with the template edit it needs rather than applied at all.

Find out whether your store has this one.

The free scan checks your real pages — home, product, populated basket, hydrated checkout — with all three engines and all nine probes, and tells you exactly what it found and where.