Semantic HTML Basics
Semantic HTML means using elements for what they are, not just for how they look.
A <button> is not just a rectangle, it's an interactive control
that browsers and screen readers understand natively. A <nav>
is not just a container, it's a landmark that lets users jump directly to
navigation without reading through everything above it.
Choosing the right element is, pound for pound, the single most effective accessibility improvement you can make. It provides keyboard behavior, screen reader announcements, and structural meaning, all without a line of JavaScript or a single ARIA attribute.
Document Structure Baseline
Every HTML page should begin with this skeleton, not as a rigid template, but as a checklist of things that must be present for assistive technologies to work correctly.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Page Name | Site Name</title>
</head>
<body>
<a href="#main-content" class="skip-link">Skip to main content</a>
<header>
<nav aria-label="Main">...</nav>
</header>
<main id="main-content" tabindex="-1">
<h1>Page Title</h1>
...
</main>
<footer>...</footer>
</body>
</html>
lang="en"on the<html>element- Required by WCAG SC 3.1.1. Tells screen readers which language engine and pronunciation rules to use. Without it, text-to-speech may mispronounce every word on the page.
- A descriptive
<title> - Required by WCAG SC 2.4.2. It should describe the current page, be unique across the site, and follow the format "Page Name, Site Name." Screen readers announce the title when a page loads, it's often the first (and sometimes only) thing a user hears.
- A skip navigation link
- Required by WCAG SC 2.4.1. It allows keyboard users to jump past repeated navigation and reach the main content directly. Place it as the very first focusable element in the body. It can be visually hidden until focused.
Landmark Regions
HTML5 sectioning elements create landmark regions. Screen reader users navigate by jumping between landmarks the way sighted users scan a page visually. If your page has no landmarks, navigation by landmark is impossible, users must read through everything linearly.
| Element | Landmark role | When to use |
|---|---|---|
<header> (child of body) |
banner | Site-wide header, logo, primary navigation |
<nav> |
navigation | Navigation links; label with aria-label if multiple |
<main> |
main | Primary page content; only one per page |
<aside> |
complementary | Related but non-essential content (sidebars, callouts) |
<footer> (child of body) |
contentinfo | Site-wide footer |
<section> with accessible name |
region | Named section; use aria-labelledby pointing to a heading |
When you have more than one <nav>, give each a unique label
so users can tell them apart:
<nav aria-label="Main">...</nav>
<nav aria-label="Breadcrumb">...</nav>
<nav aria-label="Pagination">...</nav>
Heading Hierarchy
Headings create the document outline. Screen reader users routinely navigate
by heading, jumping from one <h2> to the next to get
a sense of the page structure, the same way a sighted user might scan
section titles.
The rules are simpler than they sound:
-
Use a single
<h1>that describes the current page (not just the site name). -
Nest heading levels logically,
<h2>for major sections,<h3>for subsections within those, and so on. -
Don't choose heading levels for their visual size. If you want a visually
smaller heading, use CSS. Don't reach for
<h4>because it happens to look smaller. - Heading text must be descriptive. "Section 3" tells nobody anything. "Contact Information" tells them exactly where they are.
Not a failure, but a bad pattern: Jumping from <h1>
directly to <h3> without a structural reason creates a confusing
outline. WCAG does not explicitly forbid it, but it creates a jagged outline
that's hard to navigate.
Links vs. Buttons
This one causes more confusion than almost anything else in accessible HTML. The rule is short:
-
Use
<a href="...">for navigation: going somewhere, changing the URL, downloading a file. -
Use
<button>for actions: submitting a form, opening a modal, toggling something, deleting a record.
The distinction matters because assistive technologies announce them differently, and users have different expectations of each. A screen reader user pressing Enter on a link expects to navigate. Pressing Space on a button expects an action. Swapping the two breaks those expectations.
<!-- Navigation: use a link -->
<a href="/about">About us</a>
<!-- Action: use a button -->
<button type="button">Open menu</button>
<button type="submit">Send message</button>
<!-- Never do this -->
<a href="#" onclick="openModal()">Open</a> <!-- wrong element -->
<div onclick="navigate()">Go</div> <!-- missing role, keyboard, name -->
Lists
Lists are underused. Any group of related items, a set of features, a series of steps, a menu of options, should be marked up as a list. Screen readers announce the number of items and let users jump through them efficiently.
<!-- Unordered: items without sequence -->
<ul>
<li>Keyboard navigation</li>
<li>Color contrast</li>
<li>Alternative text</li>
</ul>
<!-- Ordered: items with sequence -->
<ol>
<li>Run automated tests</li>
<li>Test with a keyboard alone</li>
<li>Test with a screen reader</li>
</ol>
<!-- Description list: term-definition pairs -->
<dl>
<dt>WCAG</dt>
<dd>Web Content Accessibility Guidelines</dd>
<dt>ARIA</dt>
<dd>Accessible Rich Internet Applications</dd>
</dl>
One caveat: if you remove list styling with CSS (list-style: none),
some screen readers (notably Safari with VoiceOver) stop announcing list semantics.
If the list semantics matter, add role="list" to the <ul>
or <ol> to restore them.
Native Elements vs. Custom Widgets
The best ARIA is often no ARIA at all. Every time you build a custom widget
out of <div>s and JavaScript, you take on the responsibility
of recreating everything a native element gives you for free:
- An accessible name
- A role that assistive technologies understand
- Keyboard interaction (Tab, Enter, Space, Arrow keys as appropriate)
- State changes announced to screen readers (expanded, checked, selected, etc.)
| You need | Use this | Not this |
|---|---|---|
| A clickable button | <button> |
<div onclick> |
| A text input | <input type="text"> |
<div contenteditable> |
| A checkbox | <input type="checkbox"> |
<div role="checkbox"> (unless unavoidable) |
| A dropdown selector | <select> |
Custom select (only if design truly requires it) |
| A navigation link | <a href="..."> |
<span onclick="navigate()"> |
If you must build a custom interactive widget, ARIA can help, but only if applied correctly. The ARIA Authoring Practices Guide documents the required keyboard interactions and ARIA patterns for common widgets like menus, dialogs, tabs, and comboboxes.