Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

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.

  • Beginner
  • 25 minutes
  • Examples run with Node.js 24.21.0
  • By MySmartCoPilot
Free lesson, Pro answers: 25 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

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 inside article, aside, main, nav or section.
  • <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 inside main; inside article, aside, nav or section, only when it has a name.
  • <footer> is the contentinfo landmark, but only when it is not inside article, aside, main, nav or section.
  • <search> is the search landmark, always.
  • <section> is a region landmark only when it has a name, from aria-label or aria-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:

A page made of divs HTML · club-divs.html
<!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:

The same page with landmarks HTML · club-landmarks.html
<!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:

The landmarks of each version JavaScript · landmarks.mjs
// 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 rebuilt page: banner with navigation and search, main with two articles, complementary with a second navigation, and contentinfo.bodyheader: bannermain: mainaside: complementaryfooter: contentinfonav: navigation(Main)search: searcharticle (not a landmark)article (not a landmark)nav: navigation(Ride types)

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.

  1. The header is the banner landmark. Inside it are a nav, the navigation landmark named Main, and the search element, the search landmark.
  2. The main element is the main landmark. It holds two article elements, which are not landmarks.
  3. The aside is the complementary landmark. Inside it is a second nav, the navigation landmark named Ride types.
  4. 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.

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 a region landmark 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:

A blog home page made of divs HTML · blog-start.html
<!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:

The blog home page with landmarks HTML · blog-answer.html
<!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.

  1. Question 1 of 6 A <header> holds the title and byline of an <article> inside <main>. What does it become for assistive technology?

    Choose one answer.

    Show the answer to question 1

    Answer: A header of that article, not a landmark

    <header> is the banner landmark only when it is not inside article, aside, main, nav or section. Inside them it belongs to that part of the page; Chromium gives it the sectionheader role.

  2. Question 2 of 6 Which of these elements always become landmarks?

    Choose every answer that is right.

    Show the answer to question 2

    Answer:

    • <nav>
    • <search>
    • <main>

    <nav>, <main> and <search> always map to the navigation, main and search roles. A <section> is a region landmark only when it has a name, and an <article> is never a landmark.

  3. Question 3 of 6 A page has two <nav> elements without names, and axe-core reports landmark-unique. What fixes it?

    Choose one answer.

    Show the answer to question 3

    Answer: Give each <nav> a different short name with aria-label or aria-labelledby

    Two landmarks with the same role and no names look the same in a landmark list. Names such as "Main" and "Ride types" let users tell them apart; giving both the same name still fails the rule.

  4. Question 4 of 6 Which of these follows the HTML Standard's rules for <main>?

    Choose one answer.

    Show the answer to question 4

    Answer: One <main> directly inside <body>, with the site header and footer outside it

    A document must not have more than one visible <main>, and its ancestors may only be html, body, div, an unnamed form or custom elements. It holds the content unique to the page.

  5. Question 5 of 6 Which is the best name for a site's primary <nav>?

    Choose one answer.

    Show the answer to question 5

    Answer: aria-label="Main"

    Screen readers announce the role anyway, so a name that repeats it is heard twice ("Main navigation navigation"). A short, distinct name is enough.

  6. Question 6 of 6 Each product card on a listing page has a name, a photo, a price and a link. Which element suits one card?

    Choose one answer.

    Show the answer to question 6

    Answer: <article>, because each card is complete in itself

    An <article> is a self-contained item that could be reused on its own, such as a post, a comment or a product card. A <section> is a themed part of something larger, usually with a heading, and <nav> is for major blocks of navigation links.

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.

  1. 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?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. 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?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • header, nav, main, aside, footer and search give a page landmarks that screen reader users can jump between; div gives nothing.
  • header and footer are the page’s banner and contentinfo only outside article, aside, main, nav and section; inside, they belong to that part.
  • article is complete in itself, section is a themed part with a heading (a region only when named), and div is a plain wrapper.
  • Use one main per page, and name repeated landmarks with short names that do not repeat the role.
  • Validators accept a page of divs; axe-core’s landmark-one-main, region and landmark-unique rules catch what they miss.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.