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-describedby for supplementary text
  • Use aria-invalid to 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

  1. Automated checks with axe-core or Lighthouse catch ~30% of issues
  2. Keyboard testing catches navigation and focus problems
  3. Screen reader testing (VoiceOver, NVDA) catches announcement and reading order issues
  4. 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.