Costruire un overlay di audit di accessibilita dal vivo da iniettare ovunque
Lighthouse e axe sono eccellenti — e sono anche un cambio di contesto. Apri i DevTools, esegui l'audit, leggi il report, torni alla pagina. Per una rapida prima verifica mentre costruisci, vuoi i problemi disegnati sulla pagina stessa: un contorno rosso intorno a ogni immagine senza testo alt, un distintivo su ogni input senza etichetta. Questo articolo costruisce esattamente quello come una regola JavaScript che puoi iniettare in qualsiasi sito con JustZix.
Cos'e questo overlay — e cosa non e
Questa e una verifica rapida di prima passata. Coglie gli errori economici, comuni e meccanici — quelli che rappresentano una grande fetta dei reali fallimenti di accessibilita — e li mostra visivamente cosi non puoi mancarli. Non e un sostituto di axe-core o Lighthouse: non testa a fondo la semantica ARIA, l'ordine di focus o il comportamento dello screen reader. Pensalo come il linter che esegui costantemente, non l'audit completo che esegui prima del rilascio.
Il tutto e un'unica regola JS. Iniettala, dai un'occhiata alla pagina, correggi cio che si illumina, re-inietta.
Lo scheletro
Raccoglieremo i problemi in un array, disegneremo un contorno piu un distintivo per ciascuno e mostreremo un contatore riepilogativo. Inizia con l'impalcatura.
(() => {
// Clean up a previous run
document.querySelectorAll('.jz-a11y').forEach(n => n.remove());
const issues = [];
function flag(el, label, color) {
if (!el || !el.getBoundingClientRect) return;
const r = el.getBoundingClientRect();
if (r.width === 0 && r.height === 0) return; // skip hidden
issues.push({ label });
const box = document.createElement('div');
box.className = 'jz-a11y';
Object.assign(box.style, {
position: 'absolute',
left: (r.left + scrollX) + 'px',
top: (r.top + scrollY) + 'px',
width: r.width + 'px',
height: r.height + 'px',
outline: '2px solid ' + color,
background: color + '22',
zIndex: 2147483000,
pointerEvents: 'none',
boxSizing: 'border-box',
});
const tag = document.createElement('span');
tag.textContent = label;
Object.assign(tag.style, {
position: 'absolute', left: 0, top: '-18px',
font: '11px/1.4 system-ui, sans-serif',
background: color, color: '#fff',
padding: '1px 5px', whiteSpace: 'nowrap',
});
box.appendChild(tag);
document.body.appendChild(box);
}
Una chiamata a flag() per ogni elemento problematico. Registra il problema e dipinge un box posizionato in modo assoluto sopra l'elemento con un distintivo etichettato. Tutto porta la classe jz-a11y cosi l'esecuzione successiva puo cancellarlo.
Controllo 1 — immagini senza alt
Un <img> senza alcun attributo alt e un fallimento; alt="" e valido (segna l'immagine come decorativa), quindi segnaliamo solo un attributo davvero mancante.
document.querySelectorAll('img').forEach(img => {
if (!img.hasAttribute('alt')) {
flag(img, 'img: no alt', '#e11d48');
}
});
Controllo 2 — input di modulo senza etichetta
Un input ha bisogno di un nome accessibile. Puo venire da un <label> che lo avvolge o associato, da un aria-label, o da aria-labelledby. Se nessuno e presente, il campo e inutilizzabile con uno screen reader.
const fields = 'input:not([type=hidden]):not([type=submit])'
+ ':not([type=button]), select, textarea';
document.querySelectorAll(fields).forEach(el => {
const byFor = el.id &&
document.querySelector('label[for="' + CSS.escape(el.id) + '"]');
const wrapped = el.closest('label');
const aria = el.getAttribute('aria-label')
|| el.getAttribute('aria-labelledby')
|| el.getAttribute('title');
if (!byFor && !wrapped && !aria) {
flag(el, 'input: no label', '#f59e0b');
}
});
Controllo 3 — link e pulsanti vuoti
Un link o un pulsante senza contenuto testuale e senza etichetta accessibile viene annunciato semplicemente come "link" o "pulsante". E estremamente comune con i pulsanti con sola icona.
document.querySelectorAll('a, button').forEach(el => {
const text = (el.textContent || '').trim();
const aria = el.getAttribute('aria-label')
|| el.getAttribute('title');
const img = el.querySelector('img[alt]:not([alt=""])');
const hasName = text || aria || img;
if (!hasName) {
const what = el.tagName === 'A' ? 'link' : 'button';
flag(el, 'empty ' + what, '#8b5cf6');
}
});
Controllo 4 — testo a basso contrasto
La matematica completa del contrasto WCAG e complicata, ma un controllo di luminanza relativa coglie i peggiori trasgressori. Calcola la luminanza per il colore del testo e per lo sfondo risolto, poi prendi il rapporto di contrasto.
function lum(rgb) {
const c = rgb.map(v => {
v /= 255;
return v <= 0.03928
? v / 12.92
: Math.pow((v + 0.055) / 1.055, 2.4);
});
return 0.2126*c[0] + 0.7152*c[1] + 0.0722*c[2];
}
function parse(str) {
const m = str.match(/\d+(\.\d+)?/g);
return m ? m.slice(0, 3).map(Number) : null;
}
function bgOf(el) {
let n = el;
while (n) {
const bg = getComputedStyle(n).backgroundColor;
if (bg && !/rgba?\(0, 0, 0, 0\)|transparent/.test(bg)) {
return parse(bg);
}
n = n.parentElement;
}
return [255, 255, 255];
}
document.querySelectorAll('p, span, a, li, h1, h2, h3, h4, td')
.forEach(el => {
if (!(el.textContent || '').trim()) return;
const fg = parse(getComputedStyle(el).color);
const bg = bgOf(el);
if (!fg || !bg) return;
const l1 = lum(fg) + 0.05, l2 = lum(bg) + 0.05;
const ratio = l1 > l2 ? l1 / l2 : l2 / l1;
if (ratio < 4.5) {
flag(el, 'contrast ' + ratio.toFixed(1) + ':1', '#0ea5e9');
}
});
La soglia di 4,5:1 e il minimo WCAG AA per il testo di dimensione normale. Il testo grande passa a 3:1, quindi aspettati qualche falso positivo sui titoli grandi — valutali a occhio.
Controllo 5 — salti nell'ordine dei titoli e lang mancante
I titoli non dovrebbero saltare livelli — un <h2> seguito direttamente da un <h4> rompe la struttura del documento. E la radice <html> ha bisogno di un lang cosi gli screen reader scelgono la pronuncia giusta.
let prev = 0;
document.querySelectorAll('h1,h2,h3,h4,h5,h6').forEach(h => {
const level = +h.tagName[1];
if (prev && level > prev + 1) {
flag(h, 'jump h' + prev + '→h' + level, '#db2777');
}
prev = level;
});
if (!document.documentElement.getAttribute('lang')) {
issues.push({ label: 'html: no lang' });
}
Il contatore riepilogativo
Infine, un distintivo fisso nell'angolo con il totale. E il punteggio a colpo d'occhio; i contorni sono il dettaglio.
const sum = document.createElement('div');
sum.className = 'jz-a11y';
sum.textContent = issues.length
? issues.length + ' a11y issue(s)'
: 'No quick a11y issues found';
Object.assign(sum.style, {
position: 'fixed', right: '12px', bottom: '12px',
font: '13px/1.5 system-ui, sans-serif',
background: issues.length ? '#e11d48' : '#16a34a',
color: '#fff', padding: '6px 12px', borderRadius: '6px',
zIndex: 2147483600, pointerEvents: 'none',
});
document.body.appendChild(sum);
console.table(issues);
})();
Il console.table(issues) alla fine scarica anche un elenco ordinato nella Output Console, cosi hai sia l'overlay visivo sia un riepilogo copiabile.
Come usarlo giorno per giorno
- Inserisci l'intero script in una regola JS circoscritta al sito che stai costruendo, ed eseguilo a richiesta dal pannello JS (Ctrl+Enter) cosi non si ridisegna a ogni navigazione.
- I contorni sono posizionati in modo assoluto, quindi dopo un grande ridimensionamento basta rieseguirlo.
- Correggi prima i problemi piu rumorosi — alt mancante e input senza etichetta sono di solito correzioni HTML di una riga.
- Quando il distintivo nell'angolo diventa verde, passa a Lighthouse o axe per l'audit approfondito. Questo overlay risolve l'80% facile; gli strumenti veri gestiscono il resto.
Vedi anche
- Un overlay di debug responsive — la stessa idea applicata ai bug di layout.
- Disattiva i dark pattern e la falsa urgenza — correggere un diverso tipo di UX rotta.
Un overlay di audit dal vivo trasforma l'accessibilita da una checklist che dimentichi a qualcosa che non puoi ignorare. Installa JustZix, incolla lo script e lascia che sia la pagina a dirti cosa c'e di sbagliato.
Valuta questo articolo
Nessuna valutazione — sii il primo.