HTML Module 2 – Text, semantics and page structure
Page structure: header, nav, main and footer
Lay out a page with header, nav, main, aside, footer, search, section and article, see which become landmarks, and name repeated landmarks clearly.
What you will learn
- Structure a page with header, nav, main, aside, footer, section, article and search
- Explain which elements become landmarks for assistive technology, and when header and footer do not
- Decide between article, section and div
- Label repeated landmarks so that users can tell them apart
Before you start
What you’ll learn · Before you start
What you will learn
- Structure a page with header, nav, main, aside, footer, section, article and search
- Explain which elements become landmarks for assistive technology, and when header and footer do not
- Decide between article, section and div
- Label repeated landmarks so that users can tell them apart
Before you start
On this page
Most pages share the same parts: a header with the site’s name and its main links, the main content, perhaps a
sidebar, and a footer. HTML has an element for each: <header>, <nav> for major groups of links, <main> for the
page’s own content, <aside> for content to one side, and <footer>, plus <search> for a search form. Inside the
content, <article> wraps something complete in itself, such as a post, and <section> a themed part with a heading.
Browsers turn several of these elements into landmarks: named regions that assistive technology lists and lets
users jump between, so someone using a screen reader can go straight to the main content or the search box. Use one
<main> per page, and when a page has two landmarks of the same kind, such as two <nav> elements, give each a
different name.
The regions of a page
HTML-AAM, the specification that maps HTML elements to what browsers expose to assistive technology, says which element becomes which landmark:
<header>is the banner landmark, but only when it is not insidearticle,aside,main,navorsection.<nav>is a navigation landmark, always.<main>is the main landmark, always; a page has one.<aside>is a complementary landmark at the top level or insidemain; insidearticle,aside,navorsection, only when it has a name.<footer>is the contentinfo landmark, but only when it is not insidearticle,aside,main,navorsection.<search>is the search landmark, always.<section>is a region landmark only when it has a name, fromaria-labeloraria-labelledby.<article>and<div>are not landmarks: an article is a self-contained item, and a div has no meaning.
Landmarks are one of the sufficient techniques that WCAG 2.2 lists for success criterion 2.4.1, Bypass Blocks, which is about letting people jump past the parts that every page of a site repeats; headings are another. Screen readers offer them in several ways. NVDA, for example, moves to the next landmark with the D key, and in WebAIM’s tenth screen reader user survey, 31.8 % of respondents said they use landmarks whenever they are available or often.
Rebuilding a page from divs
Here is a cycling club’s page of weekend rides, built entirely from <div> elements:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Weekend rides | Riverside Cycling Club</title>
</head>
<body>
<div class="header">
<div class="logo">Riverside Cycling Club</div>
<div class="menu">
<a href="/">Home</a> <a href="/rides/">Rides</a> <a href="/join/">Join</a>
</div>
<div class="search">
<form action="/search/">
<input type="search" name="q" aria-label="Search rides">
<button type="submit">Search</button>
</form>
</div>
</div>
<div class="content">
<h1>Weekend rides</h1>
<div class="ride">
<h2>Lake loop, 40 km</h2>
<p>An easy morning ride on flat roads, with a tea stop halfway.</p>
<div class="ride-footer">Leader: Farah. Meet at the north gate at 6 am.</div>
</div>
<div class="ride">
<h2>Hill climb, 25 km</h2>
<p>Two steep climbs and a long descent. Bring lights and a spare tube.</p>
<div class="ride-footer">Leader: Arjun. Meet at the station at 5:30 am.</div>
</div>
</div>
<div class="sidebar">
<h2>Safety first</h2>
<p>Helmets are required on every club ride.</p>
<div class="menu">
<a href="/rides/easy/">Easy rides</a> <a href="/rides/long/">Long rides</a>
</div>
</div>
<div class="footer">
<p>Riverside Cycling Club, run by volunteers.</p>
</div>
</body>
</html> Both validators passed it: the Nu checker and html-validate (with its document preset) reported nothing, because
every element is used correctly. It is valid HTML with no structure. axe-core 4.13.0 is the tool that noticed, in
Chromium 153 and Firefox 155: landmark-one-main (Document should have one main landmark) and region
(All page content should be contained by landmarks) on 6 elements. Here is the same page with the elements that say what each
part is:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Weekend rides | Riverside Cycling Club</title>
</head>
<body>
<header>
<p class="logo"><a href="/">Riverside Cycling Club</a></p>
<nav aria-label="Main">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/rides/">Rides</a></li>
<li><a href="/join/">Join</a></li>
</ul>
</nav>
<search>
<form action="/search/">
<input type="search" name="q" aria-label="Search rides">
<button type="submit">Search</button>
</form>
</search>
</header>
<main>
<h1>Weekend rides</h1>
<article>
<h2>Lake loop, 40 km</h2>
<p>An easy morning ride on flat roads, with a tea stop halfway.</p>
<footer>Leader: Farah. Meet at the north gate at 6 am.</footer>
</article>
<article>
<h2>Hill climb, 25 km</h2>
<p>Two steep climbs and a long descent. Bring lights and a spare tube.</p>
<footer>Leader: Arjun. Meet at the station at 5:30 am.</footer>
</article>
</main>
<aside>
<h2>Safety first</h2>
<p>Helmets are required on every club ride.</p>
<nav aria-label="Ride types">
<ul>
<li><a href="/rides/easy/">Easy rides</a></li>
<li><a href="/rides/long/">Long rides</a></li>
</ul>
</nav>
</aside>
<footer>
<p>Riverside Cycling Club, run by volunteers.</p>
</footer>
</body>
</html> The program below reads both files and lists their landmarks, using the HTML-AAM rules from the table above:
// Lists the landmarks of each page, as a screen reader's landmark list would, using the rules of the HTML
// Accessibility API Mappings (HTML-AAM):
// header and footer are the banner and contentinfo landmarks only when they are not inside article, aside, main,
// nav or section; inside one of those they are a section header or footer, which is not a landmark;
// nav is navigation, main is main, search is search;
// aside is complementary at the top level or inside main, but inside article, nav or section only when it has a name;
// section is a region only when it has a name (aria-label, aria-labelledby or title).
// The tag reader below is enough for these two valid files; browsers use a full HTML parser.
import { readFileSync } from "node:fs";
const PAGES = ["club-divs.html", "club-landmarks.html"];
const VOID = new Set(["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "source", "track", "wbr"]);
const SECTIONING = new Set(["article", "aside", "main", "nav", "section"]);
const attributesOf = (s) => Object.fromEntries([...s.matchAll(/([a-z-]+)(?:="([^"]*)")?/g)].map((m) => [m[1], m[2] ?? ""]));
function elementsOf(html) {
const source = html.replace(/<!--[\s\S]*?-->/g, "");
const open = [];
const all = [];
for (const t of source.matchAll(/<(\/?)([a-zA-Z][\w-]*)([^>]*)>|([^<]+)/g)) {
if (t[4] !== undefined) {
for (const el of open) el.text += t[4];
continue;
}
const name = t[2].toLowerCase();
if (t[1]) {
const at = open.map((el) => el.name).lastIndexOf(name);
if (at >= 0) open.length = at;
continue;
}
const el = { name, attrs: attributesOf(t[3]), parents: [...open], text: "" };
all.push(el);
if (!VOID.has(name)) open.push(el);
}
return all;
}
function nameOf(el, byId) {
if (el.attrs["aria-labelledby"]) return el.attrs["aria-labelledby"].split(/\s+/).map((id) => byId.get(id)?.text.trim() ?? "").join(" ");
return el.attrs["aria-label"] ?? el.attrs.title ?? "";
}
function roleOf(el, name) {
const inSection = el.parents.some((p) => SECTIONING.has(p.name));
switch (el.name) {
case "header": return inSection ? null : "banner";
case "footer": return inSection ? null : "contentinfo";
case "nav": return "navigation";
case "main": return "main";
case "search": return "search";
case "aside": return !el.parents.some((p) => ["article", "aside", "nav", "section"].includes(p.name)) || name ? "complementary" : null;
case "section": return name ? "region" : null;
default: return null;
}
}
for (const page of PAGES) {
const elements = elementsOf(readFileSync(page, "utf8"));
const byId = new Map(elements.filter((el) => el.attrs.id).map((el) => [el.attrs.id, el]));
const landmarks = new Set();
const lines = [];
for (const el of elements) {
const name = nameOf(el, byId);
const role = roleOf(el, name);
if (!role) continue;
landmarks.add(el);
const depth = el.parents.filter((p) => landmarks.has(p)).length;
lines.push(`${" ".repeat(depth + 1)}${role}${name ? ` "${name}"` : ""}`);
}
const sectionParts = elements.filter((el) => ["header", "footer"].includes(el.name) && !landmarks.has(el)).length;
console.log(`${page}: ${lines.length} landmark${lines.length === 1 ? "" : "s"}`);
for (const line of lines) console.log(line);
if (sectionParts) console.log(` (${sectionParts} header or footer elements belong to a section, so they are not landmarks)`);
console.log("");
} Output
club-divs.html: 0 landmarks
club-landmarks.html: 7 landmarks
banner
navigation "Main"
search
main
complementary
navigation "Ride types"
contentinfo
(2 header or footer elements belong to a section, so they are not landmarks)
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node landmarks.mjs
Chromium 153’s accessibility tree, the structure it gives assistive technology, matched the program: a banner holding
navigation “Main” and search, then main, complementary with navigation “Ride types”, and contentinfo.
axe-core reported no violations for the new version in either engine.
The landmarks of the rebuilt page
Text description of the diagram
The diagram draws the body of the club page as a box with four landmarks inside it, from top to bottom.
- The header is the banner landmark. Inside it are a nav, the navigation landmark named Main, and the search element, the search landmark.
- The main element is the main landmark. It holds two article elements, which are not landmarks.
- The aside is the complementary landmark. Inside it is a second nav, the navigation landmark named Ride types.
- The footer is the contentinfo landmark.
The footers inside the two articles are not drawn: they belong to their articles, so they are section footers and not landmarks.
When header and footer are landmarks
A page usually has one header and one footer for the whole site. Those become the banner and contentinfo
landmarks. But <header> and <footer> can also belong to a part of the page: the byline of an article, the meeting
point of each ride. Inside article, aside, main, nav or section, they are a section header or footer, not a
landmark, so a page can use them many times without filling the landmark list. In the rebuilt page, Chromium gave both
ride footers the sectionfooter role, and the page footer the contentinfo role.
So keep the site’s own header and footer directly inside <body>, not inside <main> or a wrapper <section>. A
site footer moved inside <main> by a layout component stops being the page’s contentinfo landmark.
article, section or div
Ask what the content is:
<article>is complete in itself and could be published on its own: a post, a news story, a product card, a comment, a ride announcement. The Standard’s test is whether it is “in principle, independently distributable or reusable”. Articles can nest: comments are articles inside a post.<section>is a part of something larger, grouped by theme, and should usually start with a heading. The Standard puts it plainly: a section is appropriate only if its contents would be listed in the document’s outline. It becomes aregionlandmark only when it has a name.<div>has no meaning. Use it for a wrapper that exists only for styling or for a script.
On a test page, Chromium exposed a <section> without a name as a generic element, and one with
aria-labelledby pointing at its heading as a region named after that heading. An unnamed <aside> inside an
<article> was generic too, while an <aside aria-label="Related"> was a complementary landmark. Name a section
only when it is worth a place in the landmark list, such as a large, distinct part that users would want to jump to.
Naming repeated landmarks
A page with two <nav> elements has two landmarks with the same role, and a landmark list that says
“navigation, navigation” does not help anyone choose. Give each a short name with aria-label, or point
aria-labelledby at a visible heading. The rebuilt page names its two <nav> elements “Main” and “Ride types”. With
both names removed, axe-core reported landmark-unique in both engines, the rule that every landmark needs a role, or
a pair of role and name, that no other landmark on the page has. It reported the same violation when both were named
“Main”.
Keep names short and do not repeat the role, which screen readers announce anyway. The ARIA Authoring Practices
Guide gives the example of a navigation landmark labelled “Site Navigation”, which a screen reader announces as “Site
Navigation Navigation”. Not every group of links needs a <nav> either. The Standard says <nav> is for major
navigation blocks, and that a short list of links in a footer needs no <nav>, because the <footer> is enough.
Government websites
Guideline 5.2.27 of GIGW 3.0, the guidelines for Indian government websites and apps, adopts WCAG 2.1’s Bypass Blocks criterion: government sites must give users a way to skip blocks of content that repeat on every page. Landmarks built from these elements are one of the techniques that WCAG accepts for it, with headings and skip links.
One main, and the search element
A document must not have more than one <main> element that is not hidden, and <main> may sit only inside
<html>, <body>, <div>, an unnamed <form> or a custom element, not inside <article>, <aside>, <nav> or
<section>. It holds the content that is unique to this page, so the site header, navigation and footer stay outside
it.
<search> wraps a search or filter form and becomes the search landmark. It is for the controls, not for the list of
results, which belongs in <main>. web-features lists <search> as Baseline widely available: Chrome 118, Firefox 118
and Safari 17 support it, according to MDN’s browser-compat-data. In an older browser that does not know it, the form
inside still works; the element simply adds no landmark.
Try it: give a blog home page its landmarks
This food blog’s home page is built from <div> elements, and axe-core reported the same two violations as the club
page, landmark-one-main and region on 6 elements:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Kitchen Notes: everyday Indian cooking</title>
</head>
<body>
<div class="top">
<div class="site-name">Kitchen Notes</div>
<div class="links">
<a href="/">Home</a> <a href="/recipes/">Recipes</a> <a href="/about/">About</a>
</div>
<div class="find">
<form action="/search/">
<input type="search" name="q" aria-label="Search recipes">
<button type="submit">Search</button>
</form>
</div>
</div>
<div class="posts">
<h1>Latest recipes</h1>
<div class="post">
<h2>Poha in ten minutes</h2>
<p>Flattened rice with onion, peanuts and lemon, the quickest breakfast in the book.</p>
<div class="meta">Posted by Meera</div>
</div>
<div class="post">
<h2>Tomato rasam</h2>
<p>A thin, peppery soup that is ready while the rice cooks.</p>
<div class="meta">Posted by Ravi</div>
</div>
</div>
<div class="side">
<h2>Categories</h2>
<div class="links">
<a href="/recipes/breakfast/">Breakfast</a> <a href="/recipes/soups/">Soups</a>
</div>
</div>
<div class="bottom">
<p>Kitchen Notes is written by two home cooks.</p>
</div>
</body>
</html> Rebuild it so that it has a banner with a named main navigation and a search landmark, one <main>, an <article> for
each post with its byline in a <footer>, a sidebar whose category links are a second, differently named navigation,
and a page footer. Then paste your version into the HTML Validator, or count your landmarks by hand. One answer, with no
axe-core violations in either engine:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Kitchen Notes: everyday Indian cooking</title>
</head>
<body>
<header>
<p class="site-name"><a href="/">Kitchen Notes</a></p>
<nav aria-label="Main">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/recipes/">Recipes</a></li>
<li><a href="/about/">About</a></li>
</ul>
</nav>
<search>
<form action="/search/">
<input type="search" name="q" aria-label="Search recipes">
<button type="submit">Search</button>
</form>
</search>
</header>
<main>
<h1>Latest recipes</h1>
<article>
<h2>Poha in ten minutes</h2>
<p>Flattened rice with onion, peanuts and lemon, the quickest breakfast in the book.</p>
<footer>Posted by Meera</footer>
</article>
<article>
<h2>Tomato rasam</h2>
<p>A thin, peppery soup that is ready while the rice cooks.</p>
<footer>Posted by Ravi</footer>
</article>
</main>
<aside>
<h2>Categories</h2>
<nav aria-label="Categories">
<ul>
<li><a href="/recipes/breakfast/">Breakfast</a></li>
<li><a href="/recipes/soups/">Soups</a></li>
</ul>
</nav>
</aside>
<footer>
<p>Kitchen Notes is written by two home cooks.</p>
</footer>
</body>
</html> Its landmarks: banner (with navigation “Main” and search), main, complementary (with navigation
“Categories”) and contentinfo. The two post footers are section footers, not landmarks.
Check yourself
6 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
Interview questions
What is a landmark, and which HTML elements create one?
A landmark is a region of a page that browsers expose to assistive technology with a role, so that users can list the
regions and jump straight to one: the site header, the main content, the navigation, the search. In HTML you rarely
need ARIA for them, because the elements map to landmark roles by themselves: <main> to main, <nav> to
navigation, <search> to search, <aside> to complementary, and <header> and <footer> to banner and
contentinfo when they belong to the whole page rather than to an article or a section. A <section> becomes a
region only when it has an accessible name, and <article> and <div> are not landmarks. A good page has one
<main>, keeps all its content inside landmarks, and names landmarks that repeat, such as two <nav> elements.
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Mid to senior
In a single-page app, the shell renders the site header and footer, and every routed view also renders its own header and footer inside a wrapper div. Screen reader users report two banners on some screens and none on others. How do you diagnose and fix the landmark structure?
-
Mid to senior
Design the landmark structure for an online shop's product page with a mega-menu, breadcrumbs, the product details, reviews, a "customers also bought" strip and a footer full of links. Which elements do you use, and which parts get names?
Key takeaways
header,nav,main,aside,footerandsearchgive a page landmarks that screen reader users can jump between;divgives nothing.headerandfooterare the page’sbannerandcontentinfoonly outsidearticle,aside,main,navandsection; inside, they belong to that part.articleis complete in itself,sectionis a themed part with a heading (aregiononly when named), anddivis a plain wrapper.- Use one
mainper page, and name repeated landmarks with short names that do not repeat the role. - Validators accept a page of
divs; axe-core’slandmark-one-main,regionandlandmark-uniquerules catch what they miss.
References
- HTML Standard: Sections (WHATWG)
- HTML Standard: the main element (WHATWG)
- HTML Standard: the search element (WHATWG)
- HTML Accessibility API Mappings (HTML-AAM) (W3C)
- Understanding WCAG 2.2: Bypass Blocks (W3C)
- web-features: the search element (W3C WebDX Community Group)
- NVDA User Guide (single letter navigation) (NV Access)
- Screen Reader User Survey 10 Results (WebAIM)
- ARIA Authoring Practices Guide: Landmark Regions (W3C)
- axe-core 4.13 rule: landmark-unique (Deque Systems)
- Guidelines for Indian Government Websites and Apps (GIGW 3.0) (National Informatics Centre, Government of India)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress