Accessibilité
Un défaut d'accessibilité ne se manifeste jamais à l'exécution : la page s'affiche, le clic fonctionne, et seul l'utilisateur qui navigue au clavier ou au lecteur d'écran rencontre le mur.
C'est donc vérifié au build, sur le balisage, avec la position exacte du problème.
Panneau.spd:9:3 — avertissement a11y-img-alt
<img> n'a pas d'`alt`. Décrivez l'image, ou écrivez `alt=""` pour la déclarer
décorative — l'attribut vide est une information, son absence n'en est pas une.
7 | <h2></h2>
8 | <div on:click={basculer}>Ouvrir</div>
9 | <img src="/logo.png" />
| ^^^^
Les règles
| Code | Ce qu'il signale |
|---|---|
a11y-img-alt |
Image sans alt |
a11y-missing-name |
Bouton réduit à une icône, champ que nul <label> ne désigne |
a11y-label-association |
<label> sans for ni contrôle imbriqué |
a11y-anchor-href |
<a> sans href : ni focalisable, ni activable au clavier |
a11y-click-non-interactive |
on:click sur un élément inerte |
a11y-click-events-have-key-events |
Rôle interactif déclaré, mais seul le clic est géré |
a11y-interactive-tabindex |
Rôle interactif sur un élément non focalisable |
a11y-unknown-aria |
Attribut aria-* inexistant |
a11y-unknown-role |
Rôle ARIA inexistant |
a11y-aria-hidden-focusable |
aria-hidden sur un élément resté dans l'ordre de tabulation |
a11y-presentation-interactive |
role="none" sur un élément focalisable |
a11y-positive-tabindex |
tabindex positif : la tabulation de toute la page est réordonnée |
a11y-empty-heading |
Titre sans texte |
a11y-heading-order |
Titre qui saute un niveau : h1 puis h3 |
a11y-duplicate-id |
Identifiant écrit deux fois — ou une seule, mais dans une liste |
a11y-iframe-title |
<iframe> sans title |
a11y-media-captions |
<video> sans <track kind="captions"> |
a11y-distracting-element |
<marquee>, <blink> |
a11y-unknown-aria et a11y-unknown-role proposent le bon terme : aria-lable
devient « vouliez-vous aria-label ? ».
Sévérité
Avertissement par défaut. Rien ne casse un build sans qu'on l'ait demandé : l'accessibilité se corrige, elle ne s'impose pas au milieu d'une session de travail.
speedster({ accessibility: 'error' }) // plugin de build
speedster-check src --a11y error # intégration continue
speedster-check src --a11y off
En error, un manquement fait échouer la vérification au même titre qu'une erreur
de type — ce qu'un projet à jour veut, pour ne pas régresser.
Neutraliser une règle
Un avertissement qu'on ne peut pas taire est un avertissement qu'on apprend à ignorer — tous, y compris les justes. Un commentaire suffit, et il ne survit pas à la compilation : le balisage émis est identique.
<!-- a11y-ignore-next -->
<div on:click={fermerLaFenetre}></div>
<!-- a11y-ignore-next a11y-img-alt -->
<img src={pixelDeSuivi} />
Il ne porte que sur l'élément suivant, jamais sur ses descendants : la portée est visible à l'œil nu.
Pourquoi vous ne verrez pas de faux positifs
Une règle qui se trompe une fois sur dix finit désactivée en bloc, et le projet perd les neuf autres. Dès qu'une valeur est calculée, la vérification qui en dépend est abandonnée plutôt que devinée.
<img src="a.png" alt={legende} /> <!-- rien à signaler : l'attribut est là -->
<img {...attributs} /> <!-- l'étalement peut tout apporter : silence -->
<div role={role} on:click={agir} /> <!-- un rôle existe, on ignore lequel : silence -->
<input id={identifiant} /> <!-- peut correspondre à un `for` calculé -->
C'est la distinction entre « absent » et « inconnu », et c'est la seule raison pour laquelle ces règles sont utilisables au quotidien.
Les branches qui s'excluent le sont vraiment :
{#if court}<h1>…</h1>{:else}<h3>…</h3>{/if} <!-- pas un saut de niveau -->
{#if vide}<p id="etat">…</p>{:else}<p id="etat">…</p>{/if} <!-- pas un doublon -->
Et l'association <label for> ↔ champ ne se lit pas en descendant : les cibles sont
relevées sur tout le composant, donc un libellé écrit après son champ associe
correctement.
Ce qui n'est pas vérifiable ici
Le balisage produit par un composant enfant relève de sa compilation ; ce qu'on lui passe en contenu est écrit dans le parent, et donc vérifié dans le parent.
Deux choses restent hors de portée : l'ordre des titres entre composants, et l'unicité des identifiants sur la page assemblée. Un composant ignore où il sera monté, et combien de fois — les deviner produirait exactement les faux positifs qu'on cherche à éviter.
À l'exécution
Deux primitives, pour ce que le balisage ne peut pas dire seul :
import { announce, moveFocus } from '@ajustor/speedster-runtime'
announce('3 tâches supprimées')
announce('Connexion perdue', 'assertive')
moveFocus(panneau)
Voir Mouvement.
Et un détail qui compte : aria-*, contenteditable, draggable et spellcheck
sont écrits littéralement. aria-expanded={false} produit
aria-expanded="false", et non un attribut absent — l'absence et le « faux » ne
disent pas la même chose à une technologie d'assistance.
Ce que ces règles ne remplacent pas
Elles vérifient le balisage, pas l'expérience. Le contraste des couleurs, la
pertinence d'un texte de remplacement, la logique d'un parcours au clavier, le sens
d'un ordre de lecture : rien de tout cela ne se lit dans un fichier .spd.
Un audit reste un audit. Ce que ces règles apportent, c'est de ne plus le passer à
chercher des alt manquants.