Statut : Recommandation (REC)
Cette présente convention rassemble les bonnes pratiques d'Accessibilité en production appliquées par l'agence web Alsacreations.fr lors de nos développements. Elle a pour but d'évoluer dans le temps et de s'adapter à chaque nouveau projet.
Ce document est divisé en trois parties :
- La Checklist (ensemble des points à respecter)
- Les Explications techniques détaillées (s'y référer lorsqu'un point de la Checklist n'est pas clair)
- Les Ressources d'accessibilité (liens et outils)
Checklist Niveau 1 (base) 🥉
HTML
- Le code produit est valide et respecte les standards W3C.
- Utiliser les éléments HTML pour leur fonction/sémantique et non pas pour leur forme.
- Renseigner la langue de la page avec l'attribut
lang
de l’élément<html>
. - Indiquer avec l'attribut
lang
les changements de langue locaux dans les blocs d'une page. - Utiliser un titre
<title>
pertinent pour chaque page. - Respecter la hiérarchie des titres
<hX>
. - Utiliser les landmarks ARIA avec leur rôles explicites (ex:
<main role="main">
, demandé par RGAA) - Cacher correctement les contenus qui ne devraient pas être retranscrits par un lecteur d’écran (ex.
aria-hidden=true
). - Prévoir au moins un lien d'évitement permettant d'accéder directement au contenu principal.
- Donner un intitulé explicite à tous les liens.
- Signaler lorsqu’un lien s’ouvre dans une nouvelle fenêtre.
CSS
- Ne pas fixer de hauteur sur les éléments afin que le contenu reste lisible lorsque le texte est zoomé.
- Travailler avec des tailles de polices fluides (
em
ourem
). - Ne pas supprimer l'outline autour des éléments cliquables/focusables (pas de
outline: none
) ou utiliser:focus-visible
. - Ne pas employer de contenu généré (
::before
,::after
) pour véhiculer des informations ou pour afficher des icônes. - Masquer correctement les contenus qui devraient être lus par un lecteur d’écran (ex.
.visually-hidden
)
Formulaires
- Vérifier l'accessibilité des formulaires notamment au clavier.
- Indiquer clairement les champs obligatoires.
- Utiliser l'élément
<fieldset>
associé à<legend>
pour regrouper les champs ayant trait à la même thématique. - Toujours associer un
<label>
à son champ respectif (avecfor
etid
). - Indiquer les formats spécifiques des champs lorsqu'il y en a ; ne pas utiliser l'attribut
placeholder
comme indication (privilégierlabel
), il ne doit fournir qu'un exemple d'usage. - Associer correctement une erreur à son champ.
- Associer un
autocomplete
pour les champs demandant une donnée personnelle (nom, prénom, e-mail, adresse, etc.).
Médias
- Toutes les images doivent comporter un attribut
alt
; il doit être videalt=""
pour les images décoratives et renseigné pour les images apportant du contenu. - Lorsqu'un lien renvoie vers un téléchargement de fichier, il faut indiquer : son intitulé, son poids, son format et l'ouverture dans une nouvelle fenêtre.
- Rendre les fichiers SVG accessibles : décoratifs ou non, inline ou non, dans un bouton / lien ou non.
Checklist Niveau 2 (étendue) 🥈
- Tester l'affichage des pages avec un niveau de zoom de 200%.
- Utiliser un lecteur audio/vidéo accessible, par exemple les éléments HTML5 natifs.
- Vérifier la cohérence de la tabulation et adapter si nécessaire avec
tabindex
.
Checklist Niveau 3 (demandes spécifiques) 🥇
- Tester avec un lecteur d'écran.
- Fournir une piste de sous-titres avec le format webVTT et l'élément
<track>
pour les vidéos. - Fournir une alternative textuelle (une retranscription) aux formats audio.
- Rendre les fichiers PDF accessibles ou fournir une alternative
HTML
,.doc
,.odt
structurée. - Utiliser l'attribut
aria-live
judicieusement sur les informations provenant de chargements AJAX ou dévoilées dynamiquement par JavaScript. - Ajouter une modale de personnalisation d'affichage telle que l'outil AccessConfig (ou autre).
- Rendre chaque script compatible avec les technologies d'assistance.
Explications techniques détaillées
Sémantique HTML5
Titres
La hiérarchie peut être testée avec l'extension Headings Map pour Chrome ou Headings Map pour Firefox.
Zone d’en-tête principale
<header role="banner">[…]</header>
La balise <header>
peut être utilisée plusieurs fois dans la page mais l’attribut role="banner"
ne doit être utilisé qu’une seule fois.
Pied de page
<footer role="contentinfo">[…]</footer>
La balise <footer>
peut être utilisée plusieurs fois dans la page mais l’attribut role="contentinfo"
ne doit être utilisé qu’une seule fois.
Zone de contenu principal
<main role="main">[…]</main>
La balise <main>
ne peut être utilisée qu’une seule fois dans la page ainsi que l’attribut role="main"
.
Navigation
Utiliser des combinaisons <ul><li>
(liste non ordonnée) pour structurer les menus de navigation (principale ou secondaire) dans un élément <nav role="navigation”>
:
- Le menu principal du site (souvent affiché dans l’en-tête)
- Un menu secondaire affiché dans certaines pages internes (parfois dans une barre latérale)
- Un menu secondaire affiché dans le pied de page
- Un fil d’ariane
- Une pagination
- Une table des matières
Pour chaque balise <nav role="navigation">
, ajouter un aria-label
descriptif.
Exemple :
<nav role="navigation" aria-label="Menu principal">[…]</nav>
Moteur de recherche
Le rôle role="search"
doit être ajouté à l'élément HTML englobant le formulaire de recherche, ajouter un aria-label
descriptif.
<div role="search" aria-label="Moteur de recherche principal">
<form>[…]</form>
</div>
Plus d’informations : https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/Search_role/
Liens d’évitement ("skip link")
Un lien d'évitement vers le contenu principal est nécessaire. D'autres liens d'évitement peuvent être ajoutés pour accéder rapidement à la navigation, à la recherche, au pied de page, etc.
- Il doit être le premier lien de la page.
- Il peut être masqué (classe Tailwind
sr-only
) et visible lors du focus. - Si le contenu principal est un élément non interactif il faut mettre un
tabindex="-1"
pour rendre cet élément focusable (ex. sur une balise<main>
). Voir la partie sur les tabindex.
Voici le lien d'évitement employé au sein du Design System du W3C :
<a href="#main" class="skip-link">Skip to content</a>
.skip-link {
background-color: #f9dc4a;
border: solid 3px #000;
color: #000;
padding: 0.625em 0.9375em;
text-decoration: none;
}
.skip-link:not(:focus):not(:active) {
border: 0;
clip: rect(0 0 0 0);
-webkit-clip-path: inset(100%);
clip-path: inset(100%);
height: 1px;
overflow: hidden;
padding: 0;
position: absolute;
white-space: nowrap;
width: 1px;
}
.skip-link:focus {
left: 0;
position: absolute;
top: 0;
z-index: 999;
}
Titres de page
Le titre de la page doit être pertinent et de préférence unique pour chaque page. Dans <title>
, éviter le caractère |
(pipe) comme séparateur. Préférer :
(deux-points).
Pour une page de résultats de recherche, il faut indiquer dans le titre le mot recherché ainsi que la page actuelle si une pagination est présente : "Vous avez recherché le mot : xxx - page 2"
Liens
Les intitulés des liens
Tous les liens doivent avoir un intitulé explicite, un lien "vide" n’est pas accessible.
Exemple :
Liens vers les réseaux sociaux
Ne pas faire :
<a href="URL" class="link-facebook"></a>
.link-facebook {
display: block;
height: 2rem;
width: 2rem;
background-image: url('facebook.png');
}
→ dans ce cas là, le lecteur d’écran retranscrit l’intégralité de l’URL.
Même en ajoutant un attribut title="Retrouvez-nous sur Facebook"
sur le lien, celui-ci reste considéré comme vide.
De plus, il n’est pas sûr à 100% que l’attribut title
soit correctement restitué par le lecteur d’écran (tout dépend de la configuration de l’utilisateur).
À faire :
<a href="URL" class="link-facebook">
<span class="sr-only">Retrouvez-nous sur Facebook</span>
</a>
.link-facebook {
display: block;
height: 2rem;
width: 2rem;
background-image: url('facebook.png');
}
→ dans ce cas là, le lecteur d’écran retranscrit bien "Retrouvez-nous sur Facebook".
Ouverture dans une nouvelle fenêtre
Signaler lorsqu’un lien s’ouvre dans une nouvelle fenêtre :
Première méthode
<a href="URL" target="_blank" aria-label="Lire l’article (nouvelle fenêtre)">Lire l’article</a>
Deuxième méthode
<a href="URL" target="_blank" title="Lire l’article (nouvelle fenêtre)">Lire l’article</a>
Formulaires
Utiliser l'élément <fieldset>
associé à <legend>
pour regrouper les champs ayant trait à la même thématique. Exemple : coordonnées du visiteur lors d'une commande en ligne :
<form>
<fieldset>
<legend>Vos coordonnées</legend>
<input type="text" id="name" name="name" autocomplete="family-name">
<label for="name">Nom</label>
<input type="email" id="email" name="email" autocomplete="email">
<label for="email">Email</label>
</fieldset>
</form>
Toujours associer un <label>
à un élément de formulaire <input>
ou <textarea>
pour définir son intitulé.
Ne pas enlever les styles au focus pour toujours savoir quel est le champ actif.
Indiquer de manière claire les champs obligatoires, soit en l'indiquant dans le label ou bien en ajoutant une phrase en début de formulaire. Compléter si besoin par aria-required="true"
.
Si un champ attend un format spécifique, toujours l'indiquer. Ne pas utiliser l'attribut placeholder
comme seule indication.
Exemple :
<label for="email">E-mail <span>(nomprenom@mail.com)</label>
<input type="email" name="email" id="email" autocomplete="email">
Il est également possible de l'afficher avec aria-describedby
qui fait référence à un élément comprenant une description.
Exemple :
<label for="numero-m">Numéro de membre</label>
<input type="text" id="numero-m" name="numero-m" aria-describedby="hint">
<p id="hint">Numéro composé de 4 chiffres.</p>
Associer un autocomplete
pour les champs demandant une donnée personnelle (nom, prénom, e-mail, adresse, etc.) :
<label for="name">Nom</p>
<input type="text" id="name" name="name" autocomplete="family-name">
Voir la liste complète des autocomplete
..
TODO: gestion des erreurs
Navigation
Navigation cohérente
Faciliter la navigation avec un menu, une recherche ou un plan du site, exploitables au clavier.
Tabindex
Il permet de capturer l’ordre du focus selon le chiffre qu’on lui attribue. Un ordre logique est "naturellement" créé selon les éléments interactifs du DOM. Il comprend tous les chiffres positifs à partir de 0.
→ Il faut éviter de toucher au tabindex
positif.
On peut utiliser :
-1
: permet de rendre un élément focusable sans le rendre navigable au clavier. S'il est ajouté sur un élément interactif, celui-ci perdra le focus.0
: l'élément peut capturer le focus et être atteint via la navigation au clavier.
→ Les éléments pouvant recevoir le focus autres que nativement <a>
, <input>
ou <button>
pourront être équipés de tabindex="0"
.
Pour en savoir plus : MDN : tabindex
Details et summary
Les éléments details
et summary
sont accessibles dans la pupart des cas en respectant des bonnes pratiques, voir The details and summary elements, again.
Tableaux
N'utiliser les tableaux que pour la présentation de données, et non pour la structure du document ou du design.
Définir un titre pertinent avec la balise <caption>
. Elle doit être placée juste après la balise d’ouverture <table>
.
Les cellules d’en-têtes doivent être déclarées avec la balise <th>
. Et les cellules de données avec <td>
.
Sur les cellules d’en-tête il est nécessaire d’ajouter l’attribut scope
afin de les lier aux cellules de données. Il prend pour valeur :
col
: s’applique à toutes les cellules de la colonne.row
: s’applique à toutes les cellules de la ligne.
<table>
<caption>Quantité de fruits mangés par jour</caption>
<thead>
<tr>
<th scope="col">Kiwi</th>
<th scope="col">Orange</th>
<th scope="col">Myrtille</th>
</tr>
</thead>
<tbody>
<tr>
<td>10</td>
<td>30</td>
<td>42</td>
</tr>
</tbody>
</table>
Tableaux complexes
Dans le cas des tableaux complexes, scope
ne suffit pas pour lier l’en-tête à ses cellules de données.
Il faut ajouter l’attribut id
sur la cellule d'en-tête, et headers
avec la valeur de l’id sur la cellule de donnée :
<table>
<caption>Nombre de fruits avec pépins, et avec noyau. Et nombre de légumes avec ou sans peau</caption>
<thead>
<tr>
<th id="fruits" colspan="2">Fruits</th>
<th id="legumes" colspan="2">Légumes</th>
</tr>
<tr>
<th id="data1" headers="fruits">avec pépins</th>
<th id="data2" headers="fruits">avec noyau</th>
<th id="data3" headers="legumes">avec peau</th>
<th id="data4" headers="legumes">sans peau</th>
</tr>
</thead>
<tbody>
<tr>
<td headers="fruits data1">14</td>
<td headers="fruits data2">25</td>
<td headers="legumes data3">33</td>
<td headers="legumes data4">30</td>
</tr>
</tbody>
</table>
Bonnes pratiques ARIA
WAI-ARIA est une technologie permettant de donner des indications d'accessibilité supplémentaires par rapport aux comportements natifs déjà prévus par les navigateurs pour les éléments HTML de base.
Trois caractéristiques principales sont définies dans la spécification :
- les attributs
role
(landmarks), voir la Matrice des rôles ARIA - les propriétés, par exemple
aria-label
ouaria-required
. - les états, par exemple
aria-disabled
ARIA est aussi recommandé pour les composants complexes pilotés par JavaScript (ex : menus déroulants, sliders, onglets, modales, etc.).
Voir https://www.w3.org/WAI/ARIA/apg/patterns/
Voici un exemple d'usage de l'attribut aria-label
:
<button aria-label="accéder au code Hypertext markup language">html</button>
Cet exemple est issu d'un article des Guidelines Accessibilité de Orange nommé Les attributs ARIA qui peuvent vous sauver !
décrivant en détail les différences d'usage de aria-label
, aria-labelledby
et aria-describedby
.
Bonnes pratiques CSS
outline et focus
Les éléments interactifs (liens, champs, boutons) affichent un contour lorsqu'ils réagissent au :focus
, c'est à dire au clic, au touch ou à la navigation clavier (les 3).
Ce contour correspond à la propriété CSS outline
(ce n'est pas une border
ni un box-shadow
).
L'ensemble des navigateurs appliquent par défaut un outline
visible lors de l'événement :focus
et, même si nous pourrions trouver cela disgracieux, il est important de ne pas le supprimer autour des éléments cliquables (pas de outline: none
) car il a été conçu pour rendre ces éléments accessibles à tous (= se repérer lors d'une navigation au clavier).
Grâce à la pseudo-classe :focus-visible
il est possible de masquer le contour (focus) lors du clic ou d'un touch tout en le préservant lors d'un focus au clavier (Note : à ce jour, Safari et Internet Explorer ne reconnaissent ni :focus-visible
ni @supports selector()
et appliqueront leur outline par défaut lors du focus sur cet exemple).:
@supports selector(div:focus-visible) {
/* uniquement au clic/tap focus */
.custom-button:focus:not(:focus-visible) {
outline-color: transparent;
}
/* uniquement au focus clavier */
.custom-button:focus-visible {
outline: 6px dashed hotpink;
}
}
Voir en ligne et tester sur CodePen
CSS generated content
On peut générer du contenu en CSS à l’aide de ::before
et ::after
et la propriété content
, pour afficher une icône par exemple (gérée via une font-icon).
Mais la plupart des lecteurs d’écrans actuels peuvent retranscrire ce contenu, ce qui peut provoquer une gêne (voir https://tink.uk/accessibility-support-for-css-generated-content).
Pour éviter cela, il est préférable d’insérer l’attribut aria-hidden=true
sur l’élément.
Exemple :
<a href="URL" class="btn"> <i class="icon-kiwi" aria-hidden="true"></i> KiwiParty </a>
Contenu lu mais masqué à l’écran
Ne jamais utiliser display: none
pour masquer visuellement du texte qui devrait être retranscrit par un lecteur d’écran.
Utiliser plutôt la classe .sr-only
, présente dans Tailwind. Cette astuce CSS permet de cacher visuellement du contenu texte mais tout en restant accessible aux lecteurs d’écrans.
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
Exemple :
Bouton "précédent" d’un slider
Ne pas faire :
<button class="btn-icon swiper-button-prev">
<i class="icon-arrow" aria-hidden="true"></i>
<span>Éléments précédents</span>
</button>
.swiper-button-prev span {
display: none;
}
À faire :
<button class="btn-icon swiper-button-prev">
<i class="icon-arrow" aria-hidden="true"></i>
<span class="sr-only">Éléments précédents</span>
</button>
Bonnes pratiques Images et Médias
Image porteuse d’information ou cliquable
Une image porteuse d’information ou cliquable doit avoir une alternative textuelle, l’attribut alt
doit reprendre l’information figurant sur l’image.
Exemple d’une image cliquable :
<a href="www.knacss.com">
<img src="knacss.png" alt="Knacss">
</a>
Exemple d’une image porteuse d’information :
<img src="banner.png" alt="4,9 milliards € sont consacrés à la modernisation […] - 10 millions € […] - 700km">
Attention : inutile de commencer l’attribut alt=""
par "Image : …"
, cette information sera retranscrite par les lecteurs d’écrans lors de la lecture de l’élément <img>
.
Image décorative
Une image de décoration doit avoir un alt
vide afin que l’image ne soit pas retranscrite par les lecteurs d’écrans.
Exemple d’une image de décoration :
<img src="kiwiparty.png" alt="">
SVG et accessibilité
Les exemples à suivre proviennent du Design System du W3C ainsi que de l'article Contextually Marking up accessible images and SVGs et Les images SVG sont de plus en plus utilisées sur le web mais qu’en est-il de leur accessibilité ?.
Important : Toujours commencer par nettoyer proprement les fichiers SVG (avec SVGOMG) car les éditeurs graphiques ajoutent de nombreux éléments inutiles tels que des <title>
de type "créé par Sketch".
SVG porteur d'information
Cas d'un SVG inline :
Ajouter l'attribut role="img"
pour indiquer aux lecteurs d'écrans de la considérer comme une image et lui éviter de lire tous les nœuds HTML du SVG.
Il faut ensuite ajouter un <title>
(ou un aria-label
) pour expliciter la fonction de l'image.
Ajouter également focusable="false"
pour éviter que la touche Tab ne navigue au sein du SVG.
<svg role="img" focusable="false" aria-labelledby="title">
<title id="title">Le nom accessible</title>
<use xlink:href="#svg-id-to-reference" aria-hidden="true" />
<!-- contenu du SVG -->
</svg>
ou bien (si l'infobulle au survol n'est pas souhaitée) :
<svg role="img" aria-label="Nom accessible" focusable="false">
<use xlink:href="#..." aria-hidden="true"></use>
</svg>
Cas d'une image SVG :
Ajouter l'attribut role="img"
.
<img src="image.svg" role="img" alt="Nom accessible">
SVG décoratif
Cas d'un SVG inline :
Appliquer aria-hidden="true"
sur le svg
afin d'indiquer aux lecteurs d'écran de ne pas le restituer, ainsi que focusable="false"
.
<svg aria-hidden="true" focusable="false">
<!-- contenu du SVG -->
</svg>
Cas d'une image SVG :
alt
vide, aria-hidden="true"
.
<img src="image.svg" alt="" aria-hidden="true">
SVG dans lien ou dans un bouton
La méthode aria-label="Nom accessible"
est mal supportée par certaines assistances techniques lorsque le SVG est contenu dans un lien ou un bouton.
Il est préférable d'utiliser un <span>
invisible pour le nom accessible s'il doit être masqué à l'écran, le texte sera alors retranscrit par les lecteurs d’écrans.
Cas d'un SVG inline :
<a href="#">
<svg aria-hidden="true" focusable="false">
<!-- contenu du SVG -->
</svg>
<span class="sr-only">Nom accessible masqué à l'écran</span>
</a>
Cas d'une image SVG :
<a href="#">
<img src="image.svg" alt="">
Nom accessible visible à l'écran
</a>
Bonnes pratiques Javascript
ARIA live
Utiliser l'attribut aria-live
sur les informations provenant de chargements AJAX ou dévoilées par JavaScript dynamiquement (ex : non présentes naturellement dans le flux de la page comme des alertes).
<div role="alert" aria-live="assertive" aria-atomic="true">
<p>Message envoyé avec succès / Article ajouté au panier</p>
</div>
On pourra moduler avec aria-relevant
(additions
, removals
, text
, all
) selon qu'on ajoute le contenu au conteneur ou que c'est lui-même qui se voit inséré dans le corps de la page.
Autres composants
Pour tous les composants de page agissant sur le contenu, de type swiper, slider, slideshow, accordéon, pagination, onglets, menu déroulant, on privilégiera les scripts "accessibles", y compris ceux utilisant ARIA. Le but étant, entre autres, de ne pas gêner la navigation au clavier et de permettre la lecture de la page avec une synthèse vocale.
Pour les menus déroulants et mega menus, Accessible Mega Menu a fait ses preuves https://adobe-accessibility.github.io/Accessible-Mega-Menu/
Ressources Générales
- RGAA
- Décret n° 2019-768 du 24 juillet 2019 relatif à l'accessibilité...
- Outils d'accessibilité du Gouvernement
- Guide de l'Intégrateur RGAA3
- Guide du Développeur RGAA3
- Notices Accedeweb
- Design System du W3C
- Guidelines Accessibilité Orange
- Modèles de conception accessibles
- My accessibility toolbox
- SmashingMag : Accessibilité dans les devtools de Chrome
Outils
- Support des Assistances Techniques
- AccessConfig : Modale de personnalisation d'affichage
- Checklist accessibilité
Contraste / Webdesign
- Contrastes de couleur vs déficiences visuelles
- Tanaguru Contrast Finder ou Contrast Finder : outil en ligne, propose des couleurs proches
- Paciellogroup Color Contrast Checker (Windows, MacOS)
- Contrast Grid : grille comparant de multiples valeurs, ex. test d'une palette de couleur complète
- WCAG Color contrast checker (extension Chrome et Firefox) qui permet de vérifier les contrastes de couleurs directement depuis sa page HTML
Plugins
- Wave browser extension (Firefox, Edge et Chrome)
- Axe browser extension (Firefox, Edge et Chrome)
- HeadingsMap : Extension Chrome et Firefox) permet de vérifier que la hiérachie des titres est cohérente).
Synthèses vocales
- NVDA
- VoiceOver (natif sur macOS, iOS) (activation : cmd + fn + F5), voir raccourcis clavier
- Jaws