LogoLogoLeo de Jesús
HomeAboutMy WorkContact
My Work
Development

Web Accessibility: A Developer's Guide

Learn essential accessibility techniques to create inclusive web experiences that work for everyone, including users with disabilities.

Leo de JesúsJanuary 28, 202414 min read
AccessibilityWeb StandardsInclusive Design

Web Accessibility: A Developer's Guide

Web accessibility ensures that websites and applications are usable by everyone, including people with disabilities. This comprehensive guide will help you understand and implement accessibility best practices in your projects.

Understanding Accessibility

What is Web Accessibility?

Web accessibility means that websites, tools, and technologies are designed and developed so that people with disabilities can use them. This includes:

  • Visual impairments: Blindness, low vision, color blindness
  • Motor impairments: Limited fine motor control, tremors
  • Cognitive impairments: Learning disabilities, attention disorders
  • Hearing impairments: Deafness, hard of hearing

The Business Case

Accessibility isn't just the right thing to do—it's good business:

  • Legal compliance: Avoid lawsuits and meet regulations
  • Broader audience: Reach more users and customers
  • Better SEO: Accessible sites often rank better
  • Improved UX: Better for all users, not just those with disabilities

Semantic HTML: The Foundation

Use Proper HTML Elements

Semantic HTML provides meaning and structure:

<!-- ✅ Good - semantic structure -->
<header>
  <nav aria-label="Main navigation">
    <ul>
      <li><a href="/">Home</a></li>
      <li><a href="/about">About</a></li>
    </ul>
  </nav>
</header>

<main>
  <article>
    <h1>Article Title</h1>
    <p>Article content...</p>
  </article>
</main>

<footer>
  <p>&copy; 2024 My Company</p>
</footer>

<!-- ❌ Bad - div soup -->
<div class="header">
  <div class="nav">
    <div class="nav-item">Home</div>
    <div class="nav-item">About</div>
  </div>
</div>

Heading Hierarchy

Create a logical heading structure:

<h1>Page Title</h1>
  <h2>Section Title</h2>
    <h3>Subsection Title</h3>
    <h3>Another Subsection</h3>
  <h2>Another Section</h2>
    <h3>Subsection</h3>

ARIA: Enhancing Semantics

ARIA Labels and Descriptions

Provide additional context for screen readers:

<!-- Button with descriptive label -->
<button aria-label="Close dialog">×</button>

<!-- Form field with description -->
<label for="password">Password</label>
<input 
  type="password" 
  id="password"
  aria-describedby="password-help"
  required
>
<div id="password-help">
  Password must be at least 8 characters long
</div>

<!-- Image with descriptive text -->
<img 
  src="chart.png" 
  alt="Sales increased by 25% in Q3 2024"
>

ARIA States and Properties

Convey dynamic state information:

<!-- Expandable section -->
<button 
  aria-expanded="false"
  aria-controls="content"
  onclick="toggleContent()"
>
  Toggle Content
</button>
<div id="content" aria-hidden="true">
  Hidden content...
</div>

<!-- Form validation -->
<input 
  type="email"
  aria-invalid="true"
  aria-describedby="email-error"
>
<div id="email-error" role="alert">
  Please enter a valid email address
</div>

Keyboard Navigation

Focus Management

Ensure all interactive elements are keyboard accessible:

/* Visible focus indicators */
:focus {
  outline: 2px solid #0066cc;
  outline-offset: 2px;
}

/* Custom focus styles */
.button:focus {
  box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.3);
}

Tab Order

Maintain logical tab order:

<!-- Logical tab sequence -->
<form>
  <label for="name">Name</label>
  <input type="text" id="name" tabindex="1">
  
  <label for="email">Email</label>
  <input type="email" id="email" tabindex="2">
  
  <button type="submit" tabindex="3">Submit</button>
</form>

Skip Links

Provide shortcuts for keyboard users:

<a href="#main-content" class="skip-link">
  Skip to main content
</a>

<nav>...</nav>

<main id="main-content">
  <!-- Main content -->
</main>
.skip-link {
  position: absolute;
  top: -40px;
  left: 6px;
  background: #000;
  color: #fff;
  padding: 8px;
  text-decoration: none;
  z-index: 1000;
}

.skip-link:focus {
  top: 6px;
}

Color and Contrast

Color Contrast Ratios

Ensure sufficient contrast between text and background:

  • Normal text: 4.5:1 contrast ratio
  • Large text: 3:1 contrast ratio
  • UI components: 3:1 contrast ratio
/* ✅ Good contrast */
.text-dark {
  color: #000000; /* Black text */
  background: #ffffff; /* White background */
  /* Contrast ratio: 21:1 */
}

.text-medium {
  color: #333333; /* Dark gray */
  background: #ffffff; /* White background */
  /* Contrast ratio: 12.6:1 */
}

/* ❌ Poor contrast */
.text-poor {
  color: #999999; /* Light gray */
  background: #ffffff; /* White background */
  /* Contrast ratio: 2.8:1 - Too low! */
}

Don't Rely on Color Alone

Provide additional visual cues:

<!-- ✅ Good - multiple indicators -->
<button class="success">
  <span class="icon" aria-hidden="true">✓</span>
  Success
</button>

<!-- ❌ Bad - color only -->
<button style="color: green;">Success</button>

Forms and Inputs

Proper Form Labels

Associate labels with form controls:

<!-- ✅ Good - explicit association -->
<label for="username">Username</label>
<input type="text" id="username" name="username">

<!-- ✅ Good - implicit association -->
<label>
  Email
  <input type="email" name="email">
</label>

<!-- ❌ Bad - no association -->
<div>Username</div>
<input type="text" name="username">

Form Validation

Provide clear error messages:

<form>
  <div class="form-group">
    <label for="email">Email Address</label>
    <input 
      type="email" 
      id="email"
      name="email"
      aria-describedby="email-error"
      aria-invalid="true"
      required
    >
    <div id="email-error" class="error-message" role="alert">
      Please enter a valid email address
    </div>
  </div>
</form>

Required Fields

Clearly indicate required fields:

<label for="name">
  Name <span class="required" aria-label="required">*</span>
</label>
<input type="text" id="name" required>

Images and Media

Alt Text

Provide meaningful alternative text:

<!-- ✅ Good - descriptive alt text -->
<img src="chart.png" alt="Sales increased by 25% in Q3 2024">

<!-- ✅ Good - decorative image -->
<img src="decoration.png" alt="" role="presentation">

<!-- ❌ Bad - generic alt text -->
<img src="chart.png" alt="chart">

Complex Images

Use long descriptions for complex content:

<img 
  src="infographic.png" 
  alt="Infographic showing company growth"
  longdesc="#infographic-description"
>

<div id="infographic-description">
  <h3>Company Growth Infographic</h3>
  <p>This infographic shows our company's growth over the past five years...</p>
</div>

Video and Audio

Provide captions and transcripts:

<video controls>
  <source src="video.mp4" type="video/mp4">
  <track 
    kind="captions" 
    src="captions.vtt" 
    srclang="en" 
    label="English"
  >
  <p>Your browser doesn't support video. 
     <a href="transcript.txt">Read the transcript</a>
  </p>
</video>

Testing and Validation

Automated Testing Tools

Use tools to catch accessibility issues:

# Install axe-core for automated testing
npm install --save-dev @axe-core/react

# Install Lighthouse CLI
npm install -g lighthouse
// React component with axe testing
import { useEffect } from 'react'
import axe from '@axe-core/react'

function App() {
  useEffect(() => {
    axe(React, ReactDOM, 1000)
  }, [])

  return (
    <div>
      {/* Your app content */}
    </div>
  )
}

Manual Testing

Test with real assistive technologies:

  1. Screen readers: NVDA (Windows), JAWS (Windows), VoiceOver (Mac)
  2. Keyboard only: Navigate without mouse
  3. Voice control: Dragon NaturallySpeaking, Voice Control
  4. Zoom: Test at 200% zoom level

Browser DevTools

Use built-in accessibility features:

  • Chrome DevTools: Accessibility panel
  • Firefox: Accessibility inspector
  • Safari: Web Inspector accessibility features

Common Patterns

Modal Dialogs

Make modals accessible:

function Modal({ isOpen, onClose, children }) {
  useEffect(() => {
    if (isOpen) {
      // Trap focus
      const focusableElements = modalRef.current?.querySelectorAll(
        'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
      )
      
      if (focusableElements?.length) {
        focusableElements[0].focus()
      }
    }
  }, [isOpen])

  if (!isOpen) return null

  return (
    <div 
      className="modal-overlay"
      onClick={onClose}
      role="dialog"
      aria-modal="true"
      aria-labelledby="modal-title"
    >
      <div 
        className="modal-content"
        onClick={e => e.stopPropagation()}
        ref={modalRef}
      >
        <h2 id="modal-title">Modal Title</h2>
        {children}
        <button onClick={onClose}>Close</button>
      </div>
    </div>
  )
}

Data Tables

Make tables accessible:

<table>
  <caption>Monthly Sales Report</caption>
  <thead>
    <tr>
      <th scope="col">Month</th>
      <th scope="col">Sales</th>
      <th scope="col">Growth</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">January</th>
      <td>$10,000</td>
      <td>+5%</td>
    </tr>
    <tr>
      <th scope="row">February</th>
      <td>$12,000</td>
      <td>+20%</td>
    </tr>
  </tbody>
</table>

Conclusion

Web accessibility is not a one-time task but an ongoing commitment to inclusive design. By following these guidelines and continuously testing with real users, you can create web experiences that work for everyone.

Remember:

  • Start with semantic HTML
  • Test with real assistive technologies
  • Include users with disabilities in your design process
  • Make accessibility part of your development workflow
  • Keep learning and improving

The web should be accessible to everyone, and as developers, we have the power to make that happen.

Back to My Work
L3CHUGU1T4© 2026BlogContact