I loader sono utili, ma se progettati male possono creare problemi: annunciano informazioni inutili agli screen reader, muovono troppo l’interfaccia e sfidano il contrasto o la percezione. Qui trovi linee guida pratiche e snippet pronti all’uso per un loader CSS accessibile, con attenzione a ARIA, movimento ridotto e alternative determinate.
Principi chiave
- Non bloccare la lettura dello screen reader: usa
role="status"o elementi nativi (<progress>), evita annunci ripetitivi. - Rispetta chi riduce le animazioni: onora
prefers-reduced-motion. - Offri alternative determinate quando possibile: una barra di avanzamento è meglio di un’animazione infinita.
- Non affidarti solo al colore/forma: aggiungi testo nascosto “Caricamento…”.
- Gestisci il focus: non spostarlo sul loader; resta dove serve all’utente.
Spinner minimale (indeterminato) e accessibile
Questo spinner è silenzioso per gli screen reader (annuncia una sola volta lo stato), riduce l’animazione se necessario e usa solo <span>.
<!-- Markup -->
<span class="loader" role="status" aria-live="polite" aria-busy="true">
<span class="sr-only">Caricamento…</span>
</span>
/* CSS base */
.loader {
display: inline-block;
width: 1.25rem;
height: 1.25rem;
vertical-align: text-bottom;
position: relative;
}
.loader::before {
content: "";
box-sizing: border-box;
position: absolute;
inset: 0;
border: 2px solid currentColor;
border-right-color: transparent;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
/* Testo solo per screen reader */
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden; clip: rect(0,0,0,0);
white-space: nowrap; border: 0;
}
/* Riduzione movimento */
@media (prefers-reduced-motion: reduce) {
.loader::before {
animation: none;
border-right-color: currentColor; /* cerchio statico */
opacity: 0.65;
}
}
/* Animazione */
@keyframes spin {
to { transform: rotate(360deg); }
}
Quando usarlo: attività brevi (<3–5 s) senza percentuale nota. Se il caricamento supera pochi secondi, passa a una soluzione determinata o a un testo di stato più informativo.
Alternativa determinata: barra nativa con <progress>
Quando conosci l’avanzamento, il componente nativo è la scelta più accessibile: espone ruolo e valori agli AT senza ARIA extra.
<label id="dl-label">Download in corso</label>
<progress value="42" max="100" aria-describedby="dl-label">42%</progress>
progress {
width: 100%;
height: 0.75rem;
}
/* Stile personalizzato (fallback generico) */
progress::-webkit-progress-bar { background: #eee; }
progress::-webkit-progress-value { background: currentColor; }
progress::-moz-progress-bar { background: currentColor; }
Nota: aggiorna value via JavaScript durante l’operazione. Il testo interno (es. “42%”) funge da fallback se lo stile nativo non è supportato.
Skeleton screen (per contenuti lunghi)
Lo skeleton riduce l’ansia da attesa mostrando l’ossatura del layout, evitando animazioni complesse. Mantienilo sobrio e non fuorviante.
<h3>Articoli consigliati</h3>
<p role="status" aria-busy="true">Caricamento contenuto…</p>
<p class="skeleton" aria-hidden="true"> </p>
<p class="skeleton" aria-hidden="true"> </p>
<p class="skeleton" aria-hidden="true"> </p>
.skeleton {
display: block;
height: 1rem;
margin: 0.5rem 0;
border-radius: 0.25rem;
background: linear-gradient(90deg, #eee 25%, #f5f5f5 37%, #eee 63%);
background-size: 400% 100%;
animation: shimmer 1.2s ease-in-out infinite;
}
@keyframes shimmer {
0% { background-position: 100% 0; }
100% { background-position: 0 0; }
}
@media (prefers-reduced-motion: reduce) {
.skeleton { animation: none; }
}
Aggiornare lo stato senza spam
Gli screen reader annunciano i cambiamenti in regioni live. Mantieni aria-live="polite" e aggiorna il testo in modo parsimonioso.
const status = document.querySelector('[role="status"] .sr-only');
function setStatus(message) {
if (!status) return;
status.textContent = message;
}
// Esempio: setStatus("Quasi pronto…");
Colori e dimensioni
- Il loader deve rispettare il contrasto sullo sfondo (consigliato almeno 3:1 per elementi non testuali sottili).
- Scala il componente in base al contesto: 16–24px per testo inline; 32–48px per container più grandi.
Checklist rapida
- Il loader è ignorabile? Nessun focus forzato, niente annunci continui.
- Rispetta
prefers-reduced-motion? - Mostra testo di stato (anche nascosto) comprensibile?
- Usa una barra determinata quando la progressione è nota?
- Il contrasto è sufficiente nello stato attivo e inattivo?
Esempio completo (inline spinner, fallback ridotto)
<span class="loader" role="status" aria-live="polite" aria-busy="true">
<span class="sr-only">Caricamento…</span>
</span>
<!-- Inserisci gli stili dello spinner e della classe .sr-only visti sopra -->
Con questi accorgimenti, avrai loader eleganti che non intralciano gli ausili, rispettano le preferenze di movimento e comunicano lo stato in modo chiaro.