Como optimizar la accesibilidad con Claude Code
Aprende a optimizar la accesibilidad con Claude Code. Incluye ejemplos de codigo practicos y guia paso a paso.
Ventajas de usar Claude Code para la accesibilidad
La accesibilidad web es importante, pero comprender los criterios WCAG y el uso correcto de los atributos ARIA es complejo. Claude Code puede generar codigo eficientemente siguiendo las mejores practicas de accesibilidad y auditar el codigo existente.
Auditoria de accesibilidad del codigo existente
> Revisa todos los componentes en src/components/ desde la perspectiva de accesibilidad.
> Verifica segun los criterios WCAG 2.1 AA y lista los problemas con sus soluciones propuestas.
Claude Code detecta problemas como los siguientes:
- Imagenes sin atributo alt
- Elementos de formulario sin etiqueta label asociada
- Ratio de contraste de color insuficiente
- Elementos interactivos no accesibles por teclado
- Atributos ARIA utilizados incorrectamente
Generacion de componentes accesibles
Dialogo modal
> Crea un dialogo modal accesible.
> Implementa trampa de foco, cierre con tecla ESC y bloqueo de scroll del fondo.
import { useEffect, useRef, useCallback } from 'react';
interface ModalProps {
isOpen: boolean;
onClose: () => void;
title: string;
children: React.ReactNode;
}
function Modal({ isOpen, onClose, title, children }: ModalProps) {
const modalRef = useRef<HTMLDivElement>(null);
const previousFocus = useRef<HTMLElement | null>(null);
// Trampa de foco
const trapFocus = useCallback((e: KeyboardEvent) => {
if (!modalRef.current) return;
const focusable = modalRef.current.querySelectorAll<HTMLElement>(
'a[href], button:not([disabled]), input:not([disabled]), select, textarea, [tabindex]:not([tabindex="-1"])'
);
const first = focusable[0];
const last = focusable[focusable.length - 1];
if (e.key === 'Tab') {
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
if (e.key === 'Escape') onClose();
}, [onClose]);
useEffect(() => {
if (isOpen) {
previousFocus.current = document.activeElement as HTMLElement;
document.body.style.overflow = 'hidden';
document.addEventListener('keydown', trapFocus);
// Enfocar el primer elemento enfocable dentro del modal
setTimeout(() => {
modalRef.current?.querySelector<HTMLElement>('[autofocus], button')?.focus();
}, 0);
}
return () => {
document.body.style.overflow = '';
document.removeEventListener('keydown', trapFocus);
previousFocus.current?.focus();
};
}, [isOpen, trapFocus]);
if (!isOpen) return null;
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center"
role="presentation"
>
<div
className="fixed inset-0 bg-black/50"
aria-hidden="true"
onClick={onClose}
/>
<div
ref={modalRef}
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
className="relative z-10 mx-4 w-full max-w-lg rounded-lg bg-white p-6 shadow-xl dark:bg-gray-800"
>
<h2 id="modal-title" className="text-xl font-bold mb-4">
{title}
</h2>
{children}
<button
onClick={onClose}
aria-label="Cerrar"
className="absolute right-4 top-4 rounded-full p-1 hover:bg-gray-100 dark:hover:bg-gray-700"
>
<svg aria-hidden="true" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" />
</svg>
</button>
</div>
</div>
);
}
Menu desplegable accesible
function DropdownMenu({ label, items }: { label: string; items: MenuItem[] }) {
const [isOpen, setIsOpen] = useState(false);
const [activeIndex, setActiveIndex] = useState(-1);
const menuRef = useRef<HTMLUListElement>(null);
const handleKeyDown = (e: React.KeyboardEvent) => {
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
setActiveIndex(prev => Math.min(prev + 1, items.length - 1));
break;
case 'ArrowUp':
e.preventDefault();
setActiveIndex(prev => Math.max(prev - 1, 0));
break;
case 'Enter':
case ' ':
e.preventDefault();
if (activeIndex >= 0) items[activeIndex].onClick();
setIsOpen(false);
break;
case 'Escape':
setIsOpen(false);
break;
}
};
return (
<div className="relative" onKeyDown={handleKeyDown}>
<button
aria-haspopup="true"
aria-expanded={isOpen}
onClick={() => setIsOpen(!isOpen)}
>
{label}
</button>
{isOpen && (
<ul ref={menuRef} role="menu" className="absolute mt-1 rounded-md border bg-white shadow-lg dark:bg-gray-800">
{items.map((item, i) => (
<li
key={i}
role="menuitem"
tabIndex={-1}
className={`cursor-pointer px-4 py-2 ${i === activeIndex ? 'bg-blue-100 dark:bg-blue-900' : ''}`}
onClick={item.onClick}
>
{item.label}
</li>
))}
</ul>
)}
</div>
);
}
Accesibilidad de formularios
> Reconstruye el formulario de contacto de forma accesible.
> Haz que los errores de validacion sean comunicados a los lectores de pantalla.
function ContactForm() {
const [errors, setErrors] = useState<Record<string, string>>({});
return (
<form aria-label="Formulario de contacto" noValidate>
<div className="mb-4">
<label htmlFor="name" className="block font-medium mb-1">
Nombre <span aria-label="obligatorio">*</span>
</label>
<input
id="name"
type="text"
required
aria-required="true"
aria-invalid={!!errors.name}
aria-describedby={errors.name ? 'name-error' : undefined}
className="w-full rounded border p-2"
/>
{errors.name && (
<p id="name-error" role="alert" className="mt-1 text-sm text-red-600">
{errors.name}
</p>
)}
</div>
<div className="mb-4">
<label htmlFor="email" className="block font-medium mb-1">
Correo electronico <span aria-label="obligatorio">*</span>
</label>
<input
id="email"
type="email"
required
aria-required="true"
aria-invalid={!!errors.email}
aria-describedby="email-hint email-error"
className="w-full rounded border p-2"
/>
<p id="email-hint" className="mt-1 text-xs text-gray-500">
ej., [email protected]
</p>
{errors.email && (
<p id="email-error" role="alert" className="mt-1 text-sm text-red-600">
{errors.email}
</p>
)}
</div>
<button type="submit" className="rounded bg-blue-600 px-4 py-2 text-white">
Enviar
</button>
</form>
);
}
Introduccion de pruebas automatizadas
> Anade pruebas de accesibilidad usando jest-axe.
import { render } from '@testing-library/react';
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
describe('Modal accessibility', () => {
it('should have no accessibility violations', async () => {
const { container } = render(
<Modal isOpen={true} onClose={() => {}} title="Test Modal">
<p>Content</p>
</Modal>
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
Summary
Con Claude Code puedes generar eficientemente componentes accesibles conforme a los criterios WCAG y auditar el codigo existente. Tambien es efectivo configurar la ejecucion automatica de pruebas de accesibilidad con la funcionalidad de hooks. Para mas detalles, consulta la guia de hooks. Los consejos para incorporarlo en tu flujo de desarrollo diario se presentan en Tips para triplicar tu productividad.
Para mas detalles sobre Claude Code, consulta la documentacion oficial de Anthropic. Para los detalles de las directrices WCAG, consulta W3C WCAG 2.1.
PDF gratuito: Hoja de trucos de Claude Code en 5 minutos
Solo deja tu correo y te enviaremos al instante la hoja de trucos en una página A4.
Cuidamos tus datos personales y nunca enviamos spam.
Sobre el autor
Masa
Ingeniero apasionado por Claude Code. Dirige claudecode-lab.com, un medio tecnológico en 10 idiomas con más de 2.000 páginas.
Artículos relacionados
7 plantillas de CLAUDE.md para Claude Code que puedes copiar en proyectos reales
Siete plantillas prácticas de CLAUDE.md para apps individuales, sitios de contenido, APIs, equipos y repos legacy, con errores que debes evitar.
Guia de Approval y Sandbox para Claude Code | Configuracion segura para el trabajo diario
Como dividir acciones de Claude Code en allow, ask, deny y sandbox con settings practicos, hooks y casos reales.
Guía completa para empezar con Claude Code 2026 | De cero a usarlo en tu trabajo real en 7 pasos
La guía definitiva para quienes usan Claude Code por primera vez. Desde la instalación hasta integrarlo en tu flujo de desarrollo real — con todos los tropiezos que Masa tuvo al comenzar.