Volver al Blog
UX/UI Design2 de abril de 202610 min de lectura

Accesibilidad WCAG: Lo Mínimo Que Todo Diseñador Necesita Saber

DR

Doug Ribeiro

10 min de lectura

La accesibilidad es uno de esos temas donde la brecha entre lo que los equipos dicen y lo que despliegan es vergonzosamente amplia. He estado en ambos lados de esa brecha. Al principio de mi carrera desplegué interfaces que fallaban verificaciones básicas de contraste. Con el tiempo, liderando el rediseño en Strongest Families Institute y luego entregando features que cumplen con accesibilidad en BMO, hice de la accesibilidad una parte no negociable de mi propio trabajo, en los diseños que entrego y en el código que escribo. Este post es el mínimo destilado al que me exijo y que impulso en cada equipo al que me uno.

Por Qué WCAG 2.2 AA Es Tu Línea Base, No Tu Techo

WCAG significa Web Content Accessibility Guidelines, y la línea base a la que apuntar es el nivel AA de la versión 2.2. Se convirtió en Recomendación del W3C en octubre de 2023 y reemplaza a la 2.1, que ahora es el objetivo heredado. El nivel A cubre las fallas más críticas. El nivel AAA es aspiracional y frecuentemente impráctico para aplicaciones complejas. AA está en el medio: alcanzable con disciplina, significativo para usuarios reales, y legalmente relevante en Canadá. Las organizaciones bajo regulación federal como los bancos caen bajo la Accessible Canada Act y la Canadian Human Rights Act. Ontario agrega la AODA, cuyo reglamento de accesibilidad todavía apunta a WCAG 2.0 AA, y otras provincias tienen sus propias leyes. Un empleador bajo regulación federal que opera en Ontario puede estar bajo más de una de estas a la vez. Construir para 2.2 AA las cubre todas.

Si tu proyecto se definió bajo 2.1, la brecha es pequeña y vale la pena cerrarla. WCAG 2.2 agrega cuatro criterios AA: 2.4.11 Focus Not Obscured (Minimum), 2.5.7 Dragging Movements, 2.5.8 Target Size (Minimum) a 24x24 píxeles CSS, y 3.3.8 Accessible Authentication (Minimum). También elimina 4.1.1 Parsing, que ya no aplica. Haz una revisión de brechas contra esos cinco puntos y estás al día.

En banca, el cumplimiento AA no es opcional. La Accessible Canada Act pone a las organizaciones bajo regulación federal en la obligación de identificar, eliminar y prevenir barreras, así que la accesibilidad se trata como una categoría de riesgo junto con seguridad y privacidad de datos, y los features que construí en BMO tenían que cumplir con accesibilidad. Ese encuadre es útil en cualquier lugar: una vez que dejas de presentar la accesibilidad como un bien moral y empiezas a presentarla como mitigación de riesgo, la conversación con el negocio se vuelve mucho más corta.

Contraste de Color: La Falla Más Común

WCAG AA requiere una relación de contraste de al menos 4.5:1 para texto normal y 3:1 para texto grande (18pt regular o 14pt bold). Estas ratios suenan técnicas pero la regla es simple: si tu texto gris sobre blanco se ve sutil y elegante, probablemente falla.

Dos herramientas que vale la pena tener: el WebAIM Contrast Checker para cualquier cosa donde puedas pegar un par de hex, y un plugin de verificación de contraste dentro de Figma para que el chequeo ocurra antes del handoff. En Figma, ejecuto verificaciones de contraste antes de que cualquier diseño vaya a dev. En Strongest Families, establecí el design system para el rediseño de la plataforma IRIS, y el contraste fue una de las primeras restricciones incorporadas: cada par de colores se verificó contra AA antes de convertirse en parte de la paleta, lo que también mantuvo la paleta pequeña. Un beneficio colateral que mejoró la consistencia en todo el producto.

  • Texto normal (menor a 18pt regular / 14pt bold): mínimo ratio 4.5:1
  • Texto grande: mínimo ratio 3:1
  • Componentes de UI y objetos gráficos: mínimo 3:1 contra colores adyacentes
  • Elementos decorativos sin valor informativo: sin requisito
  • Texto placeholder en inputs: también debe cumplir 4.5:1, frecuentemente olvidado
  • Elementos en estado deshabilitado: explícitamente excluidos de requisitos de contraste, pero usar con moderación

Navegación por Teclado: Cada Elemento Interactivo

Cada elemento interactivo en tu UI debe ser alcanzable y operable usando solo un teclado. Esto significa links, botones, inputs de formulario, modales, dropdowns, date pickers, componentes select personalizados, todo. El orden de tabulación debe ser lógico y coincidir con el orden visual de lectura.

El modo de falla que veo más frecuentemente en aplicaciones Angular son componentes personalizados que tragan eventos de teclado. Un desarrollador construye un dropdown hermoso usando divs y click handlers. Funciona perfectamente con mouse. Con teclado es completamente invisible. Trato la navegación por teclado como parte de los criterios de aceptación de cada componente interactivo que construyo, lo diga el ticket o no. Si no está en los AC, no se testea, y si no se testea, no se despliega bien.

typescript
// Bad: div-based button with no keyboard support
// <div class="btn" (click)="submit()">Submit</div>

// Good: native button or explicit keyboard handler
// <button type="button" (click)="submit()">Submit</button>

// If you must use a non-semantic element:
@Component({
  selector: 'app-custom-button',
  template: `
    <div
      class="custom-btn"
      role="button"
      [attr.tabindex]="disabled ? -1 : 0"
      [attr.aria-disabled]="disabled"
      (click)="handleAction()"
      (keydown.enter)="handleAction()"
      (keydown.space)="handleAction()">
      <ng-content></ng-content>
    </div>
  `
})
export class CustomButtonComponent {
  @Input() disabled = false;

  handleAction(): void {
    if (!this.disabled) {
      // emit action
    }
  }
}

Mi preferencia fuerte es siempre usar elementos HTML nativos: button, a, input, select. Vienen con soporte de teclado, gestión de foco y semántica ARIA integrados. Cada componente personalizado que construyes para reemplazar uno nativo es una deuda que tendrás que pagar en atributos ARIA y event handlers.

Gestión del Foco: La Parte Que Todos Olvidan

La navegación por teclado trata sobre alcanzar elementos. La gestión del foco trata sobre a dónde va el foco después de una acción. Esta distinción importa enormemente en aplicaciones de una sola página.

Considera un diálogo modal. Un usuario presiona Enter en un botón trigger. El modal se abre. ¿Dónde está el foco? Si no lo gestionas, el foco se queda en el botón trigger detrás del modal. El usuario tabula hacia adelante y aterriza en elementos del fondo que ahora están visualmente ocultos. Esto es una falla del criterio de éxito 2.4.3 Focus Order, que exige que el orden del foco preserve el significado y la operabilidad. Y también es una experiencia genuinamente terrible para usuarios de lectores de pantalla.

  • Cuando un modal se abre: mover el foco al contenedor del modal o a su primer elemento interactivo
  • Cuando un modal se cierra: devolver el foco al elemento que lo activó
  • Al navegar entre rutas: mover el foco al encabezado de contenido principal o al contenedor de la página
  • Al mostrar errores inline después del envío del formulario: mover el foco al primer error o a un resumen
  • Al expandir un acordeón: el foco puede quedarse en el trigger; no lo muevas a menos que se cargue contenido dinámicamente
  • Atrapar el foco dentro de modales: el usuario no debería poder tabular fuera de un modal abierto hacia el contenido de fondo

Antes de escribir todo esto por tu cuenta, mira @angular/cdk/a11y. Trae cdkTrapFocus, FocusTrapFactory, FocusMonitor y LiveAnnouncer, y en un proyecto Angular ya tienes el CDK en disco. Escribe un focus trap a mano sólo cuando necesites un comportamiento que la directiva no cubre. En cualquier caso, fíjate en la guarda de lista vacía de abajo. Si el contenedor no tiene nada enfocable, first y last son undefined y llamar focus() sobre ellos lanza un error.

typescript
// Focus trap service for modals in Angular
import { Injectable, ElementRef } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class FocusTrapService {
  private focusableSelectors = [
    'a[href]',
    'button:not([disabled])',
    'input:not([disabled])',
    'select:not([disabled])',
    'textarea:not([disabled])',
    '[tabindex]:not([tabindex="-1"])'
  ].join(', ');

  getFocusableElements(container: HTMLElement): HTMLElement[] {
    return Array.from(
      container.querySelectorAll<HTMLElement>(this.focusableSelectors)
    ).filter(el => !el.closest('[hidden]'));
  }

  trapFocus(container: HTMLElement, event: KeyboardEvent): void {
    if (event.key !== 'Tab') {
      return;
    }

    const focusable = this.getFocusableElements(container);
    if (focusable.length === 0) {
      return;
    }

    const first = focusable[0];
    const last = focusable[focusable.length - 1];

    if (event.shiftKey && document.activeElement === first) {
      event.preventDefault();
      last.focus();
    } else if (!event.shiftKey && document.activeElement === last) {
      event.preventDefault();
      first.focus();
    }
  }
}

ARIA Labels: Úsalos Bien o No Los Uses

ARIA, Accessible Rich Internet Applications, es un conjunto de atributos HTML que comunican significado semántico a tecnologías asistivas. La regla más importante sobre ARIA es la primera regla de ARIA: no uses ARIA si hay un elemento HTML nativo que hace el trabajo. role='button' en un div es siempre peor que un elemento button.

Dicho esto, hay casos legítimos donde ARIA es necesario: widgets complejos, contenido dinámico, componentes personalizados. Estos son los atributos que uso con más frecuencia y lo que cada uno hace.

  • aria-label: proporciona un label de texto para un elemento cuando el texto visible está ausente o es insuficiente; usar en botones de solo ícono
  • aria-labelledby: apunta al ID de otro elemento cuyo texto sirve como label; usar para fieldsets de formularios y diálogos
  • aria-describedby: apunta a texto descriptivo adicional; usar para texto de ayuda de inputs y mensajes de error
  • aria-expanded: comunica el estado abierto/cerrado de acordeones, dropdowns y menús
  • aria-live: marca una región que recibirá actualizaciones dinámicas; los lectores de pantalla anunciarán cambios automáticamente
  • aria-hidden: oculta elementos decorativos del árbol de accesibilidad; usar en íconos dentro de botones con label
  • role: sobrescribe el rol ARIA implícito; usar con moderación, solo cuando el elemento semántico realmente no está disponible
typescript
// Practical ARIA in Angular templates

// Icon-only button
<button type="button" aria-label="Close notification">
  <app-icon name="close" aria-hidden="true"></app-icon>
</button>

// Input with visible label and helper text
<div class="field">
  <label id="email-label" for="email">Email address</label>
  <input
    id="email"
    type="email"
    aria-labelledby="email-label"
    aria-describedby="email-hint email-error"
    [attr.aria-invalid]="emailControl.invalid && emailControl.touched" />
  <span id="email-hint">We will never share your email.</span>
  <span id="email-error" role="alert" *ngIf="emailControl.invalid && emailControl.touched">
    Please enter a valid email address.
  </span>
</div>

// Expandable panel
<button
  [id]="'btn-' + id"
  [attr.aria-expanded]="isOpen"
  [attr.aria-controls]="'panel-' + id"
  (click)="toggle()">
  {{ title }}
</button>
<div
  [id]="'panel-' + id"
  [hidden]="!isOpen"
  role="region"
  [attr.aria-labelledby]="'btn-' + id">
  <ng-content></ng-content>
</div>

Testing con Lectores de Pantalla: No Puedes Saltarte Este Paso

Las herramientas automatizadas como Axe, Lighthouse e IBM Equal Access Checker detectan mucho, pero no todo. El estudio de Deque de 2021 sobre sus propias auditorías encontró que el testing automatizado con axe detectaba alrededor del 57% de los problemas por volumen (la cifra más antigua del 20 al 30% se refiere a la cobertura de criterios de éxito de WCAG, no al volumen de issues). El resto requiere testing manual. El test manual más valioso es el testing con lector de pantalla. Realmente apagar tu monitor e intentar completar una tarea usando solo audio.

Mi configuración de testing usa NVDA en Windows con Firefox y VoiceOver en macOS con Safari. Ambas están entre las cinco combinaciones más comunes de la última encuesta de lectores de pantalla publicada por WebAIM, aunque NVDA ahora se usa con Chrome alrededor del doble de veces que con Firefox. Antes de que un feature mío se despliegue, hago un recorrido con lector de pantalla. No es una auditoría completa, pero captura las fallas obvias antes de que lleguen a producción.

  • NVDA + Firefox (Windows): gratuito, ampliamente usado, esencial para audiencias empresariales con predominio de Windows
  • VoiceOver + Safari (macOS/iOS): integrado en dispositivos Apple, crítico para testing mobile
  • TalkBack + Chrome (Android): esencial para testing mobile en Android
  • JAWS + Chrome o Edge (Windows): lector de pantalla empresarial costoso, común en banca y gobierno

Un ejemplo típico: un formulario de múltiples pasos muestra 'Paso 1 de 5' visualmente, pero VoiceOver lee solo 'Formulario.' El indicador de progreso está construido enteramente con clases CSS, sin markup semántico. Poner un aria-label en un contenedor común no lo arregla, porque los lectores de pantalla no anuncian de forma confiable los labels en elementos genéricos. Lo que funciona es texto visualmente oculto que anuncia el paso actual, más aria-current='step' en el ítem activo de la lista de pasos. Ese es el tipo de problema que ninguna herramienta automatizada va a capturar.

Lo Que Reviso en Cada Etapa

Reviso la accesibilidad en tres etapas: revisión de diseño antes del handoff a dev, revisión de componentes durante desarrollo, y una pasada pre-release. En Strongest Families las tres eran mías, porque yo diseñaba y hacía el handoff. En BMO estaba del lado del desarrollo, trabajando con el design system del banco, que era de otro equipo, así que las etapas que aplicaba eran la revisión de componentes y la pasada pre-release sobre mis propios features. El checklist de la etapa de diseño cubre lo siguiente.

  • Todas las ratios de contraste de texto y elementos interactivos verificadas y documentadas en Figma
  • Estados de foco visibles y estilizados para cada elemento interactivo, no solo el outline predeterminado del navegador
  • Los estados de error incluyen tanto color como un ícono o texto, nunca solo color
  • Los inputs de formulario tienen labels visibles. El placeholder text solo falla el SC 3.3.2 Labels or Instructions, y también el SC 1.3.1 cuando no hay ningún label programático
  • Cualquier contenido transmitido solo por color tiene un indicador secundario
  • Los targets de toque cumplen el SC 2.5.8 Target Size (Minimum), 24x24 píxeles CSS, que es la regla AA. En mobile diseño a 44x44, que es el SC 2.5.5 en AAA y coincide con las guías de plataforma de Apple y Material
  • Los modales y diálogos tienen un mecanismo de cierre explícito y un encabezado
  • Las imágenes tienen alt text especificado en el diseño; las imágenes decorativas están explícitamente marcadas como decorativas
  • Las tablas de datos tienen encabezados de columna y fila identificados
  • El movimiento y la animación tienen una consideración de movimiento reducido
Revisión de diseñoRatios de contraste, visibilidad del estado de foco, claridad de los la… 1 Revisión de componentes (dev)Navegación por teclado, roles y labels ARIA, pasada con lector de panta… 2 Auditoría previa al lanzamientoChecklist WCAG completo, escaneo automatizado (axe-core), pasada con Vo… 3

El Business Case Es Más Fácil de Lo Que Piensas

Cada vez que he tenido que hacer el caso para inversión en accesibilidad he usado los mismos tres argumentos. Primero, riesgo legal: en Canadá y la mayoría de las jurisdicciones occidentales, no proporcionar servicios digitales accesibles a los clientes de un banco es accionable. Segundo, tamaño de mercado: cerca del 16% de la población mundial, 1.300 millones de personas o 1 de cada 6, vive con una discapacidad significativa según la OMS, y el diseño accesible mejora la usabilidad para todos los usuarios, no solo los que tienen discapacidades. Tercero, el efecto rampa: las funcionalidades construidas para accesibilidad, como subtítulos, control por voz y atajos de teclado, son usadas intensamente por personas sin discapacidades.

La accesibilidad no es un feature que agregas al final. Es un atributo de calidad que construyes desde el inicio, como el rendimiento o la seguridad. Si la estás retrofitteando, ya estás pagando la penalidad.

Lo mínimo que necesitas saber realmente no es tanto. Aprende los cuatro principios WCAG: Perceptible, Operable, Comprensible, Robusto. Pasa tus diseños por un verificador de contraste antes del handoff. Testea con teclado antes de cada release. Testea con lector de pantalla antes de cada lanzamiento de feature importante. Agrega ARIA solo donde HTML nativo es insuficiente. Ese es el mínimo. Empieza ahí y construye hacia arriba.

Referencias

AccessibilityWCAGUX/UIFront-End