Web accessibility (a11y) is often treated as an afterthought or a compliance checkbox. In practice, it’s a set of development habits that improve the experience for all users, not just those using assistive technology.
Semantic HTML Is 80% of the Work
Most accessibility problems come from using the wrong HTML elements:
<!-- Bad: div soup with click handlers -->
<div class="button" onclick="submit()">Submit</div>
<!-- Good: actual button -->
<button type="submit">Submit</button>
The <button> element gives you keyboard support, focus management, screen reader announcements, and proper semantics for free. The <div> gives you none of that.
Use the right elements:
<nav>for navigation<main>for primary content<aside>for complementary content<header>and<footer>for landmarks<h1>through<h6>in order (don’t skip levels)<ul>/<ol>for lists<table>for tabular data (not layout)
Keyboard Navigation
Every interactive element must be reachable and operable via keyboard:
- Tab moves focus between interactive elements
- Enter/Space activates buttons and links
- Escape closes modals and popups
- Arrow keys navigate within components (tabs, menus, dropdowns)
Test by unplugging your mouse. If you can’t complete a flow, keyboard users can’t either.
/* Don't remove focus indicators */
:focus-visible {
outline: 2px solid var(--color-accent);
outline-offset: 2px;
}
Images and Alt Text
Every <img> needs an alt attribute. The content depends on context:
<!-- Informative image: describe what it shows -->
<img src="chart.png" alt="Revenue grew 40% from Q1 to Q4 2025" />
<!-- Decorative image: empty alt -->
<img src="divider.svg" alt="" />
<!-- Image as link: describe the destination -->
<a href="/profile">
<img src="avatar.jpg" alt="Your profile" />
</a>
Forms
Label every input. Provide clear error messages:
<div class="field">
<label for="email">Email address</label>
<input
type="email"
id="email"
name="email"
aria-describedby="email-error"
aria-invalid="true"
/>
<p id="email-error" class="error" role="alert">
Please enter a valid email address.
</p>
</div>
- Connect labels to inputs with
for/id - Use
aria-describedbyfor supplementary text - Use
aria-invalidto indicate error state - Use
role="alert"for live error messages
Color and Contrast
Text must have a contrast ratio of at least 4.5:1 against its background (3:1 for large text). Don’t rely on color alone to convey information:
/* Bad: only color indicates error */
.error { color: red; }
/* Better: color plus icon */
.error {
color: red;
&::before { content: "⚠ "; }
}
Testing
- Automated checks with axe-core or Lighthouse catch ~30% of issues
- Keyboard testing catches navigation and focus problems
- Screen reader testing (VoiceOver, NVDA) catches announcement and reading order issues
- Manual review catches logical issues no tool can find
Accessibility is not a feature you add. It’s a quality of the code you write.