Axe-core como un corrector ortográfico para la accesibilidad
Una librería de 200 KB que corre en cada PR y atrapa el 60% de problemas de a11y antes de mergear. Esto es lo que sí captura, lo que no, y por qué importa de todas formas.
La accesibilidad sigue siendo el gran ausente del workflow de muchos equipos. La conocemos. Sabemos que importa. Y aun así, el lunes pasa, sale un commit con un input sin label, un botón sin aria-label, un contraste de 3.1:1, y nadie lo nota hasta que un usuario con lector de pantalla escribe.
El cambio en Agentikas vino el día que tratamos la accesibilidad como un problema de tooling, no de cultura. Si un linter te marca let mal escrito, también puede marcarte un <img> sin alt. La librería que hace esto se llama axe-core, y la corremos en cada PR.
Qué es axe-core, en una frase
Una librería de Deque Systems, open source desde hace una década, que toma una página renderizada y devuelve una lista estructurada de violaciones de WCAG. Tres categorías por severidad — critical, serious, moderate — con la regla violada, el selector CSS exacto del nodo problemático, y un enlace a la documentación de cómo arreglarlo.
{
"violations": [
{
"id": "image-alt",
"impact": "critical",
"description": "Images must have alternate text",
"nodes": [
{ "target": ["#hero-image"], "html": "<img src=\"hero.jpg\">" }
],
"helpUrl": "https://dequeuniversity.com/rules/axe/4.10/image-alt"
}
]
}
Es como un linter, pero para HTML accesible. Lo conectas, lo ejecutas, lees la salida, arreglas.
Cómo lo integramos en CI
Axe-core tiene tres formas de ejecución, según el contexto:
- En tests E2E con Playwright (lo que usamos): el navegador real renderiza la página, axe la analiza después de hidratarse, y el test falla si hay violaciones critical/serious.
- En tests unitarios con jsdom: rápido pero menos fiel — algunos problemas (contraste de color computado) no se detectan sin un navegador.
- En el editor con la extensión axe DevTools: para auditoría manual durante desarrollo.
Nuestra configuración mínima en Playwright:
import { test, expect } from "@playwright/test";
import AxeBuilder from "@axe-core/playwright";
test("home page has no accessibility violations", async ({ page }) => {
await page.goto("/");
const results = await new AxeBuilder({ page })
.withTags(["wcag2a", "wcag2aa"])
.analyze();
const critical = results.violations.filter(v =>
v.impact === "critical" || v.impact === "serious"
);
expect(critical).toEqual([]);
});
Las dos cosas importantes: filtrar por wcag2a y wcag2aa (los niveles realistas — AAA es estricto y casi nadie lo cumple), y bloquear solo en critical/serious. Las violaciones moderate y minor se reportan pero no rompen el build, porque incluyen advertencias subjetivas que generan ruido si las haces blockers.
Lo que sí captura
Axe-core captura, sin discusión, una lista concreta y valiosa:
- Imágenes sin texto alternativo — incluyendo
<img>sinalty SVGs sinaria-label - Inputs sin label asociado — incluyendo casos sutiles como
placeholderusado como label - Contraste de color insuficiente — calcula el ratio computed y lo compara con WCAG AA
- Botones sin texto accesible — un
<button>con solo un icono SVG y sinaria-label - Encabezados saltados — un
<h3>sin<h2>previo en la sección - Roles ARIA inválidos o redundantes
- Páginas sin
<html lang>— uno de los más comunes y de impacto crítico para lectores de pantalla - Tablas con encabezados sin
scope
Esta es la parte aburrida y mecánica de la accesibilidad. Es donde los humanos se aburren, se cansan, y se les escapa. Es exactamente donde una herramienta determinista brilla.
Lo que no captura
Axe es honesto sobre sus límites — la propia documentación dice que captura un 30-50% de problemas reales de accesibilidad. El otro 50-70% requiere juicio humano:
- Si el alt text es bueno o malo. Axe ve que
altexiste; no ve si dice "image" en vez de describir la imagen. - Si el orden del foco es lógico. La jerarquía del DOM puede estar bien y la experiencia con teclado seguir siendo confusa.
- Si una animación causa malestar a alguien con sensibilidad vestibular. Existe
prefers-reduced-motion, pero usarlo bien va más allá de un check. - Si un componente custom es navegable con teclado. Axe ve los atributos ARIA; no prueba el comportamiento real.
- Si un mensaje de error es comprensible. Que un input tenga
aria-invalides bueno; que el mensaje diga "el formato no es válido" es inútil.
El objetivo no es que axe sustituya el juicio. Es que libere al juicio para concentrarse en lo no-mecánico. Si axe hace el 50% gratis, el otro 50% deja de ser intimidante.
Por qué tratarlo como spell-check
El cambio mental que hicimos: dejar de tratar la accesibilidad como un proyecto trimestral ("nuestra auditoría de a11y") y tratarla como una propiedad continua del código, igual que el linting o los tests. La metáfora exacta: spell-check.
Nadie escribe un documento Word y luego "pasa el corrector ortográfico" como un proyecto. Lo tienes activo todo el tiempo. Cuando aparece un subrayado rojo, decides — corregir, ignorar, añadir al diccionario. Pero la decisión llega a tiempo, no tres meses después de publicar.
Axe en CI es eso. Cada PR pasa el "corrector". Cuando hay una violación, decides en el momento — la arreglas, le pones un aria-label, declaras una excepción justificada en el código. La accesibilidad deja de ser una deuda y pasa a ser una propiedad del commit.
El coste real de empezar
El argumento típico es "no tenemos tiempo para arreglar todo lo que axe va a marcar". Es razonable como miedo, falso como hecho.
Cuando lo añadimos a Agentikas, la primera ejecución encontró 23 violaciones, 8 critical. Las arreglamos en un día. Las críticas eran obvias — un <img> sin alt en el hero, un selector de idioma sin aria-label, un modal sin role="dialog". Cosas que tardas más en escribir esta lista que en arreglar.
Desde entonces, cada PR tiene cero violaciones critical o serious — porque no se mergean si las hay. La deuda no se acumula.
Axe-core es open source y está en github.com/dequelabs/axe-core. Lo integramos en Agentikas vía @axe-core/playwright. La configuración exacta del CI está en github.com/agentikas/agentikas-blog — fórkala.
Comentarios
Cargando comentarios…
Inicia sesión en tu dashboard para participar.