Semantic HTML and Accessibility
Long before screen readers, people built the web with plain HTML: headings, paragraphs, lists, links, tables. That design turned out to be a gift. Because each element says what it is, software can help people in ways the author never planned: reading aloud, jumping between sections, reformatting, translating, or listing every link.
When you use the right element, you get accessibility almost for free. When you pile up generic div and span elements and style them to look right, you throw that gift away. This lesson shows what you gain by choosing well, and what you lose by not.
What You Will Learnβ
- How people navigate by structure, rather than reading top to bottom
- Writing a clean heading outline
- Using landmarks to mark the regions of a page
- Using the right element for lists, tables, buttons, and links
- Adding a skip link
- Keeping the reading order and the visual order together
- Hiding text visually but not from screen readers
The Big Ideaβ
- HTML Code
- Structure Navigator
<body>
<a href="#main" class="skip-link">Skip to main content</a>
<header>
<p>The Bread Bin</p>
<nav aria-label="Main">
<ul>
<li><a href="/recipes">Recipes</a></li>
<li><a href="/shop">Shop</a></li>
</ul>
</nav>
</header>
<main id="main">
<h1>Sourdough for Beginners</h1>
<h2>Ingredients</h2>
<ul>
<li>Flour</li>
<li>Water</li>
<li>Salt</li>
</ul>
<h2>Method</h2>
<p>Mix, rest, shape, and bake.</p>
</main>
<footer>
<p>Β© 2026 The Bread Bin</p>
</footer>
</body>
Load each sample, and look at the three lists. These are the same lists that a screen reader can offer. A "div soup" page gives a reader nothing to jump between. The semantic page gives them a table of contents, a map of regions, and a list of links.
How People Really Navigateβ
Sighted people scan a page: they glance at the big text, spot the menu, and skip to what they want. Screen reader users do the same thing, but through the code. According to surveys of screen reader users, headings are the most common way to find information, followed by landmarks and links. They rarely listen to a page from top to bottom.
Common navigation shortcuts in screen readers:
| Shortcut idea | What it needs from your HTML |
|---|---|
| Jump to the next heading | Real h1 to h6 elements |
| Jump by heading level | Headings in a logical order |
| Jump to a landmark (menu, main, footer) | header, nav, main, aside, footer |
| List all links | Link text that makes sense alone |
| Jump to the next list, table, form field, button | The real elements |
If your page does not offer these things, a screen reader user has to listen to everything, in order, every time.
Headings: The Outline of Your Pageβ
Headings are the single most useful structure on a page.
<h1>Sourdough for Beginners</h1>
<h2>Ingredients</h2>
<h2>Method</h2>
<h3>Mixing</h3>
<h3>Shaping</h3>
<h2>Baking tips</h2>
Rules of thumb:
- One
h1, that names the page. (More than one is allowed by HTML, but one is clearer, and is the usual guidance.) - Do not skip levels. Go from
h2toh3, not fromh2toh4. - Choose the level for meaning, not for size. If a heading is too big, change it in CSS.
- Never use a heading just for styling. Do not make bold text a heading to make it look big, and do not use a
divto look like one. - Write headings that make sense alone, because people read them as a list.
- Do not leave a heading empty.
Read only the headings of your page, from top to bottom. Do they tell the story? If not, a reader jumping through them will be lost.
Landmarks: The Map of Your Pageβ
Landmarks mark the large regions of a page, so people can jump straight to them.
| HTML element | Landmark role | Use |
|---|---|---|
<header> (directly in body) | banner | The site-wide header: logo, site name |
<nav> | navigation | A group of navigation links |
<main> | main | The main content. One per page |
<aside> | complementary | Related content, such as a sidebar |
<footer> (directly in body) | contentinfo | The site-wide footer |
<section> with a name | region | A significant section that you have named |
<form> with a name | form | An important form |
<div role="search"> or <search> | search | A search area |
Important details:
- A
headerorfooterinside anarticleorsectionis not a page-level landmark. It belongs to that part. - If you have more than one
nav, give each one a label, so users can tell them apart:<nav aria-label="Main">,<nav aria-label="Footer">. - Do not label things with the landmark's own name. "Main navigation" is fine as a label on a
nav. Do not write "navigation" inside the label, because the screen reader already says it. - Put all page content inside a landmark, so nothing is left outside the map.
- Do not overdo it. A page with 20 landmarks is as hard to use as one with none.
For more on these elements and how to lay out a whole page, see Semantic Page Layout.
Skip Linksβ
On most sites, every page begins with the same menu. A sighted mouse user ignores it. A keyboard user must tab through every link before reaching the content, on every page. A skip link fixes that. It is the first link on the page, and it jumps over the repeated blocks.
<a href="#main" class="skip-link">Skip to main content</a>
...
<main id="main" tabindex="-1">...</main>
.skip-link {
position: absolute;
left: -9999px; /* hidden off screen until focused */
}
.skip-link:focus {
position: static; /* appears when a keyboard user reaches it */
padding: 0.5rem 1rem;
background: #fde68a;
color: #1e293b;
}
This is directly related to WCAG 2.4.1 (Bypass Blocks). Use the test below. First try it without the skip link, and count the stops. Then try again with it.
- HTML Code
- Skip Link Test
<body>
<a class="skip-link" href="#main">Skip to main content</a>
<nav aria-label="Main"> ...nine links... </nav>
<main id="main" tabindex="-1">
<h1>Welcome</h1>
<button type="button">Order bread</button>
</main>
</body>
Then press Tab repeatedly. When the skip link appears, press Enter.
The main content of the page starts here.
Focus log:
- (nothing yet)
Keep pressing Tab, or use the skip link. With a skip link, one key press takes a keyboard user past the menu.
This is a real focus test. The skip link is hidden off screen until it receives focus, which is the usual pattern. The demo moves focus in script, because a fragment link inside this documentation site could navigate away from the page. On a normal page, a plain fragment link works too.
The tabindex="-1" on main lets script and fragment links move focus to it, without adding it to the normal Tab order.
Lists, Tables, and Other Structureβ
| If your content is⦠| Use | Because |
|---|---|---|
| A set of related items | ul, ol | A screen reader announces "list, 5 items", and lets people jump or skip it |
| Steps in order | ol | The numbering is meaningful, and is announced |
| Terms and definitions | dl, dt, dd | The relationship is in the code |
| Data in rows and columns | table, with th | Headers are announced with each cell. See Accessible Tables |
| A group of related form fields | fieldset, legend | The group name is announced with each field |
| A quotation | blockquote, q | It is announced as a quotation |
| Emphasis | em, strong | They carry meaning. i and b are only visual |
| A date or time | time | Machines can read it |
| An abbreviation | abbr | A title can explain it |
Do not use a table for layout. Screen readers will announce data table structure that is not there. Use CSS grid or flexbox.
Do not use <br> to make lists or spacing. A row of lines and dashes is not a list. Use real list elements, and CSS for the gaps.
Buttons and Links: Pick the Right Oneβ
This is one of the most important decisions in HTML:
| It⦠| Use | Why |
|---|---|---|
| Goes to another page or place | <a href="β¦"> | Links work with the keyboard, open in new tabs, and are announced as links |
| Does something on this page (open, submit, toggle) | <button type="button"> | Works with Enter and Space, and is announced as a button |
If it looks like a button but takes you to another page, it can still be a link, styled like a button. If it opens a menu on the current page, it is a button. Mixing them up confuses both screen reader users and keyboard users. See Accessible Buttons and Accessible Links.
Reading Order and Visual Orderβ
Assistive technology reads your page in source order, the order of the HTML. CSS can move things around on the screen, using order, flex-direction: row-reverse, grid placement, or absolute positioning. If the visual order and the source order disagree, a keyboard user's focus can jump around the screen unpredictably, and a screen reader reads things in a confusing order.
Guidelines:
- Write your HTML in the order you want it read. Then use CSS to place it.
- Test by tabbing. The focus should move in a sensible visual path.
- Avoid positive
tabindexvalues (such astabindex="3"). They override the natural order, and are very hard to maintain. Use0or-1only. - Avoid reordering interactive content in ways that make a mismatch.
Visually Hidden Textβ
Sometimes you want text that screen readers can read, but sighted users do not need: for example, an extra word for an icon, or a heading for a region. Use a visually hidden class:
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0 0 0 0);
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
<a href="/cart">
<svg aria-hidden="true" focusable="false"> ... </svg>
<span class="visually-hidden">Shopping cart, 3 items</span>
</a>
Know the difference between ways of hiding:
| Method | Visible? | Read by screen readers? | In the Tab order? |
|---|---|---|---|
display: none, visibility: hidden, the hidden attribute | No | No | No |
aria-hidden="true" | Yes | No | Yes (still focusable!) |
.visually-hidden | No | Yes | Yes, if it is a control |
Be careful with aria-hidden on anything focusable. See the aria-hidden lesson.
The Page Language and Titleβ
Two small things at the top of the page have a big effect:
<html lang="en">
<head>
<title>Sourdough for Beginners | The Bread Bin</title>
</head>
The lang attribute lets a screen reader choose the right voice. The title is the first thing announced when a page loads. For more, see The HTML Element and The Title Element.
Good Habitsβ
- Choose the element that matches the meaning.
- One
h1, no skipped levels, and headings that tell the story. - Wrap content in landmarks, and label repeated ones.
- Add a skip link to every page that has a repeated menu.
- Use lists for lists and tables for tables.
- Use
buttonfor actions andafor navigation. - Keep source order the same as visual order.
- Use
divandspanonly when no meaningful element fits. - Test with the keyboard and a screen reader.
Accessibility Checklistβ
- The page has a
lang, and a unique, meaningful<title>. - There is a logical heading outline, with no skipped levels and no empty headings.
-
mainappears once, andheader,nav, andfooterare used where they fit. - Repeated landmarks, such as two
navelements, have different labels. - A skip link is the first focusable element.
- Lists use
ul,ol, anddl. Tables are used only for data. - Buttons are
button, and links areawith anhref. - Tab order follows the visual order, and no
tabindexis above 0. - Text meant only for assistive technology uses a visually hidden class.
- No heading is used only for its size.
Security and Privacy Notesβ
- Do not put sensitive data in visually hidden text, or in headings and landmark labels. Anyone using assistive technology, anyone reading the page source, and search engines can see it.
- Hidden text is also content. Do not use it to stuff keywords for search engines. It can be penalised, and it harms screen reader users.
- Be careful with user-generated headings. If visitors can post content, make sure they cannot inject their own
h1, landmark roles, oraria-hidden. Escape their text, and control which elements are allowed. - A skip link target should not be user-controlled. Build
href="#main"yourself, and avoid passing arbitrary fragments from the address. - Do not hide security warnings with
aria-hidden, or off-screen. Anything people need to see for safe use must be available to everyone.
Global Audience Notesβ
- Screen readers read the
langattribute, so a Hindi page set as English will be read with the wrong voice. Setlangon the page and on passages in other languages. - Heading and landmark labels should be in the page's language. An English
aria-labelon a Spanish page is read with an English or Spanish voice, and either is confusing. - Right-to-left pages need
dir="rtl". The reading order in the code should match the visual order in that direction. - Write short, plain headings that translate well, and that fit on small screens in any script.
- Many people use older phones and basic screen readers. Plain, semantic HTML works best on all of them.
- Do not use a symbol or emoji alone as a heading or label. Meanings differ across cultures.
Common Mistakes to Avoidβ
- Div soup: a page with no headings, landmarks, or lists.
- Skipping heading levels because of the size it gives.
- Using bold or a large font instead of a real heading.
- Several
mainelements, or none. - Unlabelled repeated
navelements. - No skip link on pages with long menus.
- A
divorspanfor a button, or anawith nohreffor an action. - Layout tables.
- Visual order that disagrees with the source order.
- Positive
tabindexvalues. aria-hiddenon content that can be focused.- Hiding with
display: nonecontent that screen readers need.
Practice Timeβ
Try these in your browser and your editor:
- Use the structure navigator, load the div soup sample, and fix it one problem at a time.
- Write the heading outline for your own page, then read only that list.
- Add a skip link to a page, and test it with the keyboard.
- Replace three
divelements withheader,main, andfooter. - Give two
navelements different labels. - Turn a list written with
<br>into a realul. - Find a
divthat acts as a button, and replace it with abutton. - Use CSS to reverse the visual order of two blocks, and then press Tab. What happens?
- Make an icon-only link accessible with a visually hidden label.
- Bonus: open a news site, and list the headings and landmarks it uses, with your browser's Accessibility panel.
Quick Recapβ
Click a question to check your answer.
1. Why does semantic HTML help accessibility?
Each semantic element says what it is, so browsers and assistive technology can announce it, let people jump between elements, and provide keyboard behaviour, without extra code.
2. What are the rules for a good heading outline?
One h1 that names the page, no skipped levels, levels chosen for meaning rather than size, and headings that make sense on their own.
3. What are landmarks?
Regions such as banner, navigation, main, complementary, and contentinfo, created by elements like header, nav, main, aside, and footer. They let users jump straight to a region.
4. What does a skip link do?
It is the first link on a page, and lets keyboard users jump over repeated content, such as a long menu, straight to the main content.
5. When do you use a button, and when a link?
A button does something on the current page. A link goes somewhere else, with an href.
6. How does aria-hidden="true" differ from display: none?
display: none hides the element from everyone, including screen readers, and removes it from the Tab order. aria-hidden hides it only from assistive technology, but a focusable element inside it can still be reached by keyboard, which is a trap.
Key Takeawaysβ
- People navigate by structure: headings, landmarks, links, lists, and form fields.
- Build a clean heading outline, with one
h1and no skipped levels. - Use landmarks to map the page, and label repeated ones.
- Add a skip link so keyboard users can bypass repeated menus.
- Use the right element: lists for lists, tables for data,
buttonfor actions,afor navigation. - Keep source order and visual order together, and avoid positive
tabindex. - Use a visually hidden class for text that is only for assistive technology, and know the difference from
aria-hiddenanddisplay: none. - Language and title are the first things a screen reader announces. Set both.
Next, hear how all this structure sounds with Screen Readers.