// Angular: mover foco después de una acción
@ViewChild('notification') notificationRef!: ElementRef;
public onSave(): void {
this.saveData();
// Mover foco al mensaje de confirmación
this.notificationRef.nativeElement.focus();
}
// React: mover foco
const headingRef = useRef<HTMLHeadingElement>(null);
useEffect(() => {
// Después de navegar, mover foco al título de la página
headingRef.current?.focus();
}, []);
return <h1 ref={headingRef} tabIndex={-1}>Nueva página</h1>;
// tabIndex={-1} permite foco programático pero no con Tab
Orden de tab correcto
<!-- tabIndex values -->
<!-- tabIndex="0": incluir en orden natural de Tab -->
<!-- tabIndex="-1": focusable programáticamente, no con Tab -->
<!-- tabIndex > 0: NUNCA usar — rompe el orden natural -->
<!-- Skip link (primer elemento de la página) -->
<a href="#main-content" class="skip-link">
Saltar al contenido principal
</a>
<nav>...</nav>
<main id="main-content" tabindex="-1">
<!-- Contenido principal -->
</main>
<!-- aria-live anuncia cambios al screen reader -->
<!-- Notificaciones importantes (interrumpe lo que esté leyendo) -->
<div role="alert" aria-live="assertive">
Error: el formulario tiene campos inválidos
</div>
<!-- Notificaciones informativas (espera a que termine de leer) -->
<div aria-live="polite">
3 resultados encontrados
</div>
<!-- Status bar -->
<div role="status" aria-live="polite">
Guardando...
</div>
// Angular: servicio para notificaciones accesibles
@Injectable({ providedIn: 'root' })
export class AriaNotificationService {
private readonly liveRegion: HTMLElement;
public constructor() {
this.liveRegion = document.createElement('div');
this.liveRegion.setAttribute('aria-live', 'polite');
this.liveRegion.setAttribute('role', 'status');
this.liveRegion.classList.add('sr-only'); // Visually hidden
document.body.appendChild(this.liveRegion);
}
public announce(message: string): void {
this.liveRegion.textContent = '';
// Timeout para que el screen reader detecte el cambio
setTimeout(() => {
this.liveRegion.textContent = message;
}, 100);
}
}
Parte 6 — Imágenes y media
<!-- Imagen informativa: alt descriptivo -->
<img src="chart.png" alt="Gráfico de ventas: 50% incremento en Q4 2024">
<!-- Imagen decorativa: alt vacío (screen reader la ignora) -->
<img src="decorative-line.png" alt="">
<!-- Ícono con significado -->
<button aria-label="Eliminar usuario">
<svg aria-hidden="true"><!-- ícono de basura --></svg>
</button>
<!-- Video con subtítulos -->
<video controls>
<source src="video.mp4" type="video/mp4">
<track kind="captions" src="captions.vtt" srclang="es" label="Español" default>
</video>
Parte 7 — Contraste y color
Requisitos WCAG AA:
- Texto normal (< 18px): ratio mínimo 4.5:1
- Texto grande (>= 18px bold o >= 24px): ratio mínimo 3:1
- Componentes UI y gráficos: ratio mínimo 3:1
Herramientas:
- Chrome DevTools → Accessibility panel
- axe DevTools extension
- Lighthouse accessibility audit
/* No depender SOLO del color para transmitir información */
/* MAL: solo color rojo para error */
.error { color: red; }
/* BIEN: color + ícono + texto */
.error {
color: #d32f2f;
border-left: 3px solid #d32f2f;
}
.error::before {
content: "⚠ "; /* Indicador visual además del color */
}
Reglas obligatorias
HTML semántico primero. ARIA es el complemento, no el reemplazo.
Toda imagen informativa tiene alt. Decorativas: alt="".
Todo input tiene <label> asociado. placeholder NO es label.
Errores de formulario con role="alert" y aria-describedby.
Navegable 100% con teclado. Sin trampas de foco.
Skip link como primer elemento de la página.
Contraste mínimo 4.5:1 para texto normal (WCAG AA).
No depender solo del color para transmitir información.
aria-live para contenido dinámico (notificaciones, loading, resultados).
Testear con screen reader (NVDA en Windows, VoiceOver en Mac).
Checklist
HTML semántico (header, nav, main, section, article, footer)
Landmarks con aria-label descriptivos
Inputs con labels asociados (for/id)
Errores de formulario accesibles (role="alert", aria-describedby)
Skip link implementado
Focus management en navegación SPA
Contraste >= 4.5:1 verificado
Imágenes con alt text apropiado
Navegación por teclado completa
Live regions para contenido dinámico
Testeado con Lighthouse accessibility (score >= 90)