Web Accessibility (A11Y): Building Inclusive Websites

Introduction

Accessibility makes websites usable for all, including people with disabilities. Learn key WCAG techniques and how to avoid common accessibility pitfalls.

Written At

2025-05-25

Updated At

2025-05-25

Reading time

4 minutes

Step 1: Semantic HTML & ARIA Roles

Why it matters: Screen readers rely on proper markup to interpret content.

What to do:

  1. Use native HTML elements (<button>, <nav>) over generic <div>s.
  2. Add ARIA attributes when semantics aren't enough:
    html
    <div role="alert" aria-live="assertive">
      Error message!
    </div>

Example:

A navigation menu with proper roles:

html
<nav aria-label="Main navigation">
  <ul>
    <li>
      <a href="#home" aria-current="page">
        Home 
      </a>
    </li>
  </ul>
 </nav>

Step 2: Keyboard Navigation & Focus Management

Why it matters: Many users rely on keyboards, not mice.

What to do:

  1. Ensure all interactive elements are focusable (use tabindex="0" for custom elements).
  2. Manage focus for modals and dialogs:
    javascript
    function Modal({ isOpen }) {
      const modalRef = useRef(null);
    
      useEffect(() => {
        if (isOpen) {
          modalRef.current.focus();
          trapFocus(modalRef.current);
        }
      }, [isOpen]);
    
      return (
        <div role="dialog" aria-modal="true" tabIndex="-1" ref={modalRef}>
          {/* Modal content */}
        </div>
      );
    }

Example:

A skip-to-content link implementation:

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

<!-- CSS to show on focus -->
<style>
  .skip-link {
    position: absolute;
    left: -999px
  }
  .skip-link:focus {
    left: 10px
  }
</style>

Step 3: Color Contrast & Text Readability

Why it matters: Low contrast excludes users with visual impairments.

What to do:

  1. Verify contrast ratios (minimum 4.5:1 for normal text, 3:1 for large text):
    css
    /* Good contrast example */
    .button {
      background-color: #0056b3; /* Dark blue */
      color: #ffffff; /* White */
      /* Contrast ratio: 7.43:1 */
    }
    
    /* Bad contrast example */
    .bad-button {
      background-color: #a0a0a0; /* Gray */
      color: #f0f0f0; /* Light gray */
      /* Contrast ratio: 1.53:1 */
    }
  2. Provide multiple ways to consume content:
    html
    <img src="chart.png" alt="Sales growth Q1 2023: 15% increase"
      aria-describedby="chart-desc">  
    
     <p id="chart-desc" class="visually-hidden">
        Bar chart showing 15% growth in Q1 compared to 10% in Q4 2022.
     <p>
    
    <a href="data.csv">Download raw data<p>

Example:

Accessible form with proper labeling:

html
<form>
  <div class="form-group">
    <label for="email">
      Email address
      <span aria-hidden="true">*</span>
      <span class="visually-hidden">required</span>
    </label>
    <input type="email" id="email" aria-required="true"
         aria-describedby="email-help">
    <small id="email-help">
      We'll never share your email.
    </small>
  </div>
</form>