HTML Accessibility
Why accessibility matters
Web accessibility (often abbreviated a11y) means making websites usable by everyone, including people with visual, motor, hearing, or cognitive disabilities. Over 1 billion people worldwide live with some form of disability.
But accessibility isn't just about disabilities. It helps everyone: someone using a slow connection, a person browsing on a phone in bright sunlight, or a power user who navigates entirely by keyboard.
Semantic HTML is the foundation
The single most impactful thing you can do for accessibility is use semantic HTML. Screen readers and assistive technologies rely on the meaning behind elements.
HTML
<!-- Bad: a div pretending to be a button -->
<div onclick="doSomething()">Click me</div>
<!-- Good: an actual button -->
<button onclick="doSomething()">Click me</button>The real
<button> is focusable, responds to Enter/Space keys, and is announced as "button" by screen readers. The <div> does none of that without significant extra work.Headings create structure
Screen reader users often navigate by jumping between headings. Use them in order — don't skip levels:
HTML
<!-- Good -->
<h1>Page Title</h1>
<h2>Section</h2>
<h3>Subsection</h3>
<!-- Bad — skipped from h1 to h3 -->
<h1>Page Title</h1>
<h3>Section</h3>Only use one
<h1> per page.Labels for form fields
Every form input must have a label. Without one, screen readers can't tell the user what to type:
HTML
<!-- Good: label linked to input -->
<label for="email">Email address</label>
<input type="email" id="email" name="email" />
<!-- Bad: no label -->
<input type="email" placeholder="Email" />Placeholders are not labels — they disappear when you start typing.
ARIA attributes
ARIA (Accessible Rich Internet Applications) is a set of attributes that describe roles, states, and properties to assistive technology when native HTML cannot. Think of ARIA as a vocabulary for filling gaps — not a replacement for semantic markup.
HTML
<!-- Tell screen readers this div is a navigation -->
<div role="navigation" aria-label="Main menu">
<a href="/">Home</a>
<a href="/about">About</a>
</div>But the first rule of ARIA is: don't use ARIA if native HTML does the job. This is better:
HTML
<nav aria-label="Main menu">
<a href="/">Home</a>
<a href="/about">About</a>
</nav>Every interactive control needs an accessible name — usually visible text, a
<label>, or aria-label. Every control also needs its role (button, link, checkbox) and state (expanded, checked, disabled) exposed correctly. When you build custom widgets with <div> and JavaScript, you must recreate keyboard support and ARIA state updates that a native <button> or <select> provides for free.Common ARIA attributes
| Attribute | Purpose |
|---|---|
aria-label | Provides an accessible name |
aria-labelledby | Points to another element that labels this one |
aria-hidden="true" | Hides decorative elements from screen readers |
aria-live="polite" | Announces dynamic content changes |
aria-expanded | Indicates if a collapsible section is open |
role | Defines what an element is (button, alert, dialog, etc.) |
Use
aria-live="polite" or aria-live="assertive" on regions that update without a full page reload — toast notifications, shopping cart totals, or form validation summaries. Polite waits for the user to finish speaking; assertive interrupts, so reserve it for urgent errors.Keyboard navigation
Many users navigate with the keyboard instead of a mouse. Ensure:
- All interactive elements are focusable — links, buttons, and inputs are by default. Custom widgets need
tabindex="0". - Focus order makes sense — don't rearrange with CSS in ways that break tab order.
- Focus is visible — never remove the focus outline without providing an alternative.
CSS
/* Bad — removes focus indicator */
*:focus { outline: none; }
/* Good — custom focus style */
*:focus-visible {
outline: 2px solid #2563eb;
outline-offset: 2px;
}Color and contrast
Text must have sufficient contrast against its background. The WCAG standard requires:
- 4.5:1 ratio for normal text
- 3:1 ratio for large text (18px+ or 14px+ bold)
HTML
<!-- Bad: only color indicates error -->
<input style="border-color: red" />
<!-- Good: color + text + icon -->
<input style="border-color: red" aria-invalid="true" />
<span role="alert">Please enter a valid email.</span>Alt text for images
We covered this in the images lesson, but it's worth repeating:
- Descriptive for informational images:
alt="Bar chart showing sales increased 20% in Q1" - Empty for decorative images:
alt="" - Never use
alt="image"oralt="photo"
Skip links
For keyboard users, add a hidden link that lets them skip past navigation:
HTML
<a href="#main-content" class="skip-link">Skip to main content</a>
<nav><!-- long navigation --></nav>
<main id="main-content">
<!-- page content -->
</main>CSS
.skip-link {
position: absolute;
top: -100%;
left: 0;
}
.skip-link:focus {
top: 0;
z-index: 1000;
background: #fff;
padding: 8px 16px;
}Place the skip link as the first focusable element in the document body so pressing Tab once reveals it. Large sites sometimes add multiple skip targets — "Skip to search," "Skip to footer" — but one link to
<main> covers the most common need. The target element must have a matching id and should be unique on the page. Skip links benefit sighted keyboard users too, not only screen reader users.Testing accessibility as you build
You do not need expensive tools to catch obvious issues. Tab through your page without a mouse — can you reach every control and see where focus is? Use your browser's accessibility inspector to check heading order and contrast ratios. Free screen readers like NVDA (Windows) or VoiceOver (macOS, built in) reveal how announcements sound in practice. Fix semantic HTML problems first; they are the cheapest wins with the widest impact.
Key takeaway
Accessibility isn't an afterthought — it's a quality standard. Use semantic HTML, label all form fields, ensure keyboard navigation works, maintain color contrast, and add ARIA only when HTML falls short. These practices also improve SEO and general usability.