HTML Landmarks in Practice: One Structure for Screen Readers, Search and AI
Landmarks from the ground up: implicit roles, the nesting rules, the role attribute, labelling, copy-ready patterns, a checklist — plus what extractors read from them.
by Jean Pierre Kolb ·
The first thing I did when writing this article was audit my own site — the one you are reading right now. The most interesting finding: it had no contentinfo landmark, even though there was very obviously a footer at the bottom. The footer was there, it was marked up as <footer>, it looked like a footer — but to a screen reader it was not one.
That was not sloppiness. It was the consequence of a rule that lives in a specification nobody reads and that no validator flags: a <footer> only becomes a contentinfo landmark if it is not a descendant of <article>, <aside>, <main>, <nav> or <section>. In my layout it sat inside <main>, purely for layout reasons, so that flexbox pushed it to the bottom. One character of HTML nesting decides whether a page region shows up in a screen reader's landmark list or not.
It did not stop at that one find. In the end there were three findings in five minutes, plus a dozen unnamed landmarks — all fixed before this article went live. The findings appear at the relevant points in the text, because they explain how these mistakes come about better than any invented example could: not through carelessness, but through layout decisions that are perfectly reasonable in themselves.
That is what this article is about. Landmarks are one of the few topics where accessibility, usability, classic SEO and the new question of how AI systems extract your content all point to the same answer — and at the same time one where almost every guide on the web omits half the rules. This is the full tour: which landmarks exist, under which conditions they come into being, when the role attribute is necessary and when it does damage, how to label them, what screen reader users actually do with them (the numbers are more sobering than you would expect), what Google says — and a measurement from the extraction path of AI systems that directly contradicts the standard advice.
A note on method: the rules in this article come from the primary sources (WHATWG HTML, ARIA in HTML, HTML-AAM, the ARIA APG), not from secondary literature; where two W3C documents disagree, I say so explicitly. The chapter on AI extraction rests on a measurement of my own that you can reproduce below, and on reading the source code of the two most widely used extractors.
What a landmark is — and what it is not
A landmark is a named major region of a page that assistive technology can jump to directly. No more, no less. From your DOM the browser builds a second tree, the accessibility tree — a reduced version of the page in which every element has a role, a name and a state. Screen readers read that tree, not your HTML. Landmarks are the elements in that tree whose role belongs to the "landmark" category.
The practical effect: a screen reader can list every landmark on the page — "banner, navigation Main menu, search, main, complementary Related articles, contentinfo" — and the user jumps to one with a single keystroke. Without landmarks all that is left is working through the page linearly or navigating by headings.
Three distinctions that are routinely muddled:
Landmarks are not headings. The heading hierarchy (<h1>–<h6>) describes the editorial outline, landmarks describe the functional split of the page. Both exist in parallel and both get used — headings considerably more often, more on that below. A <section> without a heading is just as legitimate as an <h2> outside any landmark; the two systems do not have to line up.
Landmarks are not sectioning content. With <article>, <aside>, <nav> and <section>, HTML has its own "sectioning content" category that feeds the outline algorithm. The overlap with landmarks is large but not complete: <article> is sectioning content and not a landmark (its role is article, from the "document structure" category), while <main> is a landmark and not sectioning content.
Landmarks are not ARIA magic. As a rule you need no role attribute at all to get them. The right HTML elements bring their role with them — that is the normal case and the better route.
The W3C authoring guide (ARIA Authoring Practices Guide) states the goal so concisely that I will quote it: including all perceivable content on a page in one of its landmark regions is "one of the most effective ways of ensuring assistive technology users will not overlook information". That is the actual guiding rule: no orphaned content between the landmarks.
The eight landmark roles and their HTML equivalents
ARIA 1.2 defines exactly eight landmark roles. Not seven, not twelve — eight. This table is the core of the article; everything else is conditions and edge cases.
| Role | HTML element | What for | Per page |
|---|---|---|---|
banner | <header> (at body level) | Page header: logo, site title, global tools | one |
navigation | <nav> | Blocks of navigation links | any number, named |
search | <search> | Search and filter functionality | usually one |
main | <main> | The page's primary content | exactly one |
complementary | <aside> | Supporting content that stands on its own | several possible, named |
contentinfo | <footer> (at body level) | Footer: imprint, copyright, legal | one |
form | <form> with a name | A self-contained form | several possible |
region | <section> with a name | An important area with no better-fitting role | several possible |
Three things stand out in that table, and all three matter more than they look.
First, two roles — banner and contentinfo — carry a condition in the element column. Second, two more — form and region — carry a condition that demands a name. Third, region is explicitly described as a fallback: it is the landmark for areas that have no better-fitting role. If you find yourself writing role="region", it is worth asking first whether you mean complementary, navigation or search.
What is not a landmark, though it is often taken for one: <article> (role article), <address> (role group), <hgroup> (role group), <figure>, <details> and every <div> without a role (role generic). A <section> without a name is not one either — more on that in a moment.
The conditions almost nobody knows
This is where "I use semantic HTML" and "my page actually has the landmarks I think it has" part company. The authoritative source is the W3C specification ARIA in HTML, which defines the implicit ARIA semantics for every HTML element.
<header> and <footer>: nesting decides
Verbatim from ARIA in HTML, for <header>:
If not a descendant of an
article,aside,main,navorsectionelement, or an element withrole=article,complementary,main,navigationorregionthenrole=banner. Otherwise,role=generic.
For <footer> the same sentence applies with contentinfo instead of banner. In practice:
<body>
<header>…</header> <!-- banner -->
<main>
<article>
<header>…</header> <!-- NOT a landmark -->
<footer>…</footer> <!-- NOT a landmark -->
</article>
<footer>…</footer> <!-- NOT a landmark — descendant of main -->
</main>
<footer>…</footer> <!-- contentinfo -->
</body>This is sensible, by the way: the footer of an individual article (author, date, tags) is not the footer of the website. The rule stops you from producing a separate contentinfo landmark for every article in a listing.
It bites, however, exactly when you do what I did: use <main> as a flex container and put the site footer inside it so that it sinks to the bottom. The layout is right, the landmark is gone — and no tool reports it as an error, because it is not one. It is a correctly applied rule with a result you did not want.
The way out is one line: the column is made by a wrapper rather than by <main>, which turns <main> and <footer> into siblings. That is exactly what this layout has looked like since the research for this article:
<div class="content-column"> <!-- makes the column: min-height, flex -->
<main class="flex-1">…</main>
<footer>…</footer> <!-- sibling → contentinfo -->
</div>One detail currently in motion: the second authoritative specification, HTML-AAM, no longer maps a nested <header>/<footer> to generic but to the new sectionheader and sectionfooter roles from the ARIA 1.3 working draft (4 June 2026). For practice this changes nothing — both versions agree that it is not a landmark. It only changes the name of what sits in the accessibility tree instead.
<section>: no name, no region
Again verbatim from ARIA in HTML:
role=regionif thesectionelement has an accessible name. Otherwise,role=generic.
This is the single most overlooked rule, because <section> is the element developers reach for out of habit whenever they mean "some section". A bare <section> is worth exactly as much to assistive technology as a <div>: nothing.
<!-- generic — no landmark, no benefit -->
<section>
<h2>Current projects</h2>
…
</section>
<!-- region — a landmark named "Current projects" -->
<section aria-labelledby="projects-title">
<h2 id="projects-title">Current projects</h2>
…
</section>Before you retrofit that everywhere: the APG explicitly recommends restraint. If every section is a region, the landmark list is as long as the table of contents and helps nobody. Use region for the two or three areas that genuinely should be jump targets — the rest may stay generic and will be found via its heading.
<form>: no name, no landmark
Same mechanism: HTML-AAM phrases it as an instruction to browsers — if a form has no accessible name, do not expose it as a landmark. Which is a good thing, otherwise every search box, every newsletter widget and every log-out-button-in-a-form would be a landmark of its own.
<form aria-label="Newsletter signup">…</form>The APG adds: a form whose purpose is searching belongs in a search landmark, not a form one.
<aside>: the rule the specifications disagree on
This gets messy, and I would rather say so than manufacture false certainty. ARIA in HTML simply lists role=complementary for <aside>, unconditionally. HTML-AAM distinguishes: at body or <main> level, complementary; inside sectioning content (<article>, <aside>, <nav>, <section>) only complementary "if the aside element has an accessible name. Otherwise, generic role".
The practical consequence is unambiguous regardless of which version your browser follows: always give a nested <aside> a name. Then you get the same result under either reading, and you incidentally satisfy the APG rule that landmarks occurring more than once must be distinguishably named.
<main>: exactly one, visible
The HTML Standard is unusually blunt here:
A document must not have more than one
mainelement that does not have thehiddenattribute specified.
Multiple <main> elements in the DOM are therefore allowed — as long as at most one is visible. That is the escape hatch for single-page applications holding several views. On top of that comes a nesting rule: <main> may only be a descendant of <html>, <body>, <div>, <form> (without an accessible name) and autonomous custom elements. A <main> inside a <section> is invalid HTML — and that one the validator does report.
Empty landmarks: the layout spacer
The second find in my own template. Two of my layouts contained an empty element whose sole purpose was to keep the columns symmetrical:
<aside class="hidden xl:block xl:w-56 xl:shrink-0"></aside>On the home page and the standalone pages that produced a complementary landmark with no content whatsoever. A screen reader user jumps to it and lands in nothing. Layout scaffolding belongs in a <div> — or, if the element only holds space anyway, it should go entirely and the gap should move into the CSS grid definition. In my case it became a <div>, as did the four other <aside> wrappers that merely enclosed the sidebar and the table of contents: in those cases the landmark is the <nav> inside, and the wrapper contributed nothing but another unnamed entry in the landmark list.
The underlying principle: an element is chosen by meaning, not by appearance. <aside> means "this is supporting content", not "this is on the right".
The role attribute: the first rule of ARIA — and its exceptions
The best known rule of ARIA authoring practice says, in essence: if a native HTML element already provides the semantics and behaviour you want, use it instead of repurposing a different element. For landmarks that means:
<!-- No: redundant -->
<nav role="navigation">…</nav>
<main role="main">…</main>
<aside role="complementary">…</aside>
<!-- Yes -->
<nav>…</nav>
<main>…</main>
<aside>…</aside>The redundant role does not directly harm accessibility — it just says the same thing twice. It harms maintainability: whoever writes <nav role="navigation"> has not grasped the mechanism, and the next element will be <div role="navigation"> with no keyboard support. The W3C WAI guidance is clear: HTML elements first, ARIA roles as a fallback for cases where HTML cannot be used.
When role genuinely is needed
Four situations in which I do set the attribute:
1. There is no fitting HTML element. For role="search" there was none for a long time; today there is <search> (Baseline since October 2023, widely available). If you have to serve older browsers, this is the one halfway decent reason for a "redundant" role — more on that below.
2. Legacy code you cannot restructure. A template system that emits <div class="sidebar"> and gives you no access to the element at least becomes a landmark with role="complementary". That is the fallback ARIA was designed for.
3. You deliberately want to override the implicit role. Rare, but it happens: a <section> that really is a navigation block, or a <form> that is a search:
<form role="search" action="/search">…</form>That was the canonical pattern for years — today you use <search> instead.
4. Specialised vocabularies. DPUB-ARIA defines additional roles for digital publications such as doc-toc, doc-bibliography, doc-glossary or doc-index — the first two examples derive from navigation, while doc-bibliography and doc-glossary derive directly from landmark. Relevant for e-books and scholarly publishing, not for the average website.
When role does damage
A wrong role overrides native semantics completely — including when that leaves the element unusable:
<!-- Broken: assistive technology sees a link, but the element
still responds to Space rather than Enter -->
<button role="link">Continue</button>
<!-- Broken: the list loses its semantics — and the <li>
elements inherit the presentation role along with it -->
<ul role="presentation">
<li>…</li>
</ul>The second one is the more common trap: role="presentation" (synonym role="none") strips an element's semantics. On a layout <table> that is right. On a navigation list it costs you the announcement "list with 7 items", which is exactly what orientation depends on.
And the classic I see in nearly every audit:
<!-- No: role on the wrong element -->
<div role="main">
<main>…</main>
</div>Two nested main landmarks. Screen readers either flag that as an error or ignore one of them — neither is what you wanted.
Labelling: aria-label, aria-labelledby and the "navigation navigation" trap
As soon as a landmark role appears more than once on a page, every instance needs its own name. The APG puts it directly:
If a specific landmark role is used more than once on a page, provide each instance of that landmark with a unique label.
Two routes get you there, and they are not equivalent.
aria-labelledby — when a visible heading exists. The name is pulled from the referenced element. Advantage: it is visible, it travels automatically through translation, and it cannot drift apart from the content.
<nav aria-labelledby="mainmenu-title">
<h2 id="mainmenu-title">Main menu</h2>
…
</nav>aria-label — when no visible heading exists. The name lives only in the attribute. It has to be translated like any other UI string — in a bilingual project that means: into the language file, not hard-coded into the template.
<nav aria-label="Breadcrumb">…</nav>And now the rule that practically every page I look at breaks. Verbatim from the APG again:
Do not use the landmark role as part of the label. For example, a navigation landmark with a label "Site Navigation" will be announced by a screen reader as "Site Navigation Navigation".
The screen reader announces the role every time. Your label is the distinguishing part, not the description.
<!-- No -->
<nav aria-label="Main navigation"> <!-- "Main navigation navigation" -->
<aside aria-label="Sidebar"> <!-- reads as a position, not a purpose -->
<search aria-label="Search area"> <!-- "Search area search" -->
<!-- Yes -->
<nav aria-label="Main"> <!-- "Main navigation" -->
<nav aria-label="Legal"> <!-- "Legal navigation" -->
<aside aria-label="Related articles"> <!-- "Related articles complementary" -->If a bare aria-label="Main" feels too thin, there are two workable ways out: a descriptive label instead of a functional one (aria-label="Knowledge base areas") or a visible, visually hidden heading plus aria-labelledby. The second is the better route, because it also gives the landmark menu of browser extensions something meaningful to display.
One special case from the APG that saves time: navigation landmarks with an identical set of links — a main menu duplicated at top and bottom, say — should carry the same name. That way users recognise that there is nothing new to find.
To close the labelling section, a rule from my own project that I want to record here because it causes damage elsewhere: visible labels on UI chrome are not headings. When I changed the footer column titles and the table-of-contents titles on this site from <h5> to <p> in 2026, the trigger was an H2 → H5 jump in the document outline. The fix was exactly the pattern above: <p id="…"> as the visible label, aria-labelledby on the surrounding landmark. The outline stayed clean, the grouping stayed intact for assistive technology. A label is not automatically a heading.
The base skeleton, ready to copy
This is the skeleton I start from. It contains every landmark exactly once, and every line has a reason.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Page title — Website</title>
</head>
<body>
<!-- First focusable element on the page -->
<a class="skip-link" href="#content">Skip to content</a>
<header> <!-- banner -->
<a href="/" aria-label="Home">…logo…</a>
<nav aria-label="Main"> <!-- navigation -->
<ul>
<li><a href="/services/">Services</a></li>
<li><a href="/work/">Work</a></li>
</ul>
</nav>
<search> <!-- search -->
<form action="/search/" role="search">
<label for="q">Search this site</label>
<input type="search" id="q" name="q">
<button type="submit">Search</button>
</form>
</search>
</header>
<main id="content" tabindex="-1"> <!-- main -->
<h1>Page title</h1>
<nav aria-label="Breadcrumb"> <!-- navigation -->
<ol>
<li><a href="/">Home</a></li>
<li><a href="/blog/">Blog</a></li>
<li aria-current="page">This post</li>
</ol>
</nav>
<article>
<header> <!-- no landmark: inside <article> -->
<p>Published on <time datetime="2026-08-13">13 August 2026</time></p>
</header>
<p>…content…</p>
<footer> <!-- no landmark: inside <article> -->
<p>Author: …</p>
</footer>
</article>
</main>
<aside aria-labelledby="related-title"> <!-- complementary -->
<h2 id="related-title">Related posts</h2>
…
</aside>
<footer> <!-- contentinfo -->
<nav aria-label="Legal"> <!-- navigation -->
<ul>
<li><a href="/imprint/">Imprint</a></li>
<li><a href="/privacy/">Privacy</a></li>
</ul>
</nav>
<p>© 2026 …</p>
</footer>
</body>
</html>The four places that matter:
<footer>sits outside<main>. Precisely the spot from my opening. If you want to push it down with flexbox, make<body>or a column wrapper the flex container (min-height: 100dvh,main { flex: 1 }) — then you do not need the nesting at all.<aside>sits outside<main>. Supporting content that stands beside the main content belongs at the same level. An<aside>inside<main>is stillcomplementary(<main>appears in the exclusion list forheader/footer, but that is a different rule) — the separation is simply cleaner.tabindex="-1"on the jump target. Without it, some browsers move the scroll position on a skip link but not the keyboard focus. The user ends up looking at the content and then tabs back through the navigation.tabindex="-1"makes the element programmatically focusable without putting it in the tab order.- The skip link is the first focusable element. It may be visually hidden, but it must become visible on focus —
position: absolute; left: -9999pxwith no focus styling is a broken skip link, and according to WebAIM one in ten skip links on the web is exactly that.
The minimal CSS that goes with it:
.skip-link {
position: absolute;
left: -100vw;
top: 0;
z-index: 100;
padding: 0.75rem 1rem;
background: Canvas;
color: CanvasText;
border: 2px solid CurrentColor;
}
.skip-link:focus-visible {
left: 0;
}Scaling up: search, multiple navigations, dashboards, forms
The base skeleton carries a content page. Four common extensions.
Search
<search> has been widely available since October 2023 and replaces the old <form role="search">. Two editorial rules from the MDN reference matter: the element wraps the search functionality, not the search results. Suggestions and quick links that are part of the search feature may live inside it.
<search>
<form action="/search/">
<label for="searchfield">What are you looking for?</label>
<input type="search" id="searchfield" name="q">
<button type="submit">Search</button>
</form>
</search>
<!-- Results: outside, in the main region -->
<main>
<h1>7 results for "landmarks"</h1>
…
</main>Two searches on one page — global and section-scoped — need names like every duplicated landmark:
<search aria-label="Whole site">…</search>
<search aria-label="Within this documentation">…</search>Multiple navigations
The normal case on any larger site, and the most common source of unnamed landmarks. My rule of thumb: every <nav> gets a name as it is written — even when there is currently only one. The second one is coming anyway.
<nav aria-label="Main">…</nav>
<nav aria-label="Breadcrumb">…</nav>
<nav aria-label="On this page">…</nav> <!-- table of contents -->
<nav aria-label="Pagination">…</nav> <!-- previous/next -->
<nav aria-label="Legal">…</nav> <!-- in the footer -->Not every collection of links is a <nav>. The HTML Standard means "major blocks of navigation links". Three links in a paragraph are not navigation; a list of twelve categories is.
One detail that only shows up in production: if your mobile navigation is a second, separately rendered menu — a desktop sidebar plus a mobile drawer, as in my layout — then both are in the DOM, even when one is hidden by CSS. display: none does remove an element from the accessibility tree, but visibility tricks and off-canvas positioning do not. Check the accessibility tree for how many navigation landmarks actually arrive, not the source.
Applications and dashboards
For interfaces with no classic "article", region is the role of choice — used sparingly, for the areas users genuinely jump between:
<main>
<h1>Overview</h1>
<section aria-labelledby="metrics-title">
<h2 id="metrics-title">Metrics</h2>
…
</section>
<section aria-labelledby="activity-title">
<h2 id="activity-title">Recent activity</h2>
…
</section>
</main>What does not belong here: landmarks around modal dialogs. The APG makes clear that modal dialogs should not wrap their content in landmarks — the modality itself already provides the containment.
Form pages
A form only becomes a landmark once it has a name, and a landmark is only worthwhile for larger forms anyway. For internal structure you use <fieldset> with <legend> — not a landmark mechanism, but the older and, in this case, the more fitting one:
<form aria-labelledby="checkout-title">
<h2 id="checkout-title">Complete your order</h2>
<fieldset>
<legend>Delivery address</legend>
…
</fieldset>
<fieldset>
<legend>Payment method</legend>
…
</fieldset>
</form>What screen reader users actually do
This is where it gets uncomfortable, and I consider it the most important section of the article — because most landmark guides make a claim here that the data does not support.
The WebAIM Screen Reader User Survey #10 (fielded December 2023/January 2024, 1,539 valid responses) asked how users find information on a lengthy page:
| Method | Share |
|---|---|
| Navigate through headings | 71.6% |
| Use the screen reader's Find feature | 13.6% |
| Read through the page | 6.4% |
| Navigate through links | 4.8% |
| Navigate through landmarks/regions | 3.7% |
3.7%. Landmarks are the least frequently named primary method. Anyone selling landmarks as the central navigation instrument of screen reader users has not read the numbers.
The second question in the same survey paints a different picture, though. Asked how often they use landmarks when landmarks are present, respondents answered:
| Frequency | Share |
|---|---|
| Whenever available | 17.9% |
| Often | 13.9% |
| Sometimes | 31.5% |
| Seldom | 20.6% |
| Never | 16.1% |
Taken together, 31.8% use landmarks regularly — and the figure is rising again, after falling from 43.8% (2014) to 25.6% (2021).
My reading of those two tables: landmarks are not a primary tool, they are an orientation tool. Headings answer "where is the thing I am looking for?"; landmarks answer "how is this page built at all, and where does the content start?". That is a rarer but more consequential question — it comes up once per page, and it comes up first. And just under 32% of regular users is not a fringe group.
More to the point: landmarks cost you almost nothing. Choosing the right element is free, an aria-label is twenty characters. The effort-to-benefit ratio is unbeatable even at 3.7% — only the sales pitch has to stay honest.
The keystrokes that make this happen in practice:
| Screen reader | Next landmark | Next heading | Overview |
|---|---|---|---|
| NVDA (Windows) | D | H | NVDA + F7 (elements list) |
| JAWS (Windows) | R (Regions quick key) | H | JAWS + F6 (headings list) |
| VoiceOver (macOS) | via the rotor | VO + Cmd + H | VO + U (rotor) |
If you do not have a screen reader to hand: the Landmarks browser extension by matatk (Firefox, Chrome, Edge, Opera, open source) shows a page's landmarks as a menu, jumps between them by keyboard, highlights them visually and ships a DevTools panel with best-practice warnings. It is the fastest way to judge a page's landmark structure, and it costs two minutes to set up.
Usability beyond screen readers
Landmarks also do work in places where nobody is thinking about accessibility.
Voice control. People navigating by voice benefit from named regions and from a clear separation of navigation and content. The names you assign for assistive technology are the same ones that work as voice commands.
Reader modes. Firefox's reader mode is built on Mozilla's Readability library, Safari's Reader on a related approach. Both decide from the DOM structure what is content and what is chrome. Whether your article shows up in reader mode with or without half the sidebar depends directly on your markup — I measure that below.
Skip links. The classic "skip to content" link is landmark usage for everyone navigating by keyboard, screen reader or not. According to WebAIM Million 2026, 17.1% of home pages have one (2025: 15.3%), and "one out of every ten 'skip' links were broken". A skip link that never becomes visible or points nowhere is worse than none: it costs a tab stop and delivers nothing.
Browser extensions and tooling. Beyond the Landmarks extension mentioned above, outline tools, translation services and print stylesheets all read the structure. The clearer the regions, the better the automated downstream processing — which leads straight to the next point.
And the side effect that ends up mattering most: a page that can be cleanly divided into landmarks is a page with a clear structure. I have yet to see a project where the attempt to nail down the landmark structure did not surface at least one structural ambiguity in the layout. In my case this time it surfaced three — footer inside <main>, an empty <aside>, and a missing skip link — and all three were fixed inside an hour.
SEO: what is documented and what is folklore
This is where marketing and evidence part ways, so I will sort strictly.
What Google says
Google's John Mueller has answered this repeatedly; quoted via Search Engine Journal (June 2023):
Semantic HTML does help to understand a page. However, it's not a magical multiplier for making a website rank higher.
and
Please use semantic HTML. It's not a ranking factor, but it can help our systems to understand your content better.
You find the same sobriety in Google's own SEO Starter Guide. In the section on things you should not focus on, on the order of headings:
Having your headings in semantic order is fantastic for screen readers, but from Google Search perspective, it doesn't matter if you're using them out of order.
That is a remarkably clear sentence, and it applies to landmarks by analogy: there is no documented ranking factor called "landmark". Anyone telling you otherwise is selling something.
What is nevertheless true
Three effects are demonstrable without any ranking factor being involved.
1. Separating main from supplementary content is an evaluation criterion. Google's guidelines for quality raters have worked with the distinction between "Main Content" and "Supplementary Content" for years. Those are human raters, not an algorithm — but the category exists, and everything that makes that separation explicit in the markup works with it rather than against it.
2. Snippet selection and deep links benefit from structure. What Google pulls as a snippet, and whether it links to a section anchor, depends on how recognisable content blocks are. Landmarks are one signal among many here — headings and structured data are stronger ones.
3. The indirect route through your users is real. A page that works in reader mode, in read-aloud mode and in translation services loses fewer visitors. That is not a ranking factor, that is simply a product that works.
What is folklore
- "
<main>tells Google which part matters." No — Google has never claimed to weight<main>specially, and Mueller has said repeatedly that<section>,<article>and<div>are treated largely alike when grouping blocks of text. - "Landmarks improve crawl budget." There is no source for this.
- "Semantic elements are a ranking factor." Explicitly denied, see above.
My position: landmarks are not an SEO tool. They are an accessibility and structure tool that does not hurt SEO and helps it indirectly in several places. That is reason enough — nobody needs to invent a ranking factor for it.
GEO: landmarks in the extraction path of AI systems
And now the interesting part, because here something can actually be measured.
When an AI system processes your page — for a training corpus, for a RAG pipeline, for the answer of a chatbot with web search — a model almost never reads the raw HTML. In between sits an extractor: a library that cuts the actual text out of the HTML and throws away navigation, ads, cookie banners and footers. Two libraries dominate this field: Readability by Mozilla (the basis of Firefox's reader mode and of numerous HTML-to-Markdown services) and trafilatura (the de facto standard in Python data pipelines).
What those two read from your markup I can not only assert — I can show it in the source code and measure it.
What the source code says
Readability keeps a list of roles whose elements are removed during extraction (Readability.js, line 178 ff.):
UNLIKELY_ROLES: [
"menu",
"menubar",
"complementary",
"navigation",
"alert",
"alertdialog",
"dialog",
],And in the extraction pass (line 1141):
if (this.UNLIKELY_ROLES.includes(node.getAttribute("role"))) {
this.log("Removing content with role " + node.getAttribute("role") + " - " + matchString);
node = this._removeAndGetNext(node);
continue;
}So three of the eight ARIA landmark roles — complementary and navigation explicitly, search not — cause an element to be dropped from the extracted text. Readability additionally checks aria-hidden="true" and discards nodes marked that way.
trafilatura goes the other way round: it holds a prioritised list of XPath expressions with which it searches for the content area. At the top are (trafilatura/xpaths.py):
XPath("(.//article)[1]"),
XPath("""
(.//*[self::article or self::div or self::main or self::section][
@role='article' or @id='article' or @id='story' or …
])[1]
"""),
…
XPath("""
(.//*[self::article or self::div or self::section][
starts-with(@class, 'main') or starts-with(@id, 'main') or starts-with(@role, 'main')])[1]|(.//main)[1]
"""),<article> and <main> appear here as elements, as do role='article' and a role starting with main. And in the discard list:
contains(translate(@role, 'N', 'n'), 'nav') orBoth libraries read landmark semantics, then. Only — and this is the point — they read it differently.
The measurement
I put the same paragraphs into different wrappers and ran them through Readability 0.6.0 (with jsdom 30.0.1). In every variant the boilerplate block is built identically: three <p> paragraphs with the same text, no class names, no IDs. Only the enclosing container changes.
const wrappers = {
"<div>": ["<div>", "</div>"],
"<nav>": ["<nav>", "</nav>"],
"<aside>": ["<aside>", "</aside>"],
'<div role="navigation">': ['<div role="navigation">', "</div>"],
'<div role="complementary">': ['<div role="complementary">', "</div>"],
'<nav role="navigation">': ['<nav role="navigation">', "</nav>"],
'<aside role="complementary">': ['<aside role="complementary">', "</aside>"],
};The result:
| Boilerplate wrapped in | Boilerplate in the extract | Article text | Characters |
|---|---|---|---|
<div> | in | in | 2731 |
<nav> | in | in | 2731 |
<aside> | in | in | 2731 |
<div role="navigation"> | out | in | 1946 |
<div role="complementary"> | out | in | 1946 |
<nav role="navigation"> | out | in | 1946 |
<aside role="complementary"> | out | in | 1946 |
Read the first three rows again. To Readability, the <nav> element on its own makes no difference. The code queries node.getAttribute("role") — and a <nav> without a role attribute returns null there. The implicit ARIA role that the screen reader cares about exists only in the browser's accessibility tree; it appears nowhere in the DOM. A library working on jsdom or an HTML parser simply cannot see it.
That puts the standard advice — "never write <nav role="navigation">, it is redundant" — into a conflict I had not seen coming: for accessibility the role is redundant. For this extraction path it is the only thing that counts.
A note on care: in a first, coarser test setup — where the boilerplate text sat raw inside a <div> rather than in <p> paragraphs — it looked as though the bare <nav> element removed the navigation. That was an artefact: Readability converts <div> elements containing only text into <p> and thereby scores them as content candidates, whereas it does not do so for <nav>. The difference came from the div-to-p promotion, not from landmark semantics. Only the setup above, in which every variant contains identical <p> paragraphs, isolates the effect of the role attribute cleanly. Whoever measures has to isolate the variable — otherwise they are measuring the test rig.
What I take from this
The result holds for one extractor. trafilatura looks for elements (.//main, (.//article)[1]) and only honours role for "nav"; for Readability it is precisely the other way round. Further services — Jina Reader, Firecrawl and similar HTML-to-Markdown converters — frequently build on Readability, which weights its behaviour disproportionately.
My recommendation, deliberately phrased as a trade-off rather than a rule:
- Always the semantic element.
<nav>,<aside>,<main>,<footer>— that is the basis for everything else and the precondition for the landmark in the accessibility tree. - On the
<nav>and<aside>elements of the outer page structure, additionally the matchingrole, if clean extraction by AI systems and reader modes matters to you. That is a handful of attributes per page, it is specification-conformant, and it costs accessibility nothing. - Nowhere else. No
roleattributes in body copy, none on elements inside the main content, norole="main"on<main>(Readability does not look formaininUNLIKELY_ROLESat all, and trafilatura finds<main>as an element).
If you do not want the effort, drop point 2 and you lose nothing in accessibility — only extraction quality in part of the pipeline landscape. That is a legitimate decision; it should just be a conscious one.
And the most honest way to solve the extraction problem is a different one anyway: to offer the content directly as Markdown so that nothing has to be guessed. That is exactly what this site does with its per-page .md mirrors and its llms.txt — every page here is available at the same URL with .md appended, free of layout noise. Landmarks are the safety net for everyone who does not take that route.
Dos and don'ts
The whole article, condensed into a comparison.
| ✅ Do this | ❌ Not this |
|---|---|
Use semantic elements: <header>, <nav>, <main>, <aside>, <footer>, <search> | Writing <div role="banner"> where <header> is possible |
Exactly one visible <main> per page | Two main landmarks via <div role="main"> around <main> |
Page <footer> outside <main>, <article>, <section> | Putting the footer inside <main> for layout reasons |
Give every <nav> a name — including the first | Shipping five unnamed navigation landmarks |
aria-labelledby pointing at a visible heading where one exists | Maintaining the name twice: visible heading and diverging aria-label |
Labels without the role name: aria-label="Legal" | aria-label="Main navigation" → "main navigation navigation" |
<section> only with a name — otherwise it is a <div> | Turning every section into a region |
<search> for search | Writing new <form role="search"> (legacy may stay) |
Skip link as the first focusable element, tabindex="-1" on the target | A skip link that stays invisible on focus |
Layout spacers as <div> | An empty <aside> holding a column width |
Identically named <nav> for identical link sets | Naming the same navigation differently twice |
| Checking the accessibility tree | Inferring landmarks from the source code |
Common mistakes — and three from my own template
The list I work through when auditing, in order of frequency.
1. Unnamed duplicate landmarks. Four <aside> elements with no name at all, and five of nine <nav> elements likewise — that was the starting state on this very page. A screen reader then said "complementary" four times and, five times over, just "navigation". The landmark list does not become useless, but it loses exactly the property it exists for. The correction went in two directions: the four <aside> elements were pure layout wrappers and became <div>; the <nav> elements got names from the language file — deliberately without the word "navigation" in them, and with the same name for the desktop and mobile variants of the same link list.
2. The footer inside <main>. My second find, described at length above. It affects every layout that solves "sticky footer" with flexbox inside the content container. The fix was a column wrapper that takes over the height, so that <main> and <footer> become siblings.
3. The missing skip link. Find number three, and the most instructive: my own project documentation had said "skip link as the first focusable element" since the initial plan — it was never implemented. Writing a requirement down and implementing it are two separate work steps, and the gap between them only shows when somebody looks. In this case a blog article did.
4. <section> as a universal container. Without a name it achieves exactly nothing and suggests structure that never reaches the accessibility tree.
5. Content outside all landmarks. The cookie banner between </header> and <main>, the back-to-top button after </footer>, the modal at the end of <body>. The APG wants all perceivable content inside landmarks — orphaned elements are only found by reading linearly.
6. Nested top-level landmarks. Per the APG, banner, main, complementary and contentinfo belong at the top level and not inside one another.
7. Landmarks around dialogs. See above: modals do not need them.
8. The role inside the label. "Search area search", "sidebar complementary", "footer contentinfo".
9. Roles without the matching behaviour. role="button" on a <div>, no tabindex, no keyboard handling. Not landmark-specific, but the most common ARIA disaster overall.
10. Too many landmarks. A dozen region landmarks is as unusable as none. The landmark layer is an overview, not a table of contents.
For a sense of how widespread the baseline state is: the WebAIM Million report of February 2026 examined the home pages of the top 1,000,000 websites. 84.3% had at least one region or ARIA landmark defined (2025: 80.5%). A <main> element or main landmark was present on 46.1% (2025: 42.6%), a search landmark on 18.8% (2025: 16.6%). Half the web therefore has no marked-up main region. The direction is right, the level is not.
Testing: tools and an audit script
Landmarks cannot be judged from the source, because the implicit roles only come into being in the accessibility tree. Four routes I use:
1. The accessibility tree in DevTools. In Chrome and Edge: Elements panel → "Accessibility" tab → "Full-page accessibility tree". Firefox has a dedicated Accessibility panel with filtering by landmarks. This is the binding source: what is not listed as a landmark here is not one.
2. The Landmarks extension. See above — the fastest route to an overview, including a DevTools panel with warnings.
3. Automated checkers. axe DevTools, Lighthouse and WAVE cover part of the rules: duplicate landmarks, region without a name, content outside landmarks. What they do not find is the footer inside <main> — because that is not a violation, it is a correctly applied rule with an unwanted result. Do not rely on green ticks.
4. A script for the existing corpus. For an overview across many pages, a rough count in the built HTML is enough for me — the very one I used to collect the numbers in this article:
# Count landmark elements per page
f=_site/blog/html-landmarks/index.html
for t in nav main aside footer header article section form search; do
n=$(grep -oE "<${t}[[:space:]>]" "$f" | wc -l)
printf "%-8s %s\n" "$t" "$n"
done
# How many <nav> elements carry a name?
grep -oE '<nav[^>]*aria-label' "$f" | wc -l
# Find empty landmarks
grep -oE '<(aside|nav|section)[^>]*></(aside|nav|section)>' "$f"That is no substitute for the accessibility tree, but it finds the gross outliers in seconds: pages with six unnamed <nav> elements, empty <aside> elements, missing <main>. For the detailed check you go to DevTools afterwards.
One thing no tool will do for you: decide whether your landmark split is editorially correct. Whether this block really is supporting content or belongs to the main content, whether this collection of links is navigation or body copy — that is an editorial decision, not a technical one.
Frameworks and CMSs: the reality
Briefly, because the situation is similar everywhere.
WordPress block themes usually deliver workable landmarks via theme.json and the block templates: the group block knows HTML elements such as <main>, <aside> and <section>, and the navigation blocks render <nav>. The typical gap is names — several navigation blocks end up in the markup without an aria-label. It is worth checking header and footer templates in particular.
Bootstrap and Tailwind are entirely neutral on landmarks: they ship classes, not semantics. <div class="navbar"> stays a <div>. The Bootstrap docs do use <nav class="navbar"> in their examples, but nothing stops you from adopting that and then writing <div> again in your own components.
React, Vue, Svelte and the associated meta-frameworks bring the same neutrality — plus a specific risk: layout components that nest via fragments or wrapper <div> elements easily produce unintended nesting. Exactly the problem that cost my footer its contentinfo role. With client-side routing there is the additional issue that focus stays wherever it was after a view change; a <main tabindex="-1"> that receives programmatic focus after every navigation is the established solution there.
Static site generators — Eleventy, Astro, Hugo — have the advantage that the resulting HTML is directly inspectable. The script from the previous section runs across the whole _site/ directory and finds outliers across every page type.
The shared lesson: no framework gives you landmarks, and every layout system can take them away. The place you have to look is always the HTML you ship.
The checklist, ready to copy
For your review document, pull request template or ticket system:
## Landmark review
### Base structure
- [ ] Exactly one visible `<main>` present
- [ ] `<main>` is only a descendant of `html`, `body`, `div` or `form` (unnamed)
- [ ] The page `<header>` is a direct child of `<body>` → `banner`
- [ ] The page `<footer>` is NOT inside `<main>`/`<article>`/`<section>`/`<aside>`/`<nav>` → `contentinfo`
- [ ] "Skip to content" link is the first focusable element
- [ ] Jump target has `tabindex="-1"` and the link becomes visible on focus
- [ ] No perceivable content outside all landmarks (check cookie banners, buttons, modals)
### Labelling
- [ ] Every `<nav>` has `aria-label` or `aria-labelledby`
- [ ] Every `<aside>` has a name if more than one exists
- [ ] No label contains the role name ("navigation", "search", "footer", "complementary")
- [ ] `aria-labelledby` points at a heading that actually exists and is visible
- [ ] Navigations with identical link sets carry the same name
- [ ] Labels live in the language file, not hard-coded (for multilingual sites)
### Conditional roles
- [ ] Every `<section>` meant to be a landmark has an accessible name
- [ ] Every `<form>` meant to be a landmark has an accessible name
- [ ] Nested `<aside>` elements have a name (the specifications disagree here)
- [ ] Search uses `<search>` rather than `<form role="search">`
### Hygiene
- [ ] No redundant roles in body content (`<nav role="navigation">` only deliberately, see below)
- [ ] No empty landmarks (layout spacers are `<div>`)
- [ ] No nested top-level landmarks (banner/main/complementary/contentinfo)
- [ ] No landmarks wrapping modal dialogs
- [ ] No `role="presentation"`/`role="none"` on navigation lists
- [ ] At most a handful of `region` landmarks per page
### Extraction (optional, for AI/reader-mode optimisation)
- [ ] Outer `<nav>` elements additionally carry `role="navigation"`
- [ ] Outer `<aside>` elements additionally carry `role="complementary"`
- [ ] `<main>` and `<article>` are present as elements (trafilatura looks for them)
- [ ] Alternatively/additionally: ship a Markdown version of the page
### Verification
- [ ] Accessibility tree checked in DevTools (not just the source)
- [ ] Landmark list walked with a screen reader or the Landmarks extension
- [ ] Automated checker (axe/Lighthouse/WAVE) reports no landmark findings
- [ ] Mobile variant checked — duplicated menus count twiceFAQ
Do I need <section> at all? Only with a name, and only for areas that should genuinely be jump targets. Without a name, <section> is identical to <div> — in which case use <div>, it is more honest.
Is <article> a landmark? No. <article> has the role article from the "document structure" category. Screen readers can still navigate articles, but <article> does not appear in the landmark list.
May I have several <main> elements? In the DOM yes, visible no. The HTML Standard requires that at most one <main> exists without a hidden attribute. For single-page applications holding several views, that is the intended solution.
What about <header> and <footer> inside <article>? They are correct and useful — they are just not landmarks. Under current HTML-AAM they get the roles sectionheader/sectionfooter from ARIA 1.3; under ARIA in HTML, generic. Either way: not a landmark, not a jump target.
Should I put role="navigation" on <nav> or not? For accessibility: no, redundant. For extraction by Readability-based tooling: yes, because only the attribute exists in the DOM. My recommendation is the compromise from the GEO chapter — on the outer structural elements yes, elsewhere no.
Does a menu hidden by CSS count as a landmark? display: none and visibility: hidden remove an element from the accessibility tree; off-canvas positioning (left: -100vw, transform) does not. A mobile drawer that is merely moved stays a landmark — on desktop, in addition to the desktop navigation.
How many landmarks are too many? There is no number in the specification. My rule of thumb: if the landmark list is longer than what you would tell somebody on the phone about the structure of the page, there are too many.
Do landmarks help rankings? No. Google has explicitly said they do not. They help with being understood — by screen readers, reader modes and extractors. That is a different and, I think, better argument.
What about aria-current? Not a landmark attribute, but the natural partner: aria-current="page" marks the active entry inside a navigation landmark. Without it even the finest <nav> leaves users disoriented.
And role="region" versus <section aria-label> — which is better? Identical in outcome. Use <section> with a name, because the element is there anyway and the rule then lives in one place only.
Conclusion
Landmarks are cheap and still mostly get done wrong. That is not down to their complexity — there are eight roles — but to the fact that the decisive rules are conditions that no validator checks and that cannot be read off the source.
The three sentences I would keep if I were allowed to keep only three:
- Nesting decides.
<header>and<footer>are landmarks only at body level. One layout container too many and the region disappears — no error, no warning, nobody notices. - No name, no landmark; and a wrong name, no good landmark.
<section>and<form>need an accessible name to count at all; landmarks that occur more than once need distinguishable names to be useful; and none of those names may repeat the role. - The implicit role is not in the DOM. It only comes into being in the browser's accessibility tree. Everything working on an HTML parser — extractors, reader modes, AI pipelines — cannot see it. That is why the supposedly superfluous
roleattribute makes the difference in part of that tooling.
What surprised me most while writing is in the first line: I started auditing my own site and found three issues in five minutes — a missing footer landmark, an empty <aside>, and a skip link that had been documented since the initial plan and never built. None of them was an error in the eyes of a tool. All three were errors in the eyes of users. And all three were fixed inside an hour — which is the actual point: the effort is in looking, not in repairing.
Take the checklist, open the accessibility tree of your most important page and look at which landmarks actually arrive. It takes five minutes, and I bet you find something too.
Glossary
Terms that appear in the text and would have overloaded the sentence there.
| Term | Meaning |
|---|---|
| Accessibility tree | The second tree a browser builds alongside the DOM: every element with a role, a name and a state. Screen readers read it, not the HTML. |
| ARIA | Accessible Rich Internet Applications — the W3C specification for attributes describing an element's role, state and relationships to assistive technology. |
| ARIA in HTML | The W3C specification defining, for every HTML element, its implicit ARIA role and which attributes are permitted. |
| APG | ARIA Authoring Practices Guide — the W3C authoring guide with patterns and rules for using ARIA. |
| DPUB-ARIA | The ARIA extension for digital publishing, with roles such as doc-toc or doc-bibliography. |
| GEO | Generative Engine Optimization — optimising content for generative AI systems rather than for classic search result pages. |
| HTML-AAM | HTML Accessibility API Mappings — the specification describing how browsers hand HTML elements to the operating system's accessibility APIs. |
| jsdom | A JavaScript implementation of the DOM for Node.js — lets you parse and manipulate HTML without a browser. |
| Landmark | A region of the page that assistive technology can jump to directly. Eight roles in ARIA 1.2. |
| RAG | Retrieval-augmented generation — an approach where a language model looks up matching documents from a data set before answering. |
| Readability | Mozilla's library for extracting the main content from HTML; the basis of Firefox's reader mode and of many HTML-to-Markdown services. |
| Rotor | VoiceOver's navigation menu (VO + U), which lists a page's headings, links, landmarks and other element types. |
| Sectioning content | The HTML category comprising <article>, <aside>, <nav> and <section>, which feeds the document outline algorithm. |
| trafilatura | A Python library for text and metadata extraction from web pages; widely used in data pipelines for language models. |
| Accessible name | The text assistive technology uses to name an element — computed from content, aria-label, aria-labelledby, <label> or title. |
Further reading
- ARIA in HTML — W3C — the authoritative table of implicit roles
- HTML Accessibility API Mappings — W3C — how browsers hand HTML to the accessibility APIs
- WAI-ARIA 1.2 — W3C — the role taxonomy with the eight landmark roles
- Landmark Regions — ARIA Authoring Practices Guide — the authoring rules on labelling and nesting
- Page Regions — W3C WAI tutorial — the didactic introduction
- ARIA11: Using ARIA landmarks to identify regions of a page — a sufficient technique for WCAG 1.3.1, 1.3.6 and 2.4.1
- ARIA roles — MDN — reference for all roles including the landmark category
<search>— MDN — the element for the search landmark<main>— HTML Standard (WHATWG) — the "at most one visiblemain" rule- WebAIM Million 2026 — landmark prevalence across one million home pages
- Screen Reader User Survey #10 — WebAIM — the numbers on actual landmark usage
- Landmarks — browser extension by matatk — jump to, highlight and audit landmarks
- Readability — Mozilla — the source code with
UNLIKELY_ROLES - trafilatura — the second major extractor, element-based
- SEO Starter Guide — Google — including the note on semantic ordering
- Semantic HTML: Not A Ranking Factor But Still Important — the quoted statements by John Mueller
From this blog:
- CSS
@layerin Practice — the other structural decision you make once and use for years - DESIGN.md: The Design System as a File the AI Can Read Too — how structural knowledge gets documented so it does not get lost
- Who Checks the Checker? Adversarial Fact-Fidelity for AI Text — the method used to verify the claims in this article
- Technical SEO: The Foundation — crawling, rendering and structure in context
- Writing for AI: The GEO Writing Playbook — why a Markdown version of a page is the most honest answer to the extraction problem