CSS, bien qu'il ne soit qu'un langage de présentation, peut parfois impacter l'expérience utilisateur des personnes utilisant des technologies d'assistance. Ironiquement, certaines propriétés CSS, conçues pour simplifier la mise en page et son accessibilité, peuvent conduire… à engendrer des soucis d'accessibilité.
Cet article passe en revue certaines propriétés CSS pouvant poser ce type de problèmes, les mécanismes sous-jacents, et surtout, comment les contourner efficacement.
On ne traitera pas des cas basiques comme l'absence de contraste, les polices illisibles, ou les problèmes de focus. Ici, on se concentre sur des pièges plus subtils liés à l'utilisation de certaines propriétés CSS spécifiques, à savoir display
et list-style
.
display
sur les éléments de tableau
L'application de la propriété display
sur les éléments de tableau (<table>
, <tr>
, <td>
, <th>
) offre une flexibilité de mise en page extraordinaire, mais peut également entraîner des problèmes d'accessibilité majeurs en modifiant leur sémantique sur Safari / VoiceOver, rendant les tableaux inaccessibles. Cela concerne particulièrement les declarations display: block
, display: flex
, et display: grid
.
À savoir qu'il ne s'agit pas d'un bug, mais d'un choix de Webkit qui préconise l'utilisation de l'attribut ARIA `role` dans ce cas de figure.
/* ❌ Safari ne reconnaît plus la rangée dans son arbre d'accessibilité */
tr {
display: grid;
}
Solution :
/* ✅ OK si la rangée a un rôle ARIA explicite (ici "row") */
tr[role="row"] {
display: grid;
}
En HTML :
<!-- ✅ Bonne pratique -->
<table>
…
<tr role="row">
<td>Donnée</td>
</tr>
…
</table>
list-style: none
sur les listes
L'utilisation de list-style: none
pour supprimer les puces des listes est une pratique courante en CSS.
Cependant, cette approche peut avoir des conséquences inattendues sur l'accessibilité, particulièrement dans Safari : quand list-style: none
est appliqué, il supprime également la sémantique de liste de l'arbre d'accessibilité. VoiceOver ne reconnaît plus l'élément comme une liste, privant l'utilisateur d'informations contextuelles importantes.
/* ❌ Safari supprime la fonction de liste */
ul {
list-style: none;
}
Solution :
/* ✅ Uniquement si la liste a un rôle ARIA explicite */
ul[role="list"] {
list-style: none;
}
En HTML :
<!-- ✅ Bonne pratique -->
<ul role="list">
<li>Élément de liste</li>
<li>Autre élément</li>
</ul>
display: contents
La propriété display: contents
est l'une des sources les plus pernicieuses de problèmes d'accessibilité. Elle retire l'élément de l'arbre de rendu tout en conservant ses enfants, ce qui peut se révéler extrêmement pratique pour les layouts complexes ou enchevêtrés (Bootstrap, si tu nous entends…).
Cependant, son utilisation sur des éléments sémantiques comme les boutons, les tableaux, les titres ou les listes entraîne une perte totale de la sémantique et de l'interaction pour les utilisateurs de technologies d'assistance.
/* ❌ Danger : supprime la sémantique du bouton */
button {
display: contents;
}
L'impact :
- Les boutons et divers éléments de formulaire deviennent inopérants au clavier
- Les tableaux perdent leur structure logique
- Les titres et les listes ne sont plus reconnus comme tels
Solution :
/* ✅ Utiliser display: contents uniquement sur des éléments non-sémantiques */
.wrapper {
display: contents; /* OK sur une div */
}
Checklist avant déploiement
Avant de déployer votre projet, voici une checklist rapide pour garantir l'accessibilité pouvant être impactée par les styles CSS :
- Tableaux : Vérifiez que
display: flex/grid
sur des éléments tabulaires (<table>
,<tr>
,<td>
) n'est pas utilisé sans un rôle ARIA explicite - Listes : Assurez-vous que
list-style: none
est accompagné derole="list"
- Boutons et formulaires : Évitez
display: contents
sur les éléments interactifs
Mini Reset CSS spécial "accessibilité"
Un reset CSS est souvent utilisé pour uniformiser les styles de base entre les navigateurs. Cependant, il est crucial de s'assurer que ces resets ne compromettent pas l'accessibilité. Voici quelques bonnes pratiques à caser pour un reset CSS accessible :
/* Reset CSS accessible */
/* Hauteur de ligne minimale pour les contenus */
body {
line-height: 1.5;
}
/* Hauteur de ligne réduite pour les éléments interactifs */
h1,
h2,
h3,
h4,
button,
input,
label {
line-height: 1.1;
}
/* Améliore la lisibilité des liens */
a:not([class]) {
text-decoration-skip-ink: auto;
}
/* Supprime les styles de liste uniquement si role="list" est explicite */
ul[role="list"],
ol[role="list"] {
list-style: none;
}
/* Masquage du contenu visuellement tout en le gardant accessible aux technologies d'assistance. */
.visually-hidden {
position: absolute !important;
width: 1px !important;
height: 1px !important;
margin: -1px !important;
overflow: hidden !important;
white-space: nowrap !important;
clip-path: inset(50%) !important;
}
/* Désactivation des animations pour les utilisateurs sensibles */
@media (prefers-reduced-motion: reduce) {
*,
::before,
::after {
transition-duration: 0s !important;
transition-delay: 0s !important;
animation-duration: 1ms !important;
animation-delay: -1ms !important;
animation-iteration-count: 1 !important;
background-attachment: initial !important;
scroll-behavior: auto !important;
}
}
Si vous voulez découvrir le fichier "Reset" employé chez Alsacréations, n'hésitez pas à vous rendre sur notre mini-projet reset.alsacreations.com qui décrit en détail la composition et l'usage de ce fichier chez nous.
Conclusion
L'accessibilité en CSS n'est pas qu'une question de bonnes intentions, c'est un domaine technique qui nécessite — notamment — une compréhension approfondie des interactions entre les propriétés CSS et les technologies d'assistance. Les propriétés ou valeurs modernes comme display: contents
, bien qu'utiles, peuvent créer des barrières d'accessibilité si elles ne sont pas utilisées avec précaution.
La règle d'or : testez toujours avec de vraies technologies d'assistance, et gardez en tête que ce qui fonctionne visuellement peut être complètement cassé pour un utilisateur de lecteur d'écran.
Commentaires
Bonjour,
ce reset css est très intéressant, je vais le mettre en place.
Merci pour l'article, je ne connaissais pas ces effets secondaires sur Safari.
Bonjour,
Merci pour cet article. Je ne connaissais pas ces écueils avec Safari.
Par contre, c'est clairement une entorse aux standards de la part de Apple. Normalement, on n'est pas censé préciser le rôle sur <ul role="list"> ou <tr role="row">, vu que c'est le rôle par défaut de ces éléments.
Est-ce que cette redondance théoriquement complètement inutile ne provoque pas de bugs du côté de Jaws ou NVDA ?
Tant qu'on parle des choses un peu étranges avec Safari, il serait aussi intéressant peut-être de parler de l'astuce role=text permettant d'empêcher VO de séparer un bout de texte en plusieurs parties:
[code=html]
<p>
J'aime le <span style="color: red;">rouge</span> et j'aime le <span style="color: blue;">bleu</span> !
</p>
[/code]
Pas testé ce cas précis, mais parfois, Safari découpe le texte en plusieurs parties (plusieurs balayages / VO+droite nécessaires), et c'est un peu gênant:
- J'aime le
- rouge
- et j'aime le
- bleu
- !
Solution, ajouter le rôle <p role="text"> ici. Ce rôle n'est pas standard, il n'a pas d'impact ailleurs que sur Safari.
Attention quand même au fait que dès lors, le texte est lu d'un seul tenant par VO, donc à ne surtout pas faire s'il contient des liens ou d'autres éléments interactifs, et à ne pas faire non plus si le texte est long (plusieurs paragraphes).
@QuentinC : Merci pour ton retour très enrichissant, je vais me renseigner sur ce rôle "text" propriétaire.
Concernant WebKit et display, a priori c'est un vaste sujet. WebKit considère que masquer les puces ou changer la valeur de display correspond à une volonté de ne plus considérer la liste comme une liste.
Dans les cas à la con, j'ai aussi vu que l'utilisation de webkit-appearance: none sur les input de type checkbox faisait complètement foirer la vocalisation de ces derniers sur Firefox/NVDA y a qq années, je ne sais pas si le bug a été résolu.