HTML Accessibility
Why Accessibility Matters
Web accessibility means building pages that everyone can use — including people with visual, motor, hearing, or cognitive disabilities. Accessible HTML also improves SEO and is often a legal requirement.
Semantic HTML is the Foundation
Using the right HTML elements is the most important accessibility step. Screen readers understand semantic elements and announce them correctly.
<!-- BAD: div soup — screen readers can't understand structure -->
<div class="header">
<div class="nav"><div class="link">Home</div></div>
</div>
<div class="main">
<div class="heading">Welcome</div>
<div class="button" onclick="submit()">Submit</div>
</div>
<!-- GOOD: semantic HTML — screen readers announce roles correctly -->
<header>
<nav><a href="/">Home</a></nav>
</header>
<main>
<h1>Welcome</h1>
<button type="button" onclick="submit()">Submit</button>
</main>
Images — Alt Text
<!-- Informative image: describe what it shows -->
<img src="chart.png" alt="Bar chart showing 40% increase in sales Q4 2024">
<!-- Functional image (link/button): describe the action -->
<a href="/home">
<img src="logo.png" alt="Tutorials Logic — go to homepage">
</a>
<!-- Decorative image: empty alt so screen readers skip it -->
<img src="divider.png" alt="">
<!-- Complex image: use longdesc or nearby text -->
<figure>
<img src="complex-diagram.png" alt="System architecture diagram"
aria-describedby="diagram-desc">
<figcaption id="diagram-desc">
The system consists of three layers: presentation, business logic, and data.
</figcaption>
</figure>
Forms — Labels & ARIA
<!-- Always associate labels with inputs using for/id -->
<label for="email">Email Address</label>
<input type="email" id="email" name="email" required
aria-describedby="email-hint">
<span id="email-hint">We'll never share your email.</span>
<!-- Error message linked to input -->
<input type="text" id="username" aria-describedby="username-error"
aria-invalid="true">
<span id="username-error" role="alert">Username is required.</span>
<!-- Fieldset groups related inputs -->
<fieldset>
<legend>Preferred Contact Method</legend>
<input type="radio" id="by-email" name="contact" value="email">
<label for="by-email">Email</label>
<input type="radio" id="by-phone" name="contact" value="phone">
<label for="by-phone">Phone</label>
</fieldset>
ARIA Roles & Attributes
ARIA (Accessible Rich Internet Applications) attributes add accessibility information when HTML semantics aren't enough.
<!-- aria-label: provides a label when no visible text exists -->
<button aria-label="Close dialog"><i class="fas fa-times"></i></button>
<!-- aria-hidden: hide decorative elements from screen readers -->
<i class="fas fa-star" aria-hidden="true"></i>
<span>4.5 stars</span>
<!-- aria-expanded: toggle state for menus/accordions -->
<button aria-expanded="false" aria-controls="menu">Menu</button>
<ul id="menu" hidden>
<li><a href="/">Home</a></li>
</ul>
<!-- role="alert": announces dynamic content changes -->
<div role="alert" aria-live="polite">
Form submitted successfully!
</div>
<!-- Skip navigation link (keyboard users) -->
<a href="#main-content" class="skip-link">Skip to main content</a>
<main id="main-content">...</main>
Keyboard Navigation
<!-- tabindex="0": make non-interactive element focusable -->
<div tabindex="0" role="button" onclick="doSomething()"
onkeydown="if(event.key==='Enter') doSomething()">
Custom button
</div>
<!-- tabindex="-1": focusable via JS but not Tab key -->
<div id="modal" tabindex="-1">Modal content</div>
<!-- Visible focus styles (never remove outline without replacement) -->
<style>
:focus-visible {
outline: 3px solid #3498db;
outline-offset: 2px;
}
</style>
<!-- Use real buttons and links — they're keyboard accessible by default -->
<button type="button">This works with keyboard</button>
<a href="/page">This too</a>