RizTech Academy logo
RizTech Academy
AccessibilityLesson 2 of 625 min

Headings, landmarks and structure

Module 1 taught you which structural elements exist. This lesson is about what a screen reader user actually does with them, because once you have watched that, the reason for the rules stops being abstract.

Nobody reads a page top to bottom

A sighted visitor scans — headings, bold text, images — and jumps to the part they want. That takes about two seconds and needs no thought.

A screen reader user does the same thing, with different tools. They do not listen from the top. They pull up a list.

In VoiceOver, Ctrl + Option + U opens the rotor: a menu of the page's headings, or its links, or its landmarks, or its form controls. In NVDA, Insert + F7 lists elements the same way. H jumps to the next heading, D to the next landmark, K to the next link, 1 to the next level-1 heading.

Your headings and landmarks are that menu. They are not typography — they are the table of contents that decides whether the page can be navigated at all.

Which reframes everything:

  • A page with no headings has an empty menu. The only option is to listen to everything, in order.
  • A page whose headings are chosen for size has a menu in the wrong order.
  • A <div class="title"> styled to look like a heading is not in the menu at all.

Heading structure as an outline

<h1>Sharma Kirana</h1>
  <h2>Our prices</h2>
    <h3>Atta and rice</h3>
    <h3>Oil and ghee</h3>
  <h2>Delivery</h2>
    <h3>Areas we cover</h3>
  <h2>Contact</h2>

Read the headings alone and you should have a fair summary of the page. That is the test, and it is the same test as a document's table of contents.

The rules, and the reason for each:

One <h1>, saying what this page is. Not the site name repeated on every page — About Sharma Kirana, Delivery areas. A screen reader user pressing 1 expects to land on the page's subject.

Never skip a level going down. h1 then h3 makes a listener wonder what they missed. Going back up is fine — h3 then h2 starts a new section.

Choose the level for structure, never for size. If your h2 is too big, that is one line of CSS. This was the module 1 rule and it is worth repeating because the temptation is constant.

Do not use a heading for something that is not one. A short bold line introducing a paragraph is a <p> with a class, or a <strong>. Making it an h4 puts noise in the menu.

Checking it

Devtools does this for you. In Chrome, Elements → Accessibility → and the full tree; or run this in the console:

[...document.querySelectorAll("h1,h2,h3,h4,h5,h6")]
  .forEach(h => console.log("  ".repeat(+h.tagName[1] - 1) + h.tagName + " " + h.textContent.trim().slice(0, 60)));

That prints your outline, indented. If it does not read like a sensible contents page, neither does the page. It also makes a skipped level obvious, because the indentation jumps two steps.

Landmarks

The second menu. Each of these creates a region a user can jump straight to:

<body>
  <header>            <!-- banner -->
    <nav>…</nav>      <!-- navigation -->
  </header>
  <main>…</main>      <!-- main -->
  <aside>…</aside>    <!-- complementary -->
  <footer>…</footer>  <!-- contentinfo -->
</body>

<main> is the one to get right. It is what "skip to main content" targets, and without it a keyboard user tabs through your entire navigation on every page. One per page, not nested inside the others.

Two details worth knowing:

<header> and <footer> are only landmarks at page level. Inside an <article> or <section> they are just grouping elements, which is correct and means you can use them freely inside components.

Label your landmarks when there is more than one of a kind. Two <nav>s produce two identical "navigation" entries in the menu:

<nav aria-label="Main">…</nav>
<nav aria-label="Breadcrumb">…</nav>
<nav aria-label="Footer">…</nav>

Do not write aria-label="Main navigation" — the role is already announced, so the listener hears "Main navigation navigation". The label is the distinguishing word only.

You can also point at a visible heading instead, which is better when one exists:

<section aria-labelledby="delivery-heading">
  <h2 id="delivery-heading">Delivery</h2>
  …
</section>

aria-labelledby reuses text that is already on screen, so it cannot drift out of sync and it is translated along with the page. Prefer it to aria-label whenever there is visible text to point at.

Lists and tables are structure too

From module 1, with the reason attached now:

A <ul> is announced as "list, 5 items", and each item as "item 2 of 5". That count is orientation — it tells a listener how much is coming and where they are. A stack of <div>s gives none of it, and there is no way to recover it.

A <table> with scope lets a user land on a cell and hear "Karve Nagar, Charge, ₹20". Without it they hear "₹20" and have to remember which column they are in while navigating a grid by keyboard. That is the difference between a usable table and an unusable one, and it is two attributes.

Reading order is DOM order

The single most important consequence of modules 3 and 4.

A screen reader and the keyboard follow the DOM, not your CSS. So order in Flexbox, grid-template-areas, grid-auto-flow: dense and position: absolute can all put the visual order and the reading order out of step — and when they do, a keyboard user tabs around the screen in an order that makes no sense while a listener hears the page in an order that does not match what anybody sees.

Get the HTML order right and you never have this problem. The module 3 practice-layouts skeleton is main before aside for exactly this reason: the mobile order is the DOM order, so nothing needs rearranging and content comes before the sidebar at every width.

The test: tab through the page and watch where the focus ring goes. If it jumps around, fix the HTML, not the CSS.

Language

<html lang="en">
…
<p>The shop is called <span lang="hi">किराना</span>.</p>

From module 1, with the consequence stated: a screen reader picks its voice and pronunciation rules from lang. English read by a Hindi voice is not slightly wrong, it is unintelligible — and the reverse is equally true. On a page mixing English and Devanagari, marking the spans matters.

Page titles

<title>Delivery areas — Sharma Kirana</title>

The <title> is the first thing a screen reader announces on page load, and it is how somebody with twelve tabs open tells them apart. Specific part first, as module 1 said. Every page needs its own.

Check your work

How a screen reader user navigates. By pulling up a list of headings, landmarks or links — not by listening from the top.

What your headings actually are. That menu.

What a <div class="title"> is worth. Nothing — it is not in the menu.

The heading test. Read the headings alone: do they summarise the page?

Why never skip a level. A listener wonders what they missed.

The landmark to get right. <main>, because "skip to main content" targets it.

When <header> is not a landmark. Inside an <article> or <section>.

How to label two navs. aria-label with the distinguishing word only — not "Main navigation", or it is announced twice.

Why aria-labelledby beats aria-label. It reuses visible text, so it cannot drift and it gets translated.

What a list announcement gives you. The item count and your position in it.

What reading order follows. The DOM, not the CSS — so fix the HTML, not the layout.

What lang changes. The voice and pronunciation rules.

What is announced first on page load. The <title>.

Practice

  1. Run the heading-outline console snippet on your own page. Read the output aloud and judge whether it summarises the page.
  2. Run it on three real websites. Most have problems; name them.
  3. Skip a heading level deliberately and look at the indentation in the output.
  4. Find a <div> styled as a heading on any site and confirm it does not appear in the outline.
  5. Open the devtools Accessibility tree for your page and find every landmark.
  6. Remove <main> and try "skip to main content" navigation in a screen reader.
  7. Put two <nav> elements on a page with no labels, then list landmarks in a screen reader. Add aria-label to each and list again.
  8. Write aria-label="Main navigation" and listen to it. Then just "Main".
  9. Replace an aria-label with aria-labelledby pointing at a visible h2.
  10. Convert a stack of <div>s to a <ul> and listen to both.
  11. Reorder a Flexbox row visually with order, then tab through it and watch the focus ring.
  12. Put <aside> before <main> in the HTML, keep the visual order with grid-template-areas, then navigate with a screen reader.
  13. Set lang="hi" on an English page and listen to a sentence.
  14. Give two pages the same <title> and try to tell them apart from the tab strip.

Official documentation

Next: the keyboard, which is the test that catches the most.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship