React

Test di accessibilità con axe: cosa trova e cosa no

axe controlla un componente React dentro il test: trova ruoli mancanti ed etichette assenti, ma non giudica contrasto, ordine di tabulazione o lettura.

In sintesi

  • axe esegue regole sul DOM renderizzato e restituisce violazioni con un livello di impatto.
  • In jsdom la regola del contrasto non gira: nessun CSS, nessun layout, nessun pixel.
  • Il test boccia solo impact critical; le violazioni minori vanno in backlog, non in CI.

axe trova in un test le violazioni strutturali del DOM: ruoli mancanti, etichette assenti, gerarchia di titoli rotta. Non trova contrasto, ordine di tabulazione o senso di lettura.

La regola di base

axe-core, pubblicato da Deque con licenza MPL-2.0, esegue un set di regole sul DOM già renderizzato. Ogni violazione porta un impact: critical, serious, moderate o minor.

Il test che vale come modello

import { render } from "@testing-library/react";
import { axe, toHaveNoViolations } from "jest-axe";

expect.extend(toHaveNoViolations);

test("il dialog non ha violazioni critiche", async () => {
  const { container } = render();
  const { violations } = await axe(container);
  expect(violations.filter((v) => v.impact === "critical")).toHaveLength(0);
});

Il test renderizza, esegue axe e boccia solo critical. Filtrare per impatto tiene la CI verde: un test che fallisce per un aria-* deprecato viene spento dopo una settimana.

Cosa controlla davvero

  • Ruoli ed etichette: un input senza label o un pulsante senza nome accessibile viene segnalato.
  • Struttura: heading saltati, landmark duplicati, liste con figli non validi.
  • ARIA: attributi sconosciuti, aria-* che richiede un ruolo assente.
  • Contenuti: link con testo vuoto, immagini senza alt, iframe senza titolo.
  • Duplicati: due id uguali nella stessa pagina.

Installazione

npm i -D vitest jsdom @testing-library/react jest-axe
npx vitest run --environment jsdom

jest-axe è MIT e axe-core è MPL-2.0: entrambi girano in locale, senza servizi esterni e senza chiamate di rete.

Tre record su cui provare il primo test: dialog, navigation-menu e dropdown-menu. Per esplorare per categoria parti da 111.985 componenti.

La limitazione

In jsdom non c'è CSS: la regola color-contrast non può girare e axe la salta in silenzio. Il test automatico copre una parte dei criteri WCAG, non tutti: contrasto, focus visibile e lettura con screen reader restano manuali.

Dove continuare

La checklist manuale è in componenti React accessibili; il caso del modale è in dialog accessibile; il modello di test è in Testing Library: cinque test; il confronto misurato fra librerie è in librerie React 2026.

Domande frequenti

axe sostituisce un test manuale?

No. Copre una parte dei criteri WCAG e lascia fuori contrasto, focus e lettura: il test manuale resta obbligatorio.

Perché il contrasto non viene controllato?

Perché jsdom non applica CSS. Senza colori calcolati e layout la regola non ha nulla da misurare, quindi viene saltata.

Meglio jest-axe o vitest-axe?

Sono lo stesso motore. jest-axe funziona con Vitest dopo expect.extend, quindi non serve un pacchetto diverso.

Quante violazioni deve avere un componente per passare?

Zero critical. Le violazioni serious e moderate si correggono, ma non bloccano la pipeline.

axe-core è gratis per uso commerciale?

Sì. axe-core è MPL-2.0 e jest-axe è MIT: uso commerciale permesso, con obbligo di pubblicare le modifiche al motore.