Accessibilité Web et attributs aria-*

Contexte et cadre légal

L'accessibilité numérique (souvent abrégée a11y) vise à rendre un site ou une application utilisable par un maximum de personnes, y compris en présence de limitations visuelles, motrices, auditives ou cognitives.

Dans l'Union européenne, l'accessibilité ne concerne plus uniquement une "bonne pratique". Plusieurs textes encadrent le sujet, dont l'European Accessibility Act (Directive (UE) 2019/882), applicable à partir du 28 juin 2025 pour une partie des produits et services visés, avec des modalités définies ensuite par chaque État membre.

L'objectif côté développement reste simple. Produire une interface utilisable au clavier, compréhensible par un lecteur d'écran, et cohérente quand le contenu change sans rechargement de page.

Sur quoi s'appuie l'accessibilité web

Avant de toucher aux détails techniques, il faut poser des bases vérifiables. Une interface accessible repose sur des choix visuels, des choix HTML, et une navigation cohérente au clavier.

La couleur

Les couleurs aident a comprendre une interface, mais elles ne sont pas fiables pour tout le monde. Une partie des utilisateurs distingue mal certaines teintes, ou ne distingue pas certaines couleurs du tout. D'autres situations posent aussi probleme, par exemple un ecran en plein soleil, une luminosite faible, ou un theme qui modifie les couleurs.

Contraste suffisant

Le contraste mesure la différence de luminosité entre deux couleurs. Plus le contraste est élevé, plus un texte ou un élément d'interface est lisible. Un contraste trop faible peut rendre la lecture pénible, ou impossible, selon la vue et les conditions d'affichage.

La couleur n'est pas une source unique d'information

Une interface ne peut pas transmettre une information uniquement via une couleur. Certaines personnes ne perçoivent pas les couleurs de la même manière. Dans ce cas, une information qui dépend seulement d'un code couleur peut disparaître.

Si une information dépend d'une couleur, la doubler avec un autre indice texte, icône, forme, soulignement, motif. Par exemple, un champ en erreur ne peut pas être seulement rouge il faut aussi un message clair, et un repère visible.

Sémantique HTML, rôles et landmarks

Le navigateur ne se contente pas d'afficher la page. Il construit aussi un arbre d'accessibilité. Cet arbre décrit l'interface sous forme d'informations exploitables par les technologies d'assistance.

Pour chaque élément, le navigateur expose notamment un rôle. Le rôle indique le type d'élément : bouton, lien, case à cocher, navigation, etc.

Catégories de rôles ARIA

Les rôles ARIA se répartissent en plusieurs grandes catégories, chacune répondant à un besoin spécifique :

  • Rôles de landmark : repères structurels pour identifier les grandes zones d'une page (main, navigation, banner, contentinfo...).
  • Rôles de widget : composants interactifs (button, checkbox, slider, tab...).
  • Rôles de structure : organisation du contenu (article, heading, list...).
  • Autres catégories : rôles de fenêtre (dialog, alertdialog), de région live (alert, status), etc.

Rôles implicites avec le HTML sémantique

Beaucoup de balises HTML possèdent déjà un rôle par défaut. On parle de rôle implicite. Cela signifie que la balise suffit pour que le navigateur comprenne la fonction de l'élément.

  • <button> expose un rôle implicite button.
  • <a href="..."> expose un rôle implicite link.
  • <input type="checkbox"> expose un rôle implicite checkbox.

À l'inverse, certaines balises sont non sémantiques. Elles servent surtout de conteneurs pour structurer la page et appliquer du style. Elles n'indiquent pas une fonction d'interface (<div> et <span> exposent un rôle implicite generic).

L'attribut role

role est un attribut HTML qui permet de définir explicitement le rôle d'un élément. Il sert surtout quand on fabrique un composant personnalisé avec une balise non sémantique (ex.: un <div> qui se comporte comme un bouton).

role peut remplacer le rôle implicite d'une balise. Cela demande de rester cohérent, si un élément est décrit comme un bouton, il doit aussi se comporter comme un bouton au clavier.

  • Préférer une balise native quand elle existe.
  • Réserver role aux composants personnalisés.
  • Ne pas surcharger (remplacer) un élément natif avec un rôle inutile.

Landmarks

Un landmark (repère de page) est un type particulier de rôle ARIA. Il sert à identifier les grandes zones d'une page, permettant aux utilisateurs de lecteurs d'écran de naviguer rapidement entre les sections principales. Un lecteur d'écran peut proposer une liste de ces repères, comme un plan de la page.

Beaucoup de landmarks existent automatiquement grâce aux balises HTML sémantiques, sans avoir besoin d'ajouter explicitement role.

  • <main> expose le landmark main.
  • <nav> expose le landmark navigation.
  • <header> peut exposer le landmark banner quand il représente l'en-tête global du document.
  • <footer> peut exposer le landmark contentinfo quand il représente le pied de page global du document.
  • <aside> expose le landmark complementary.

Utilisation complète au clavier et focus cohérent

Une interface accessible reste utilisable sans souris. Le clavier sert à se déplacer avec Tab et à activer des contrôles avec Entrée ou Espace. Le focus représente l'endroit où se trouve le clavier à un instant donné.

  • Rendre tous les contrôles atteignables au clavier.
  • Garder un focus visible, pour savoir où l'on se trouve.
  • Quand une zone s'ouvre, permettre d'y entrer au clavier. Quand elle se ferme, rendre le focus à un repère logique, souvent le déclencheur.
  • Prévoir la touche Escape sur les menus, panneaux et boîtes de dialogue.

Les attributs aria-*

Les attributs aria-* sont des attributs HTML destinés aux technologies d'assistance. Le * signifie qu'il existe plusieurs attributs (aria-expanded ou aria-checked).

Le but de ces attributs est de compléter ce que le HTML ne décrit pas assez bien, surtout quand une interface change sans rechargement ou quand un composant est personnalisé. Ils servent souvent à exprimer un état ou une relation.

Ces attributs décrivent, mais ne remplacent pas le comportement. Si un panneau est replié, il faut qu'il soit réellement masqué dans le DOM. Ensuite seulement, un attribut comme aria-expanded permet d'annoncer cet état.

Limiter le bruit et les redondances

Trop d'informations peut gêner la compréhension. Une interface accessible cherche aussi à rester claire. Un lecteur d'écran doit entendre une information utile, sans répétition inutile. Il faut donc éviter d'utiliser ARIA quand le HTML natif fait déjà le travail.

Rôle de JavaScript

Le HTML sémantique décrit la structure et la fonction des éléments. JavaScript intervient surtout quand l'interface change sans rechargement. Dans ce cas, il faut maintenir une cohérence entre ce que l'on voit, ce que l'on peut faire au clavier, et ce qu'une technologie d'assistance peut comprendre.

Par exemple, on peut ouvrir un menu, activer un onglet, filtrer une liste, afficher un message d'erreur ou révéler un panneau. Dans ces situations, un changement visuel doit correspondre à un changement réel dans le DOM.

JavaScript sert surtout à synchroniser :

  • le comportement au clavier (navigation, activation des éléments interactifs)
  • la gestion du focus (déplacement, piégeage dans les modales, retour après fermeture)
  • l'état de l'interface et son exposition aux technologies d'assistance (via les attributs ARIA)

Affichage et états réels

JavaScript modifie l'état réel de l'interface dans le DOM. Cela passe souvent par des classes CSS, par la propriété hidden, par disabled sur un bouton, ou par le remplacement d'un contenu lors d'un filtrage. Le but reste le même, un élément affiché doit être réellement présent et atteignable, un élément masqué doit être réellement retiré de la navigation.

Focus et clavier

Le focus représente l'élément actuellement actif pour le clavier. JavaScript peut déplacer le focus pour maintenir une navigation cohérente. Quand un panneau s'ouvre, le focus peut se déplacer vers son premier élément interactif. Quand il se ferme, le focus doit revenir à un repère logique, souvent le bouton déclencheur.

Sur les menus, panneaux et boîtes de dialogue, la touche Escape est attendue pour fermer. Pour les modales, le focus doit rester piégé à l'intérieur tant qu'elles sont ouvertes.

Attributs aria-* et cohérence

Quand l'interface est personnalisée ou dynamique, certaines informations ne sont pas déduites automatiquement. JavaScript peut alors mettre à jour des attributs aria-* pour rendre l'état compréhensible aux technologies d'assistance.

Par exemple, un bouton qui ouvre et ferme un panneau. Visuellement, le panneau apparaît ou disparaît. Côté accessibilité, l'état du bouton doit être explicite. JavaScript met donc à jour aria-expanded sur le bouton : "true" quand le panneau est ouvert, "false" quand il est fermé.

En parallèle, JavaScript affiche ou masque réellement le panneau, souvent avec hidden. Ces attributs ARIA décrivent l'état, mais ne remplacent pas le comportement. Les deux doivent rester synchronisés, sinon un lecteur d'écran risque d'annoncer un panneau "déplié" alors qu'il est invisible, ou l'inverse.

Nom accessible et description

Un lecteur d'écran annonce un élément à partir de son nom accessible. Ce nom ne correspond pas forcément au texte visible, il est calculé par le navigateur selon des règles dites "Accessible Name and Description". Quand un élément interactif ne possède pas de libellé clair, trois attributs reviennent souvent pour compléter ou préciser ce qui doit être annoncé.

aria-label

aria-label définit directement le nom accessible sous forme de texte. Il est utile quand l'élément ne contient pas de texte visible pertinent, par exemple une icône seule. Le contenu de aria-label est annoncé à la place du texte interne.

Une bonne pratique consiste à écrire un libellé court, descriptif et orienté action. Le libellé doit correspondre à l'intention réelle du contrôle.

Exemple avec un bouton icône


                    <button type="button" aria-label="Rechercher">🔍</button>
                

Éviter d'utiliser aria-label pour dupliquer un texte déjà présent, ou pour compenser un manque de balise label dans un formulaire. Quand un libellé visible existe déjà, il est souvent préférable de le réutiliser avec aria-labelledby.

aria-labelledby

aria-labelledby construit le nom accessible à partir du texte d'un ou plusieurs éléments existants, référencés par leur id. Cela évite de dupliquer du contenu et garantit que le nom annoncé reste cohérent si le texte visible change.

La valeur de aria-labelledby peut contenir plusieurs identifiants séparés par des espaces. Le nom accessible est alors la concaténation des textes correspondants, dans l'ordre indiqué.

Exemple avec une boîte de dialogue


                    <h2 id="titre-modal">Confirmation de suppression</h2>

                    <div role="dialog" aria-labelledby="titre-modal">
                        ...
                    </div>
                

Exemple avec plusieurs sources de texte


                    <h2 id="titre-produit">Casque audio</h2>
                    <p id="reference-produit">Réf. A19</p>

                    <button type="button" aria-labelledby="titre-produit reference-produit">
                        Ajouter au panier
                    </button>
                

Notez que les id doivent exister et rester uniques. Si un id est incorrect ou absent, le nom accessible peut devenir vide ou incomplet.

aria-describedby

aria-describedby ajoute une description complémentaire qui vient après le nom accessible. Elle sert à apporter un détail utile, une contrainte ou une aide. Le lecteur d'écran annonce généralement le nom, puis la description, souvent avec une pause.

Comme pour aria-labelledby, la valeur peut contenir plusieurs identifiants séparés par des espaces. La description finale est alors la concaténation des textes correspondants.

Exemple sur un champ de formulaire


                    <label for="pass">Mot de passe</label>
                    <input type="password" id="pass" aria-describedby="desc-pass">

                    <p id="desc-pass">8 caractères minimum, avec une majuscule.</p>
                

La description doit rester informative et concise. Elle ne remplace pas le label, elle le complète. Elle est souvent utilisée pour associer un champ aux règles de validation, aux formats attendus et aux messages d'aide qui lui sont liés.

Règles simples à retenir

  • Donner un nom accessible à tout élément interactif. Quand un texte visible suffit, ne rien ajouter.
  • Utiliser aria-label quand l'élément n'a pas de libellé visible pertinent ou qu'un libellé visuel n'est pas possible.
  • Utiliser aria-labelledby quand un texte existe déjà dans la page et doit servir de nom annoncé.
  • Utiliser aria-describedby pour ajouter une aide ou une contrainte, sans remplacer le nom principal.

États ARIA fréquemment rencontrés

Les attributs suivants décrivent des états d'interface. Ils servent à annoncer, de manière fiable, ce qui est actif, ouvert, sélectionné, invalide ou en cours. Les valeurs attendues sont souvent des chaînes "true" et "false", avec quelques exceptions.

aria-expanded

Rôle : Indique si un élément contrôlé est actuellement déplié ou replié. Cet état se place sur l'élément déclencheur, pas sur le panneau.

Valeurs possibles : "true" ou "false"

Usage typique : Bouton qui ouvre un menu, un accordéon, un panneau de filtres. Très souvent combiné avec aria-controls et un masquage réel via hidden.


                    <button type="button" aria-controls="zone-filtre" aria-expanded="false">Filtres</button>
                    <div id="zone-filtre" hidden>...</div>
                

aria-selected

Rôle : Indique quel élément est sélectionné dans un ensemble où une sélection a du sens. Ici, sélectionner signifie choisir un élément dans une liste pour afficher ou activer une zone liée. La sélection concerne souvent une navigation interne, pas une activation.

Valeurs possibles : "true" ou "false"

Usage typique : Onglets. aria-selected sert à indiquer quel onglet est actuellement choisi. Par exemple, sur une fiche produit, choisir l'onglet "Avis" affiche le panneau des avis et masque le panneau de description.

Quand un onglet devient actif, il reçoit aria-selected="true". Les autres reçoivent aria-selected="false". En parallèle, le panneau correspondant est affiché et les autres sont masqués, généralement avec l'attribut HTML hidden.

Quand on fabrique un composant personnalisé, on peut donner à ses éléments une "fonction" explicite grâce à l'attribut role. Ici, on construit un composant "onglets" au lieu d'utiliser des éléments HTML natifs, donc on décrit sa structure.

Dans un composant onglets, on retrouve souvent trois rôles.

  • tablist est le conteneur qui regroupe les onglets.
  • tab est chaque bouton d'onglet cliquable.
  • tabpanel est le contenu affiché quand l'onglet est sélectionné.
Le navigateur et les lecteurs d'écran reconnaissent alors le pattern "onglets".


                    <!--
                        role="tablist"
                        Le conteneur qui regroupe les onglets.
                        Il indique que les boutons à l'intérieur font partie d'un même ensemble d'onglets.

                        role="tab"
                        Un onglet cliquable. C'est l'élément qui peut être sélectionné.
                        aria-selected indique quel onglet est actuellement sélectionné.

                        role="tabpanel"
                        La zone de contenu associée à un onglet.
                        aria-labelledby relie le panneau à l'onglet qui lui sert de titre.
                        hidden masque réellement les panneaux non sélectionnés.
                    -->

                    <div role="tablist" aria-label="Détails">
                        <button role="tab" id="tab-1" aria-controls="panel-1" aria-selected="true">
                            Description
                        </button>
                        <button role="tab" id="tab-2" aria-controls="panel-2" aria-selected="false">
                            Avis
                        </button>
                    </div>

                    <div id="panel-1" role="tabpanel" aria-labelledby="tab-1">...</div>
                    <div id="panel-2" role="tabpanel" aria-labelledby="tab-2" hidden>...</div>
                

Dans cet exemple, aria-selected répond à la question "quel onglet est choisi dans l'ensemble". On ne décrit pas un marche arrêt, on décrit un choix parmi plusieurs.

aria-pressed

Rôle : aria-pressed sert à décrire un bouton à bascule. Un bouton à bascule ressemble à un bouton classique, mais il peut rester actif après un clic.

Valeurs possibles "true" ou "false"

Usage typique : Quand un bouton représente une option qui peut être activée ou désactivée, et que cet état doit être compris. Un cas très courant est une barre d'outils dans une application (dessin vectoriel, éditeur de texte, etc.).

Cette information est utile quand l'état est surtout visible par le style. Exemple, un bouton "Gras" d'un éditeur de texte qui change de couleur quand il est actif. Sans indication supplémentaire, un lecteur d'écran peut annoncer "bouton" sans préciser si l'option est activée. aria-pressed rend cet état explicite.

Exemple, simulation de barre d'outils


                    <!--
                        Barre d'outils simplifiée.

                        Chaque bouton est une bascule
                        - aria-pressed="true" signifie que l'outil est actif
                        - aria-pressed="false" signifie qu'il est inactif

                        Plusieurs outils peuvent être actifs en même temps
                        gras + italique, par exemple.
                    -->

                    <div role="group" aria-label="Mise en forme">
                        <!-- Par défaut, la balise button a le role "submit"-->
                        <button type="button" aria-pressed="false" id="outil-gras">Gras</button>
                        <button type="button" aria-pressed="false" id="outil-italique">Italique</button>
                        <button type="button" aria-pressed="false" id="outil-souligne">Souligné</button>
                    </div>

                    <p id="statut-outils" role="status"></p>
                

Dans cet exemple, JavaScript mettra à jour aria-pressed à chaque clic. Si l'utilisateur active "Gras", le bouton passe à aria-pressed="true". Si l'utilisateur le désactive, il repasse à aria-pressed="false".

Une zone de statut peut aussi annoncer l'action. Exemple, "Gras activé" ou "Italique désactivé". Ce n'est pas obligatoire, mais cela rend le retour plus clair quand l'interface ne change que par le style.

aria-pressed répond à la question ce bouton est-il actuellement actif Il décrit un état porté par un bouton, sans créer le comportement à la place de JavaScript.

aria-checked

Rôle : Indique l'état coché d'un contrôle qui se comporte comme une case à cocher. L'idée n'est pas "un bouton enfoncé", mais "une option est cochée ou non". Cet attribut est surtout utile quand le contrôle est personnalisé, donc quand on n'utilise pas un vrai <input type="checkbox">.

Valeurs possibles : "true", "false" ou "mixed"

Usage typique Case à cocher personnalisée, switch, sélection partielle. aria-checked sert quand un contrôle représente une option cochable, donc une préférence qui peut être activée ou désactivée, comme un interrupteur on off.

Ce type de contrôle apparait très souvent dans des menus de paramètres. Par exemple, dans les options d'affichage d'un jeu vidéo activer ou désactiver le plein écran, afficher ou masquer les FPS, activer ou désactiver la V-Sync, activer ou désactiver le flou de mouvement. Dans tous ces cas, on coche ou on décoche une option.

Un contrôle personnalisé reste du HTML classique, mais il faut décrire ce qu'il représente. Dans ce cas, on utilise un rôle qui indique le type de contrôle, puis aria-checked pour indiquer son état.

role="checkbox" sert à indiquer que l'élément se comporte comme une case à cocher. Ensuite, aria-checked indique si elle est cochée, non cochée, ou partiellement cochée. Comme l'élément n'est pas un vrai <input>, il faut aussi le rendre atteignable au clavier avec tabindex="0".

Exemple avec une case à cocher personnalisée


                    <!--
                        role="checkbox"
                        Indique que cet élément se comporte comme une case à cocher.

                        aria-checked
                        Indique l'état de la case.
                        true signifie cochée, false signifie non cochée, mixed signifie partiellement cochée.

                        tabindex="0"
                        Rend l'élément focalisable au clavier, car un <div> n'est pas focusable par défaut.
                        Le JavaScript doit ensuite gérer l'activation au clavier, par exemple avec Entrée et Espace.
                    -->

                    <div role="checkbox" aria-checked="false" tabindex="0">
                        Recevoir la newsletter
                    </div>
                

Un exemple classique du web reste le switch "Mode sombre". Quand il est activé, aria-checked vaut "true". Quand il est désactivé, il vaut "false". JavaScript doit garder cette valeur synchronisée avec l'état réel de l'interface.

Le rôle switch sert à représenter une activation simple, comme un thème sombre. Dans ce modèle, on utilise role="switch" et l'état est exprimé avec aria-checked.

Exemple avec un switch


                    <!--
                        role="switch"
                        Indique que le contrôle se comporte comme un interrupteur on off.

                        aria-checked
                        Indique si l'interrupteur est activé ou non.
                        Le JavaScript doit synchroniser cet attribut avec l'état réel de l'interface.
                    -->

                    <button type="button" role="switch" aria-checked="false">
                        Activer le mode sombre
                    </button>
                

L'état "mixed" sert quand un contrôle ne représente pas une seule option, mais l'état d'un ensemble. Par exemple, un contrôle "Tout activer" vaut "true" si tout est coché, "false" si rien n'est coché, et "mixed" si une partie seulement est cochée.

Exemple avec sélection partielle (mixed)


                    <!--
                        Objectif
                        Montrer un contrôle "Tout sélectionner" qui reflète l'état d'un groupe d'éléments.

                        Bon réflexe
                        Un vrai projet utiliserait plutôt des <input type="checkbox">.
                        Ici, on utilise des contrôles personnalisés pour apprendre aria-checked et mixed.
                    -->

                    <section aria-labelledby="titre-options">
                        <h4 id="titre-options">Options de notification</h4>

                        <!--
                            role="group"
                            Indique que les contrôles qui suivent forment un ensemble cohérent.

                            aria-labelledby
                            Donne un nom au groupe en réutilisant le texte du titre.
                        -->
                        <div role="group" aria-labelledby="titre-options">

                            <!--
                                Contrôle parent

                                role="checkbox"
                                Indique que cet élément se comporte comme une case à cocher.

                                aria-checked
                                - true si toutes les options enfants sont cochées
                                - false si aucune option enfant n'est cochée
                                - mixed si une partie seulement des options enfants est cochée

                                aria-controls
                                Indique quels éléments sont impactés par ce contrôle parent.
                                Ici, il contrôle les trois options enfants.

                                tabindex="0"
                                Rend l'élément focalisable au clavier.
                                Le JavaScript doit gérer Espace et Entrée pour basculer l'état.
                            -->
                            <div
                                id="notif-tout"
                                role="checkbox"
                                aria-checked="mixed"
                                aria-controls="notif-email notif-sms notif-push"
                                tabindex="0">
                                Tout sélectionner
                            </div>

                            <!--
                                Contrôles enfants

                                Chaque enfant est une case à cocher indépendante.
                                Le JavaScript synchronise leur aria-checked avec l'état réel.
                            -->
                            <div id="notif-email" role="checkbox" aria-checked="true" tabindex="0">
                                Email
                            </div>

                            <div id="notif-sms" role="checkbox" aria-checked="false" tabindex="0">
                                SMS
                            </div>

                            <div id="notif-push" role="checkbox" aria-checked="true" tabindex="0">
                                Notifications push
                            </div>

                        </div>

                        <!--
                            Conseil UX A11y
                            Une zone de statut peut annoncer la conséquence d'un clic
                            quand un parent coche ou décoche tout le groupe.
                            Le JavaScript mettra à jour ce texte.
                        -->
                        <p id="notif-statut" role="status" aria-live="polite">
                            Certaines options sont cochées.
                        </p>
                    </section>
                

aria-checked répond à la question "est-ce que c'est coché". On décrit un état de type case à cocher, avec un possible état intermédiaire.

aria-invalid

Rôle : Indique qu'une valeur de champ est invalide. Cet attribut complète la validation visuelle et permet d'annoncer clairement le statut.

Valeurs possibles : "true" ou "false"

Usage typique : Validation de formulaire côté client. Souvent associé à une explication via aria-describedby pointant vers un message d'erreur.


                    <label for="email">Email</label>
                    <input id="email" type="email" aria-invalid="true" aria-describedby="err-email">
                    <p id="err-email">Adresse email invalide.</p>
                

Mettre aria-invalid="false" est généralement interprété comme "pas invalide". Selon le lecteur d'écran et son niveau de verbosité, cela peut parfois déclencher une annonce inutile de type "valide" ou "non invalide" quand l'état change. Pour rester sobre, on utilise souvent aria-invalid="true" en cas d'erreur, puis on retire l'attribut quand le champ redevient valide.

Quand aria-describedby pointe vers un message d'erreur, le texte associé doit rester cohérent. En pratique, le JavaScript affiche ou masque le message, ou en met à jour le contenu, en même temps que aria-invalid.

Composants de mesure et barre de progression

Certains composants affichent une valeur numérique. Côté accessibilité, la valeur doit être exposée via des attributs ARIA pour être annoncée par les lecteurs d'écran.

Exemples courants :

  • Barre de progression pour l'upload de fichiers
  • Indicateur d'avancement d'un import ou export de données
  • Progression d'un formulaire multi-étapes
  • Chargement asynchrone de contenu

Structure de base avec role="progressbar"

Un composant de progression utilise role="progressbar". La valeur minimale et maximale sont exprimées avec aria-valuemin et aria-valuemax. La valeur courante est donnée par aria-valuenow.

Exemple de structure :


                    <div
                        role="progressbar"
                        aria-valuemin="0"
                        aria-valuemax="100"
                        aria-valuenow="25"
                        aria-label="Progression de l'import">
                        <!-- Barre visuelle (CSS) -->
                        <div class="barre" style="width: 25%"></div>
                    </div>
                

Annoncer les changements de progression

Les lecteurs d'écran ne détectent pas automatiquement les changements d'attributs comme aria-valuenow. Pour annoncer une progression qui évolue, il faut utiliser une zone de statut avec role="status".

role="status" indique une zone de statut dynamique. Quand son texte est mis à jour en JavaScript, le lecteur d'écran annonce le changement de manière polie, sans interrompre brutalement la lecture en cours.

Exemple avec annonce :


                    <div
                        class="js-barre-progression"
                        role="progressbar"
                        aria-valuemin="0"
                        aria-valuemax="100"
                        aria-valuenow="25"
                        aria-label="Progression de l'import">
                        <div class="js-barre" style="width: 25%"></div>
                    </div>
                    
                    <!-- 
                        Le conteneur suivant sert à annoncer les mises à jour de progression
                        aux technologies d'assistance.
                        Il n'est pas destiné à être affiché à l'écran.
                        Il doit donc être masqué visuellement,
                        sans être retiré de l'arbre d'accessibilité.
                    -->
                    <p class="js-annonces-conteneur" role="status" aria-live="polite">Progression : 25 %</p>
                

Code JavaScript pour mettre à jour la progression :


                    const barre = document.querySelector('js-barre-progression');
                    const statut = document.querySelector('js-annonces-conteneur');
                    const barreVisuelle = barre.querySelector('.js-barre');
                    
                    function mettreAJourProgression(valeur) 
                    {
                        // Mettre à jour la valeur ARIA.
                        barre.setAttribute('aria-valuenow', valeur);
                        
                        // Mettre à jour la barre visuelle.
                        barreVisuelle.style.width = valeur + '%';
                        
                        // Annoncer le changement (seulement à des moments clés).
                        if (valeur % 25 === 0) // Tous les 25%
                        {
                            statut.textContent = `Progression : ${valeur} %`;
                        }
                    }
                

Conseils d'utilisation

  • Éviter les annonces trop fréquentes : ne pas mettre à jour le role="status" à chaque changement (sinon, annonces répétitives et gênantes). Réserver les annonces aux moments clés (tous les 25%, ou au début/fin de la progression).
  • Progression indéterminée : si la durée n'est pas connue (chargement sans pourcentage), ne pas définir aria-valuenow et utiliser uniquement aria-label="Chargement en cours".

Relations entre éléments

Une interface visuelle exprime souvent les liens par la position, la couleur ou l'animation. Côté accessibilité, certaines relations doivent être explicites pour qu'un lecteur d'écran comprenne qu'un élément agit sur un autre. Ces relations passent généralement par des identifiants id.

aria-controls

aria-controls indique qu'un élément contrôle un autre élément du DOM, généralement une zone affichée ou masquée. La valeur de l'attribut correspond à l'id de l'élément contrôlé.

Cet attribut est fréquent dans des motifs comme un bouton qui ouvre un panneau de filtres, un accordéon, un menu repliable, une zone de détails, ou une recherche avancée. Il se combine très souvent avec aria-expanded pour représenter l'état ouvert ou fermé du contenu contrôlé.

Exemple simple avec contenu repliable


                    <button type="button" aria-controls="zone-filtre" aria-expanded="false">Filtres</button>

                    <div id="zone-filtre" hidden>
                        ...
                    </div>
                

Dans cet exemple, le bouton indique qu'il contrôle la zone dont l'identifiant vaut zone-filtre. L'état visuel est géré par hidden, et l'état annoncé est géré par aria-expanded. Les deux états doivent rester synchronisés.

Exemple avec plusieurs éléments contrôlés


                    <button type="button" aria-controls="zone-filtre zone-tri" aria-expanded="false">Options</button>

                    <div id="zone-filtre" hidden>...</div>
                    <div id="zone-tri" hidden>...</div>
                

La valeur peut contenir plusieurs id séparés par des espaces. Cela reste à réserver aux cas où un même contrôle agit réellement sur plusieurs zones.

Une relation bien décrite n'est pas suffisante si la navigation au clavier devient confuse. Quand un menu ou un panneau s'ouvre, déplacer le focus à l'intérieur est souvent la solution la plus simple. Quand il se ferme, remettre le focus sur l'élément déclencheur évite de perdre l'utilisateur.

Notifications dynamiques et aria-live

Lorsqu'un message apparait sans rechargement, il peut passer inaperçu pour un lecteur d'ecran. Une live region sert a annoncer les changements de texte qui se produisent dans une zone du DOM.

aria-live transforme une zone en "zone vivante". Quand le texte de cette zone change via JavaScript, un lecteur d'ecran peut l'annoncer. En pratique, ce mecanisme est plus fiable sur un changement de texte que sur un simple changement d'attribut (ex.: modifier href ou alt).

  • aria-live="polite" annonce de facon "polie", le lecteur d'ecran attend une pause et n'interrompt pas la lecture en cours (ex.: "Produit ajoute", "Tri modifie", "3 resultats").
  • aria-live="assertive" annonce de facon urgente, le lecteur d'ecran peut interrompre pour lire le message (ex.: "Erreur lors du paiement", "Action impossible").

Deux manieres de creer une zone vivante

Premiere maniere, utiliser directement aria-live sur une balise neutre. C'est pratique quand on veut une zone d'annonces sans donner de role particulier au contenu. Le conteneur doit exister dans le DOM avant l'annonce.


                    <div id="js-annonces-conteneur" aria-live="polite"></div>
                

Deuxieme maniere, utiliser un role qui est deja un raccourci de live region. role="status" correspond a un message d'information. Il se comporte en general comme une annonce polite. role="alert" correspond a une alerte importante. Il se comporte en general comme une annonce assertive. Dans ces cas, ajouter aria-live est souvent redondant.


                    <p id="js-annonces-conteneur" role="status"></p>
                

Par exemple, lorsqu'une visionneuse produit change d'état (un même produit de plusieurs couleurs). Changer l'image et son attribut HTML alt ne suffit pas toujours a declencher une annonce. Mettre a jour un texte de statut annonce clairement la selection.


                    <img id="photo" alt="Produit Bleu">
                    <p id="statut-produit" role="status">Variante selectionnee Bleu</p>
                

aria-atomic

aria-atomic détermine ce qui est annoncé lors d'un changement dans une live region : uniquement la partie modifiée, ou l'intégralité du message.

Valeurs possibles : "true" ou "false"

Par défaut, aria-atomic vaut "false", ce qui signifie que seule la partie modifiée du contenu est annoncée. Avec aria-atomic="true", c'est l'ensemble du contenu de la live region qui est réannoncé.

Valeurs par défaut selon les rôles :

  • aria-live : aria-atomic="false"
  • role="status" : équivaut à aria-live="polite" + aria-atomic="false"
  • role="alert" : équivaut à aria-live="assertive" + aria-atomic="true"

Exemple pratique :


                    <p id="etat-panier" aria-live="polite" aria-atomic="true">
                        Panier : <span id="nb-articles">1</span> article
                    </p>
                

Dans cet exemple, aria-atomic="true" est ajouté explicitement. Lorsque JavaScript modifie uniquement le contenu de #nb-articles, le lecteur d'écran annonce le message complet : "Panier : 3 articles". Avec aria-atomic="false", le lecteur d'écran aurait annoncé uniquement le message : "3", ce qui est peu clair pour l'utilisateur.

Langue du contenu

Bien que lang et hreflang ne soient pas des attributs ARIA, ils jouent un rôle important pour l'accessibilité des interfaces multilingues. Ces attributs HTML natifs permettent aux technologies d'assistance d'adapter correctement la prononciation et la restitution du contenu.

La langue principale d'une page est définie sur la balise <html> avec l'attribut lang. Cet attribut permet aux lecteurs d'écran de choisir la bonne voix et la bonne prononciation pour l'ensemble du document.


                    <html lang="fr">
                

Changement de langue dans la page avec l'attribut lang

Quand un fragment de texte est dans une langue différente de la langue principale de la page, l'attribut lang doit être ajouté sur l'élément concerné. Cela permet au lecteur d'écran d'adapter sa prononciation.

Exemple avec une citation :


                    <blockquote lang="en">
                        <p>To be or not to be, that is the question.</p>
                    </blockquote>
                

Sans lang="en", un lecteur d'écran français prononcerait "To be or not to be, that is the question." avec un accent français, ce qui nuit à la compréhension.

Préciser la langue de destination d'un lien avec l'attribut hreflang

L'attribut hreflang indique la langue de la page de destination d'un lien. Il est différent de lang :

  • lang : indique la langue du texte du lien lui-même
  • hreflang : indique la langue de la page cible

Exemple de sélecteur de langue :


                    <nav aria-label="Choix de la langue">
                        <a href="/fr" hreflang="fr" lang="fr">Français</a>
                        <a href="/en" hreflang="en" lang="en">English</a>
                        <a href="/de" hreflang="de" lang="de">Deutsch</a>
                    </nav>
                

Dans cet exemple, chaque lien utilise :

  • lang pour que le texte du lien soit bien prononcé
  • hreflang pour indiquer que la page de destination est dans cette langue

Conseils d'utilisation

  • Toujours définir lang sur <html>
  • Ajouter lang sur tout texte qui diffère de la langue principale, même pour un seul mot
  • Utiliser les codes langue ISO 639-1 (fr, en, de...) et optionnellement la région (en-US, fr-CA)
  • hreflang est surtout utile pour les sélecteurs de langue et le SEO, moins crucial pour l'accessibilité que lang

Lecteurs d'ecran, outils de test

Un lecteur d'ecran lit l'interface a voix haute et permet de naviguer au clavier. C'est un outil central pour verifier que la page reste comprehensible sans se baser uniquement sur l'affichage. Pour tester, utiliser au moins un lecteur d'ecran selon votre systeme.

Windows, NVDA

NVDA est un lecteur d'ecran gratuit tres utilise sous Windows. Si le raccourci a ete active pendant l'installation, le demarrer avec Ctrl + Alt + N. Sinon, le lancer depuis le menu Demarrer.

Alternative integree a Windows, Narrateur. L'activer ou le desactiver avec Windows + Ctrl + Entree.

macOS, VoiceOver

VoiceOver est integre a macOS. L'activer ou le desactiver avec Cmd + F5. Sur certains claviers, il peut falloir aussi la touche Fn.

Linux, Orca

Orca est le lecteur d'ecran le plus courant sur les environnements GNOME. Pour l'activer ou le desactiver, utiliser Super + Alt + S. Si ce raccourci n'est pas disponible, l'option se trouve generalement dans les parametres d'accessibilite.

Ressources de référence