
European Accessibility Act e WCAG 2.2: guida pratica alla conformità per sviluppatori
Cosa chiede l'European Accessibility Act, chi è obbligato, sanzioni in Italia e come rendere un sito conforme a WCAG 2.2: tastiera, contrasti, screen reader, ARIA e test.
Dal 28 giugno 2025 l’accessibilità digitale non è più una buona pratica da inserire “se avanza tempo” per buona parte dei servizi online venduti ai consumatori nell’Unione Europea: è un requisito di legge, con un’autorità che vigila e sanzioni previste. È l’effetto dell’European Accessibility Act (EAA, Direttiva UE 2019/882), recepito in Italia con il D.Lgs. 82/2022.
Un anno e qualche mese dopo, molti siti sono ancora fermi a “abbiamo installato un widget”. Questo articolo serve a due cose: capire in modo concreto cosa chiede la norma e a chi, e poi — la parte più lunga — tradurla in codice: HTML, CSS, ARIA e test automatici.
ℹ️ Nota
Non sono un avvocato e questo non è un parere legale. Per capire se la tua azienda rientra nell’ambito di applicazione, e con quali eccezioni, la fonte sono il testo del decreto e le Linee guida AgID. Qui mi concentro sulla parte tecnica.
Chi deve adeguarsi
L’EAA copre un elenco preciso di prodotti e servizi forniti ai consumatori. Per chi sviluppa web, la parte che conta sono i servizi:
| Servizio | Esempi di cosa deve essere accessibile |
|---|---|
| Commercio elettronico | Qualsiasi e-commerce B2C: catalogo, carrello, checkout, account |
| Servizi bancari ai consumatori | Home banking, app, apertura conto, pagamenti |
| Trasporto passeggeri (aereo, treno, bus, nave) | Siti e app di prenotazione, biglietti elettronici, info in tempo reale |
| Comunicazioni elettroniche | Area clienti di operatori telefonici e internet |
| Accesso a servizi media audiovisivi | Piattaforme di streaming, guide ai programmi |
| E-book | Store, reader, software dedicato |
Tre cose da sapere subito:
- B2B escluso. La norma riguarda i servizi ai consumatori. Un gestionale venduto solo ad aziende non rientra (ma se ha un’area usata dai clienti finali, ragionaci).
- Microimprese esentate, ma solo per i servizi. Meno di 10 dipendenti e fatturato o bilancio annuo non superiore a 2 milioni di euro: niente obbligo per i servizi. Per i prodotti l’esenzione non vale.
- Periodo transitorio. I contratti di servizio stipulati prima del 28 giugno 2025 possono proseguire fino alla scadenza, per un massimo di cinque anni; i prodotti già usati per erogare il servizio prima di quella data possono restare in uso fino al 28 giugno 2030. Non è un rinvio generale: un sito o un’app rilasciati o rifatti dopo giugno 2025 devono essere conformi.
Esiste anche l’eccezione dell’onere sproporzionato: se adeguarsi costa troppo rispetto alle dimensioni e alle risorse dell’azienda, la si può invocare, ma va documentata con una valutazione scritta, conservata e rinnovata periodicamente. Non è un “no” gratuito.
Chi controlla e cosa si rischia in Italia
Per i servizi l’autorità di vigilanza è l’AgID, che a marzo 2026 ha pubblicato le Linee guida operative e gestisce le segnalazioni degli utenti. Per i prodotti la competenza è del Ministero delle Imprese e del Made in Italy.
Le sanzioni previste dal D.Lgs. 82/2022:
| Violazione | Sanzione amministrativa |
|---|---|
| Mancato rispetto dei requisiti di accessibilità | da 5.000 a 40.000 € |
| Inottemperanza alle misure correttive dell’autorità | da 2.500 a 30.000 € |
| Mancata collaborazione con l’autorità | da 2.500 a 30.000 € |
Per i grandi soggetti privati che già ricadevano nella Legge Stanca (fatturato medio oltre 500 milioni di euro) si aggiunge un regime con sanzioni fino al 5% del fatturato. E poi c’è il rischio meno visibile ma più concreto: le segnalazioni di utenti e associazioni, e i clienti che semplicemente non riescono a comprare.
Quale standard tecnico: WCAG 2.1 o 2.2?
L’EAA non cita le WCAG direttamente. Il riferimento tecnico è la norma armonizzata europea EN 301 549, e qui il calendario è in movimento:
- EN 301 549 v3.2.1 (la versione armonizzata oggi in vigore) rimanda alle WCAG 2.1 livello AA.
- EN 301 549 v4.1.1, pubblicata dall’ETSI il 2 settembre 2026, si allinea alle WCAG 2.2. Diventa la versione di riferimento quando viene citata nella Gazzetta Ufficiale dell’UE, attesa entro fine anno.
Il consiglio pratico è semplice: punta già a WCAG 2.2 AA. È un sovrainsieme della 2.1 (tranne un criterio rimosso), quindi chi è conforme alla 2.2 è conforme anche alla 2.1, e non dovrai rifare il lavoro tra qualche mese.
Le novità della 2.2, a livello A e AA:
| Criterio | Livello | In pratica |
|---|---|---|
| 2.4.11 Focus Not Obscured (Minimum) | AA | L’elemento con il focus non deve finire sotto un header sticky o un banner cookie |
| 2.5.7 Dragging Movements | AA | Ogni drag & drop deve avere un’alternativa con click singoli |
| 2.5.8 Target Size (Minimum) | AA | Target cliccabili di almeno 24×24 px (o abbastanza distanziati) |
| 3.2.6 Consistent Help | A | Contatti e aiuto sempre nello stesso posto tra le pagine |
| 3.3.7 Redundant Entry | A | Non chiedere due volte gli stessi dati nello stesso processo |
| 3.3.8 Accessible Authentication (Minimum) | AA | Login senza test cognitivi: incolla e password manager consentiti |
Il criterio 4.1.1 Parsing è stato rimosso: i browser moderni gestiscono l’HTML malformato in modo coerente, quindi non era più significativo.
Dalla norma al codice
Le WCAG sono organizzate in quattro principi (percepibile, utilizzabile, comprensibile, robusto), ma nel lavoro quotidiano conviene ragionare per aree. Queste sono quelle dove trovo più problemi negli audit.
1. HTML semantico prima di tutto
La maggior parte dell’accessibilità si ottiene usando l’elemento giusto. Un <button> è focusabile, si attiva con Invio e Spazio, ha ruolo “button” e un nome accessibile. Un <div onclick> non ha nulla di tutto questo, e ricostruirlo a mano richiede role, tabindex, gestione di keydown e stati ARIA.
<header>
<a class="skip-link" href="#main">Vai al contenuto</a>
<nav aria-label="Principale">...</nav>
</header>
<main id="main">
<h1>Scarpe da trail</h1>
<section aria-labelledby="filtri-title">
<h2 id="filtri-title">Filtri</h2>
...
</section>
</main>
<footer>...</footer>
Landmark (header, nav, main, footer), un solo h1, una gerarchia di titoli senza salti e un skip link come primo elemento focusabile: chi usa uno screen reader o la tastiera salta direttamente al contenuto invece di attraversare ogni volta il menu.
2. ARIA: meno è meglio
La prima regola di ARIA è non usare ARIA se esiste un elemento nativo che fa la stessa cosa. ARIA cambia solo ciò che viene annunciato, non il comportamento: un role="button" su un div non lo rende cliccabile da tastiera.
Dove serve davvero:
aria-label/aria-labelledbyper dare un nome a controlli senza testo visibile (un bottone con solo un’icona).aria-expandedearia-controlssu menu e accordion.aria-current="page"sulla voce di navigazione attiva.aria-liveper annunciare cambi di stato che non spostano il focus.aria-hidden="true"sulle icone decorative.
<button type="button" aria-expanded="false" aria-controls="menu-mobile">
<svg aria-hidden="true">...</svg>
<span class="visually-hidden">Menu</span>
</button>
3. Navigazione da tastiera e focus
Prova il tuo sito senza mouse: Tab, Shift+Tab, Invio, Spazio, Esc, frecce. Se c’è un punto in cui non sai dove sei o non puoi andare avanti, è un problema di livello A.
/* Mai rimuovere l'outline senza sostituirlo */
:focus-visible {
outline: 3px solid var(--focus-color);
outline-offset: 2px;
}
/* 2.4.11: il focus non deve finire sotto l'header sticky */
html {
scroll-padding-top: 5rem; /* altezza dell'header + margine */
}
Per le modali, usa l’elemento <dialog> con showModal(): gestisce in modo nativo il focus trap, la chiusura con Esc e rende inerte il resto della pagina. Era il componente più difficile da fare bene a mano, oggi è gratis.
function ConfirmDialog({ open, onClose, onConfirm }: { open: boolean; onClose: () => void; onConfirm: () => void }) {
const ref = useRef<HTMLDialogElement>(null);
useEffect(() => {
if (open) ref.current?.showModal();
else ref.current?.close();
}, [open]);
return (
<dialog ref={ref} onClose={onClose} aria-labelledby="dialog-title">
<h2 id="dialog-title">Eliminare l'indirizzo?</h2>
<button onClick={onClose}>Annulla</button>
<button onClick={onConfirm}>Elimina</button>
</dialog>
);
}
Evita tabindex positivi (tabindex="3"): stravolgono l’ordine naturale. Usa solo 0 (rendi focusabile) e -1 (focusabile da script, non con Tab).
4. Contrasti e colore
I rapporti minimi per il livello AA:
- 4.5:1 per il testo normale.
- 3:1 per il testo grande (dal 24 px, o 18,66 px in grassetto).
- 3:1 per componenti dell’interfaccia e grafica significativa: bordi dei campi, icone, indicatore di focus.
Il colore non deve mai essere l’unico modo per trasmettere un’informazione: un campo con errore ha bordo rosso e un messaggio testuale; un grafico ha legenda o pattern, non solo tinte diverse.
Il modo più efficace per non sbagliare è risolvere il problema una volta sola, nei design token: se la palette del design system è verificata, ogni componente nasce conforme. Ricordati del tema scuro: è un secondo set di coppie da controllare.
5. Form ed errori
I form sono dove gli utenti con disabilità abbandonano di più, e dove si concentra il valore economico (checkout, registrazione, login).
<label for="email">Email</label>
<input
id="email"
name="email"
type="email"
autocomplete="email"
required
aria-invalid="true"
aria-describedby="email-error"
/>
<p id="email-error" class="field-error">Inserisci un indirizzo email valido, es. [email protected]</p>
- Ogni campo ha una
<label>visibile e collegata. autocompletecorretto: aiuta tutti, ed è richiesto dal criterio 1.3.5.- L’errore è testuale, specifico (dice come correggere) e collegato con
aria-describedby. - Al submit fallito sposta il focus sul primo campo in errore, o su un riepilogo degli errori.
- 3.3.7: se l’indirizzo di spedizione è già stato inserito, proponi “uguale alla fatturazione” invece di farlo riscrivere.
6. Login accessibile (3.3.8)
Il criterio 3.3.8 vieta di basare l’autenticazione su test cognitivi senza alternative. In concreto:
- Non bloccare l’incolla nei campi password (
onpaste={e => e.preventDefault()}è una violazione). - Usa
autocomplete="current-password"/"new-password"così i password manager funzionano. - I CAPTCHA “trova i semafori” o i puzzle richiedono un’alternativa.
- Le passkey sono la soluzione ideale: nessuna password da ricordare né da digitare.
7. Target, movimento e responsive
- 2.5.8: bottoni e link di almeno 24×24 px. Le icone da 16 px nelle tabelle e i link “×” per chiudere sono i casi classici.
- 2.5.7: riordini con drag & drop? Aggiungi bottoni “sposta su/giù”.
- 1.4.10 Reflow: a 320 px di larghezza (lo zoom al 400% di un desktop) niente scroll orizzontale.
- Animazioni: rispetta
prefers-reduced-motionper animazioni e parallax.
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
8. Contenuti dinamici e SPA
In una single page app il cambio di route non viene annunciato agli screen reader come un caricamento di pagina. Dopo la navigazione aggiorna il document.title e sposta il focus sull’h1 della nuova pagina (con tabindex="-1"). Per notifiche, aggiunte al carrello e risultati di ricerca usa una regione live:
<div aria-live="polite" class="visually-hidden" id="status"></div>
document.getElementById("status")!.textContent = "Prodotto aggiunto al carrello (3 articoli)";
La regione deve esistere nel DOM prima che cambi il testo, altrimenti molti screen reader non lo annunciano.
Testare: automatico, manuale, reale
I test automatici trovano solo una parte dei problemi, tipicamente stimata tra un terzo e la metà. Vedono un contrasto insufficiente o un’immagine senza alt; non possono sapere se l’alt descrive davvero l’immagine o se l’ordine del focus ha senso. Ma sono economici e impediscono le regressioni, quindi vanno in CI.
Con Playwright e axe-core:
// tests/a11y.spec.ts
import { test, expect } from "@playwright/test";
import AxeBuilder from "@axe-core/playwright";
const pages = ["/", "/prodotti", "/carrello", "/checkout"];
for (const path of pages) {
test(`${path} non ha violazioni WCAG A/AA`, async ({ page }) => {
await page.goto(path);
const results = await new AxeBuilder({ page })
.withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "wcag22aa"])
.analyze();
expect(results.violations).toEqual([]);
});
}
test("lo skip link è il primo elemento focusabile", async ({ page }) => {
await page.goto("/");
await page.keyboard.press("Tab");
await expect(page.getByRole("link", { name: "Vai al contenuto" })).toBeFocused();
});
Nota l’uso di getByRole: se i tuoi test trovano gli elementi per ruolo e nome accessibile, ogni test end-to-end diventa anche un piccolo test di accessibilità. Lo stesso vale per i test di componente con Testing Library, che ho usato nella guida a Vitest.
Poi i controlli manuali, da fare almeno sui flussi critici:
- Solo tastiera su tutto il percorso di acquisto o registrazione.
- Screen reader: NVDA su Windows, VoiceOver su macOS e iOS, TalkBack su Android.
- Zoom al 200% e larghezza di 320 px.
- Contrasto su stati hover, focus, disabled e tema scuro.
E infine, quando possibile, test con utenti reali con disabilità: trovano in mezz’ora cose che nessuna checklist prevede.
⛔ Pericolo
I widget di “accessibilità automatica” (overlay) non rendono un sito conforme. Non correggono il codice sottostante, spesso interferiscono con gli screen reader che gli utenti già usano, e nel 2025 la FTC statunitense ha sanzionato uno dei fornitori più noti con 1 milione di dollari per aver promesso una conformità WCAG che il prodotto non garantiva.
Un processo, non un progetto
L’errore più comune è trattare l’accessibilità come un audit da fare una volta. Il sito cambia ogni settimana, e ogni rilascio può introdurre nuove barriere. Quello che funziona:
- Audit iniziale sui template e sui flussi principali (home, listing, scheda prodotto, checkout, login, area utente), non su ogni pagina.
- Backlog di remediation ordinato per impatto: prima ciò che blocca un compito (non riesco a pagare), poi ciò che lo rende difficile.
- Design system conforme: correggi il componente una volta, si sistemano cento pagine.
- Test axe in CI che fanno fallire la build sulle regressioni.
- Informazioni sull’accessibilità pubblicate: l’EAA chiede di spiegare come il servizio soddisfa i requisiti, e di offrire un canale per segnalare problemi.
- Formazione: designer, sviluppatori e redattori. Un
altvuoto su un’immagine informativa è un problema di contenuto, non di codice.
Checklist rapida
- Landmark, un solo
h1, gerarchia dei titoli corretta - Skip link come primo elemento focusabile
- Tutto raggiungibile e utilizzabile da tastiera, nessuna trappola
- Focus visibile (
:focus-visible) e mai nascosto da header sticky - Contrasti 4.5:1 per il testo, 3:1 per componenti e testo grande, anche in dark mode
- Immagini informative con
altsignificativo, decorative conalt="" - Ogni campo con
<label>,autocompleteed errori collegati - Login: incolla consentito, password manager funzionanti, alternative ai CAPTCHA
- Target di almeno 24×24 px, alternative al drag
- Nessuno scroll orizzontale a 320 px, rispetto di
prefers-reduced-motion - Modali con
<dialog>, cambi di route e notifiche annunciati - Video con sottotitoli, audio con trascrizione
- Test axe in CI e verifica manuale con screen reader dei flussi critici
- Informazioni sull’accessibilità e canale di segnalazione pubblicati
Domande frequenti
❓ Il mio sito vetrina deve essere conforme all'EAA?
Un sito puramente informativo, che non vende né eroga uno dei servizi elencati dalla direttiva, di norma non rientra nell’EAA. Se però ha un e-commerce o permette di prenotare e pagare un servizio coperto, sì. In ogni caso un sito accessibile raggiunge più persone, funziona meglio con gli agenti AI e si posiziona meglio: non serve un obbligo per farlo.
❓ Devo seguire le WCAG 2.1 o le 2.2?
Oggi la norma armonizzata in vigore rimanda alle 2.1 AA, ma la nuova EN 301 549 v4.1.1 si allinea alle 2.2. Puntare subito alle 2.2 AA costa poco di più ed evita di rifare il lavoro quando la nuova versione verrà citata in Gazzetta Ufficiale.
❓ Lighthouse a 100 nella sezione Accessibility significa conformità?
No. Lighthouse usa un sottoinsieme delle regole di axe-core e verifica solo ciò che è controllabile automaticamente. Un 100 vuol dire “nessun errore rilevabile da una macchina”, non “utilizzabile da una persona con screen reader”.
❓ L'accessibilità peggiora le prestazioni o la SEO?
Il contrario. HTML semantico, testo invece di immagini, struttura dei titoli chiara e immagini
con alt sono le stesse cose che aiutano motori di ricerca e agenti AI.
E un sito leggero, che rispetta i Core Web Vitals,
è più usabile anche per chi naviga con tecnologie assistive su dispositivi poco potenti.
Conclusione
L’European Accessibility Act ha trasformato l’accessibilità da “nice to have” a requisito, ma la parte tecnica non è cambiata: è ancora HTML semantico, tastiera, contrasti, form fatti bene e test. Se parti dai componenti condivisi, metti axe in CI e verifichi a mano i flussi che portano soldi, sei già nella parte giusta della curva — e il sito diventa migliore per tutti, non solo per chi usa tecnologie assistive.
Riferimenti:

Co-Fondatore & CTO presso PAPION. Senior full-stack engineer specializzato in React, TypeScript, Node.js e sicurezza applicativa.