ARIA
Arricchire la semantica dei componenti complessi per renderli accessibili
Cos'è ARIA?
ARIA (Accessible Rich Internet Applications) è una specifica W3C che consente di migliorare l'accessibilità delle applicazioni web ricche e dei componenti dinamici.
🏷️ Ruoli
Definiscono cosa è un elemento (button, navigation, alert...)
role="button"📊 Stati
Descrivono lo stato corrente di un elemento (checked, expanded, disabled...)
aria-expanded="true"🔧 Proprietà
Forniscono informazioni aggiuntive (label, describedby, controls...)
aria-label="Menu principale"⚠️ Regola d'oro ARIA
Non usare ARIA se puoi usare HTML nativo
Moduli con attributi ARIA
Esempio negativo
Modulo senza ARIA
❌ Modulo inaccessibile senza attributi ARIA appropriati:
<!-- Negativo: nessuna etichetta, nessuna descrizione -->
<input type="text" placeholder="La tua email">
<!-- Negativo: icona non accessibile -->
<button>
<span class="icon">🔍</span>
</button>
<!-- Negativo: errore non associato, nessun autocompletamento -->
<input type="password">
<div class="error">Password troppo corta</div>Esempio positivo
Modulo con ARIA
✅ Modulo accessibile con attributi ARIA appropriati:
<!-- Positivo: aria-label per campo senza etichetta visibile -->
<input
type="text"
aria-label="Indirizzo email (obbligatorio)"
aria-describedby="email-desc"
aria-required="true">
<span id="email-desc">Formato: nome@esempio.com</span>
<!-- Positivo: pulsante con aria-label -->
<button aria-label="Invia modulo">
<span aria-hidden="true">🔍</span>
</button>
<!-- Positivo: errore associato con aria-describedby -->
<input
type="password"
aria-label="Password (minimo 8 caratteri)"
aria-invalid="true"
aria-describedby="pwd-error"
autocomplete="new-password">
<div id="pwd-error" role="alert">
La password deve contenere almeno 8 caratteri
</div>Attributi ARIA utilizzati:
aria-label: Fornisce un'etichetta accessibilearia-describedby: Associa una descrizione dettagliataaria-required: Indica un campo obbligatorioaria-invalid: Segnala un campo non validoaria-labelledby: Fa riferimento a un'etichetta esistenterole="alert": Annuncia immediatamente gli erroriaria-hidden: Nasconde elementi decorativiaria-live: Annuncia cambiamenti dinamici
Spiegazione: Gli attributi ARIA arricchiscono i moduli fornendo etichette, descrizioni e stati alle tecnologie assistive.
Accordion accessibile
Esempio negativo
Senza attributi ARIA
<!-- Negativo: Nessun attributo ARIA, nessuna navigazione da tastiera -->
<div class="accordion-header" @click="toggle">
<span>Title</span>
<span>+</span>
</div>
<div v-if="open">
Content
</div>Esempio positivo
Con ARIA appropriato
<!-- Positivo: ARIA completo con navigazione da tastiera -->
<button
id="accordion-button-1"
aria-expanded="false"
aria-controls="panel-1"
@click="toggle"
@keydown.enter="toggle"
>
Title
</button>
<div
id="panel-1"
role="region"
aria-labelledby="accordion-button-1"
:hidden="!open"
>
Content
</div>Spiegazione: Un accordion deve utilizzare gli attributi ARIA appropriati, supportare la navigazione da tastiera e annunciare i cambiamenti di stato.
Tabindex e navigazione da tastiera
L'attributo tabindex controlla se un elemento può ricevere il focus da tastiera e in quale ordine.
✅ tabindex="0"
L'elemento è focalizzabile nell'ordine naturale del DOM
tabindex="0"<!-- Positivo: Elemento interattivo focalizzabile -->
<div role="button" aria-label="Custom action" tabindex="0">
Custom button
</div>⚠️ tabindex="-1"
Focalizzabile tramite JavaScript ma non tramite Tab
tabindex="-1"<!-- Positivo: Per focus programmatico -->
<div role="alert" aria-live="assertive" tabindex="-1">
Error message
</div>❌ tabindex > 0
Cambia l'ordine di navigazione (evita!)
tabindex="5"<!-- Negativo: Rompe l'ordine naturale del tab -->
<button tabindex="3">Third</button>
<button tabindex="1">First</button>
<button tabindex="2">Second</button>📋 Best practice per tabindex
- Usa
tabindex="0"per rendere focalizzabili elementi interattivi personalizzati - Usa
tabindex="-1"per il focus programmatico (modal, alert) - MAI usare valori positivi - rispetta l'ordine del DOM
- Gli elementi interattivi nativi (
<button>,<a>,<input>) non necessitano di tabindex
🎯 Esempi interattivi
Testa la navigazione da tastiera (Tab/Shift+Tab) con questi elementi:
❌ Elementi non focalizzabili
Paragrafo
💡 Suggerimenti per il test
- Usa Tab per navigare in avanti
- Usa Shift + Tab per navigare indietro
- Usa Spazio o Invio per attivare gli elementi
- Osserva l'indicatore di focus (contorno blu)
- Nota quali elementi possono o non possono ricevere il focus
Menu a discesa accessibile
Esempio negativo
Menu senza ARIA
<!-- Negativo: Nessun ruolo menu, nessun focus trap -->
<div @click="open = !open">
Actions ▼
</div>
<div v-if="open">
<div @click="action">Edit</div>
<div @click="action">Delete</div>
</div>Esempio positivo
Menu con ARIA
<!-- Positivo: Attributi ARIA e gestione del focus -->
<button
aria-haspopup="true"
aria-expanded="false"
aria-controls="menu"
@keydown.escape="close"
>
Actions
</button>
<div
id="menu"
role="menu"
v-if="open"
>
<button role="menuitem">Edit</button>
<button role="menuitem">Delete</button>
</div>Spiegazione: Un menu a discesa deve supportare la navigazione da tastiera, chiudersi con Escape e mantenere il focus appropriatamente.
Galleria di immagini accessibile
Esempio negativo
Galleria di immagini accessibile
<!-- ❌ Negativo: galleria non accessibile da tastiera -->
<div class="thumbnails">
<img v-for="image in images"
:src="image.thumb"
@click="selectImage(index)"
:class="{ active: selected === index }" />
</div>
<div class="main-image">
<img :src="images[selected].full" />
</div>Esempio positivo
Galleria di immagini accessibile
Vista frontale del MacBook Pro da 16 pollici con lo schermo chiuso, che mostra il design in alluminio elegante.
<!-- ✅ Positivo: galleria accessibile con navigazione da tastiera -->
<div role="tablist" aria-label="Immagini del prodotto">
<button v-for="(image, index) in images"
:id="`tab-${index}`"
role="tab"
:aria-selected="selected === index"
:aria-controls="`panel-${index}`"
:tabindex="selected === index ? 0 : -1"
@click="selectImage(index)"
@keydown="handleKeydown($event, index)">
<img :src="image.thumb" :alt="`${image.alt} - Miniature`" />
</button>
</div>
<div :id="`panel-${selected}`"
role="tabpanel"
:aria-labelledby="`tab-${selected}`">
<img :src="images[selected].full" :alt="images[selected].alt" />
<p>{{ images[selected].description }}</p>
</div>
<!-- Bonnes pratiques :
- Pattern tablist/tab/tabpanel ARIA
- Navigation avec flèches, Home, End
- Focus management avec tabindex
- Descriptions détaillées pour chaque image -->Spiegazione: Una galleria di immagini deve consentire la navigazione da tastiera e fornire descrizioni contestuali per ogni immagine. Il pattern ARIA tablist/tab/tabpanel è perfetto per questo caso d'uso.
Tabella ordinabile accessibile
Esempio negativo
| Nome ↑ | Ruolo | Stato | |
|---|---|---|---|
| Alice Martin | alice@example.com | Admin | Attivo |
| Bob Dupont | bob@example.com | Utente | Inattivo |
| Charlie Leroy | charlie@example.com | Moderatore | Attivo |
| Diana Moreau | diana@example.com | Utente | Sospeso |
Esempio positivo
| Alice Martin | alice@example.com | Admin | Attivo |
|---|---|---|---|
| Bob Dupont | bob@example.com | Utente | Inattivo |
| Charlie Leroy | charlie@example.com | Moderatore | Attivo |
| Diana Moreau | diana@example.com | Utente | Sospeso |
Spiegazione: Una tabella ordinabile deve indicare lo stato di ordinamento corrente, consentire la navigazione da tastiera e annunciare i cambiamenti agli screen reader.
Regioni dinamiche aria-live
Esempio negativo
Senza aria-live
I cambiamenti non vengono annunciati
<!-- Mauvais : pas d'attribut aria-live -->
<div class="status-banner">
<p></p>
</div>Contatore: 0
Problemi:
- I cambiamenti non vengono annunciati
- Gli utenti di screen reader non sanno che il contenuto è cambiato
- Nessuna indicazione dell'importanza del messaggio
Esempio positivo
Con aria-live appropriato
I cambiamenti vengono annunciati in base alla loro priorità
<!-- Bon : avec aria-live="polite" pour informations -->
<div
class="status-banner"
aria-live="polite"
aria-atomic="true"
role="status"
>
<p>Information : Données mises à jour</p>
</div>
<!-- Bon : avec aria-live="assertive" pour alertes -->
<div
class="alert-banner"
aria-live="assertive"
aria-atomic="true"
role="alert"
>
<p>Attention : Action requise immédiatement</p>
</div>Zona polite (informazioni)
Zona assertive (avvisi)
Vantaggi:
- I messaggi importanti interrompono con aria-live="assertive"
- I messaggi informativi attendono con aria-live="polite"
- aria-atomic="true" annuncia l'intero contenuto della regione
- I ruoli appropriati (status, alert) forniscono più contesto
Spiegazione: Le regioni aria-live consentono di annunciare i cambiamenti di contenuto agli screen reader. 'polite' attende la fine dell'annuncio corrente, 'assertive' interrompe immediatamente.
Pulsanti icona accessibili
Esempio negativo
Esempio positivo
Spiegazione: I pulsanti che contengono solo icone devono avere un aria-label o testo visivamente nascosto per essere compresi dagli screen reader.
Combobox / Ricerca con suggerimenti
Esempio negativo
Senza ARIA o navigazione da tastiera
Esempio positivo
Con ARIA e navigazione da tastiera
Spiegazione: Una ricerca con suggerimenti (combobox) deve essere navigabile da tastiera, annunciare i risultati e consentire la selezione.
Notifiche toast (Live Regions)
Esempio negativo
Senza aria-live
❌ Le notifiche non vengono annunciate agli screen reader
<!-- Negativo: non annunciato agli screen reader -->
<div v-if="showToast" class="toast">
Votre demande a été enregistrée
</div>Esempio positivo
Con aria-live appropriato
✅ Le notifiche vengono annunciate correttamente
<!-- Positivo: notifica info (polite) -->
<div
v-if="showToast"
role="status"
aria-live="polite"
aria-atomic="true"
class="toast"
>
<p>Votre demande a été enregistrée</p>
<button @click="dismiss" aria-label="Fermer notification">✕</button>
</div>
<!-- Positivo: avviso errore (assertive) -->
<div
v-if="showError"
role="alert"
aria-live="assertive"
aria-atomic="true"
class="toast error"
>
<p>Erreur : action impossible à exécuter</p>
<button @click="dismiss" aria-label="Fermer alerte">✕</button>
</div>Differenze:
role="status"+aria-live="polite": Per notifiche informative, non interrompe la lettura correnterole="alert"+aria-live="assertive": Per errori critici, interrompe immediatamentearia-atomic="true": Annuncia l'intero contenuto della notifica
Spiegazione: Le notifiche dinamiche devono essere annunciate agli screen reader senza rubare il focus. Usa role='status' per le info e role='alert' per gli errori.