Semantic HTML: structure a page so browsers, readers and search engines understand it
What the HTML landmark and sectioning elements mean, how to pick the right one, a complete page skeleton you can copy, and the mistakes that quietly break accessibility.
A page built out of nothing but <div> elements looks fine to a sighted visitor and tells everyone else nothing. A screen reader cannot offer to jump to the navigation, a search engine cannot tell the article from the sidebar, and a reader-mode button has to guess. Semantic HTML fixes that by using elements whose names mean something. This tutorial explains the handful of elements that matter, shows a full page skeleton, and lists the mistakes that come up most in code review.
What "semantic" buys you
Every HTML element has a role that the browser exposes to assistive technology through the accessibility tree. A <nav> has the role navigation; a <div> has no role at all. Screen readers such as NVDA, JAWS and VoiceOver build their "jump to landmark" and "list headings" menus from those roles, which is how an experienced user can skip past a 40-link header in one keystroke. Search engines use the same structure to decide which text is the main content. And the browser's own features, such as Firefox's reader view and the "find in page" heading outline in some browsers, rely on it too.
You get all of this for free by choosing the right element. There is no extra attribute to add and no script to run.
The landmark elements
These are the ones that map to landmarks, the big regions of a page. Use each at most once per page unless noted.
| Element | Role | Use it for |
|---|---|---|
<header> | banner (when it is not inside an article or section) | The site-wide top strip: logo, site name, main navigation. |
<nav> | navigation | A group of links for moving around the site or page. Several are allowed; label each with aria-label if there is more than one. |
<main> | main | The one thing the page is about. Exactly one per page. |
<aside> | complementary | Related but separable content: a sidebar, pull quote, "see also" box. |
<footer> | contentinfo (when not inside an article or section) | Copyright, legal links, contact details. |
<form role="search"> or <search> | search | The site search box. <search> is newer (2023); the role on a form works everywhere. |
The content elements
<article>: a self-contained piece that would make sense on its own, such as a blog post, a product card, a comment. If you could syndicate it in a feed, it is an article.<section>: a thematic grouping, almost always with its own heading. If you cannot think of a heading for it, it is probably a<div>.<h1>to<h6>: headings in order. One<h1>per page, then<h2>for the main sections,<h3>inside those. Do not skip levels to get a smaller font; use CSS for size.<p>,<ul>,<ol>,<dl>: prose and lists. A list of links in the nav is still a list.<figure>with<figcaption>: an image, chart or code sample with a caption that refers to it.<time datetime="2026-10-11">: a date or time the machine can read, whatever format humans see.<button>for actions,<a href>for navigation. A link that runs JavaScript and goes nowhere should be a button.
A complete skeleton
This is a minimal but complete page. Save it as index.html, open it in a browser, and use the accessibility panel in developer tools (Chrome: Elements, then the Accessibility tab) to see the landmarks it produces.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Garden notes: October</title>
<meta name="description" content="What went into the ground this month and what came out.">
</head>
<body>
<a href="#main" class="skip">Skip to content</a>
<header>
<a href="/">Garden notes</a>
<nav aria-label="Main">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/archive/">Archive</a></li>
<li><a href="/about.html">About</a></li>
</ul>
</nav>
</header>
<main id="main">
<article>
<header>
<h1>October: the last tomatoes</h1>
<p>Published <time datetime="2026-10-11">11 October 2026</time></p>
</header>
<section aria-labelledby="harvest">
<h2 id="harvest">Harvest</h2>
<p>Nine kilos of tomatoes, most of them green.</p>
<figure>
<img src="tomatoes.jpg" alt="A wooden crate of green and orange tomatoes" width="800" height="600">
<figcaption>The last crate before the frost.</figcaption>
</figure>
</section>
<section aria-labelledby="planting">
<h2 id="planting">Planting</h2>
<p>Garlic and broad beans went in on the 9th.</p>
</section>
</article>
<aside aria-labelledby="related">
<h2 id="related">Related notes</h2>
<ul><li><a href="/2026/09/">September</a></li></ul>
</aside>
</main>
<footer>
<p>© 2026 Garden notes. <a href="/contact.html">Contact</a></p>
</footer>
</body>
</html>Notice what is not in it: no wrapper <div> around everything (you can add one for layout later; it changes nothing semantically), no <br> for spacing, no headings used as bold text.
Headings are the outline
Screen-reader users navigate by heading more than by anything else. In NVDA, pressing H jumps to the next heading and 2 jumps to the next <h2>. That only works if the levels reflect structure. The rule is simple: the heading level says how deep in the outline the section is, not how big the text should be. If a card title should look small, keep it an <h3> and style it with CSS.
A related rule: every <section> and <article> should begin with a heading. If a region has no visible heading, either it is not a section, or give it one and hide it visually (see the accessibility checklist for the .visually-hidden pattern).
Common mistakes
Multiple <main> elements
Some templates put a <main> in every component. Only one may be visible at a time; a second one is an error and confuses the landmark list.
<section> as a styling hook
Wrapping a row of three cards in <section> so you can target it in CSS adds a sectioning element with no heading. Use a <div class="row"> instead. <div> is not bad; it is the right element when there is no meaning to convey.
Unlabelled repeated landmarks
Two <nav> elements (main menu and footer menu) both announce as "navigation". Add aria-label="Main" and aria-label="Footer" so they can be told apart.
Links that are really buttons
<a href="#" onclick="openMenu()"> announces as a link, appears in the links list, and scrolls to the top when JavaScript fails. Use <button type="button">. If you need it to look like a link, style the button.
Images without useful alt text
alt="" is correct for decorative images and tells screen readers to skip them. Omitting the attribute entirely makes the reader announce the file name. Descriptive alt text says what the image shows in context, not "image of".
Missing lang and title
<html lang="en"> picks the right pronunciation rules and hyphenation. <title> is the first thing a screen reader announces and the text in the browser tab and search results. Both are one line and frequently forgotten.
Checking your work
Three quick checks catch most problems:
- In Chrome developer tools, open the Accessibility tab on the
<body>element and read the tree. Each landmark should appear with a sensible name. - Install a headings outline extension or use the browser's Lighthouse accessibility audit; it flags skipped heading levels and missing alt attributes.
- Turn off CSS (Firefox: View, Page Style, No Style). The page should still read top to bottom in a sensible order.
Exercise
Take any page you have built, or the skeleton above, and rewrite it using only <div> and <span> with class names. Open both versions with a screen reader (VoiceOver is built into macOS and iOS; NVDA is free on Windows) and try to jump to the main content. Then put the semantic elements back, one at a time, and notice which keystrokes start working.
Summary
- Semantic elements give regions a role that assistive technology, search engines and browsers use. You get it for free by choosing the element.
- One
<main>, one<h1>, headings in order, labelled navigation, a skip link. <article>for self-contained pieces,<section>for headed groups,<div>for pure layout.- Buttons for actions, links for navigation,
alton every image.
Next: lay the skeleton out responsively with Flexbox and Grid.