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.

HTML elements and their landmark roles
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:

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.


This one causes more confusion than almost anything else in accessible HTML. The rule is short:

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:

Prefer native HTML elements over custom widgets
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.