Why web accessibility matters

Web accessibility (a11y) is not an extra or a "nice to have." It is a fundamental requirement that benefits all your users:

  • 1 in 5 people has some form of disability
  • Users with temporary disabilities (broken arm, eye infection)
  • Users in situational contexts (sun on the screen, hands occupied)
  • Older users with reduced vision or mobility
  • Improves SEO and overall usability

Additionally, in many countries web accessibility is a legal requirement. The European Accessibility Act takes effect in June 2025, and the ADA in the United States has already generated thousands of lawsuits over inaccessible websites.

WCAG 2.2: the four principles

The Web Content Accessibility Guidelines are organized around four fundamental principles, known as POUR:

1. Perceivable

Content must be perceivable through all available senses.

2. Operable

The interface must be operable by any user with any input device.

3. Understandable

The content and interface must be understandable.

4. Robust

Content must be interpretable by current and future assistive technologies.

Complete checklist by category

Structure and semantics

  • Use a single <main> per page
  • Headings in hierarchical order without gaps (h1 -> h2 -> h3)
  • Correct landmarks: <header>, <nav>, <main>, <aside>, <footer>
  • Semantic lists for groups of items (<ul>, <ol>, <dl>)
  • Tables with <caption>, <thead>, <th scope="col|row">
  • Document language declared: <html lang="es">
  • Language changes marked: <span lang="en">Responsive design</span>

Images and multimedia

  • All informative images have a descriptive alt
  • Decorative images use alt="" or aria-hidden="true"
  • Complex graphics have a long description with aria-describedby
  • Videos have subtitles
  • Audio has a transcript available
  • Animations can be paused or disabled
  • Respect prefers-reduced-motion:
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

Keyboard navigation

  • All interactive elements are reachable with Tab
  • Tab order is logical and predictable
  • Focus is visible at all times (:focus-visible)
  • There are no keyboard traps (user can exit with Tab/Escape)
  • A "Skip to content" link exists
  • Modals trap focus correctly (focus trapping)
  • Escape closes modals and popups
  • Keyboard shortcuts do not interfere with the browser

Forms

  • Each input has a <label> associated with for/id
  • Input groups use <fieldset> and <legend>
  • Required fields have aria-required="true" or required
  • Errors are announced with aria-describedby and aria-invalid
  • Autocomplete uses autocomplete with correct values
  • Format instructions are visible, not only as placeholder
<div class="form-group">
  <label for="email">Correo electronico</label>
  <input
    id="email"
    type="email"
    required
    autocomplete="email"
    aria-describedby="email-help email-error"
  />
  <p id="email-help" class="hint">Ejemplo: [email protected]</p>
  <p id="email-error" class="error" role="alert" aria-live="assertive">
    <!-- Mensaje de error dinámico -->
  </p>
</div>

Color and contrast

  • Minimum contrast ratio 4.5:1 for normal text (AA)
  • Minimum contrast ratio 3:1 for large text (24px+ or 19px+ bold)
  • Minimum contrast ratio 3:1 for UI components and graphics
  • Information is not conveyed by color alone
  • Works correctly in high contrast mode

Verify contrast programmatically

// Calcular ratio de contraste entre dos colores
function contrastRatio(luminance1, luminance2) {
  const lighter = Math.max(luminance1, luminance2);
  const darker = Math.min(luminance1, luminance2);
  return (lighter + 0.05) / (darker + 0.05);
}

function relativeLuminance(r, g, b) {
  const [rs, gs, bs] = [r, g, b].map(c => {
    c = c / 255;
    return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
  });
  return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
}

Dynamic content and ARIA

  • Dynamic changes are announced with aria-live
  • Loading states use aria-busy="true"
  • Tabs use roles tablist, tab, tabpanel
  • Accordions use aria-expanded
  • Menus use roles menu, menuitem
  • Tooltips are linked with aria-describedby
  • Toggle buttons use aria-pressed
<!-- Tabs accesibles -->
<div role="tablist" aria-label="Secciones del curso">
  <button role="tab"
          id="tab-1"
          aria-selected="true"
          aria-controls="panel-1">
    Leccion 1
  </button>
  <button role="tab"
          id="tab-2"
          aria-selected="false"
          aria-controls="panel-2"
          tabindex="-1">
    Leccion 2
  </button>
</div>

<div role="tabpanel"
     id="panel-1"
     aria-labelledby="tab-1">
  Contenido de la leccion 1...
</div>

<div role="tabpanel"
     id="panel-2"
     aria-labelledby="tab-2"
     hidden>
  Contenido de la leccion 2...
</div>

Focus management in SPAs

In Single Page Applications (SPA), focus management is critical:

// Angular: mover el foco al contenido principal despues de navegar
import { Router, NavigationEnd } from '@angular/router';
import { inject } from '@angular/core';

export class AppComponent {
  private router = inject(Router);

  constructor() {
    this.router.events.subscribe(event => {
      if (event instanceof NavigationEnd) {
        const main = document.querySelector('main');
        if (main) {
          main.setAttribute('tabindex', '-1');
          main.focus();
        }
      }
    });
  }
}

Testing tools

Automated

Tool Type Coverage
axe DevTools Browser extension ~57% of WCAG issues
Lighthouse Chrome DevTools General audit
pa11y CLI / CI Automation
eslint-plugin-jsx-a11y Linter Prevention in code
jest-axe Unit testing Automated tests

Essential manual testing

Automated tools only detect 30-50% of accessibility issues. Manual testing is mandatory:

  1. Navigate with keyboard only: Tab, Shift+Tab, Enter, Space, Escape, arrows
  2. Use a screen reader: NVDA (Windows), VoiceOver (Mac/iOS), TalkBack (Android)
  3. Verify at 200% zoom: everything must be functional and readable
  4. Test without colors: use a color blindness simulation extension
  5. Verify in high contrast mode: Windows High Contrast Mode

The 10 most common mistakes

  1. Images without alt attribute
  2. Forms without associated labels
  3. Insufficient contrast
  4. No visible focus indicator
  5. Generic links like "click here" or "read more" without context
  6. Content only accessible with mouse (hover)
  7. Modals without focus trapping
  8. No skip link
  9. Videos without subtitles
  10. Incorrect use of ARIA (worse than not using ARIA)

Conclusion

Web accessibility is not a phase of development; it is a design philosophy. Every decision, from HTML structure to colors and interactions, impacts the experience of millions of people. This checklist is a solid starting point, but true accessibility comes from empathizing with your users and continuously testing with real people and assistive technologies.