Skip to Content

Chapitre 6.3 — États & propriétés

⏱️ TL;DR — ARIA distingue les états (qui changent dans le temps : aria-expanded, aria-checked, aria-selected, aria-pressed…) des propriétés (plus stables : aria-label, aria-labelledby, aria-describedby, aria-controls, aria-hidden). Les états ne se posent pas « une fois pour toutes » : ils doivent suivre l’état réel de ton composant, mis à jour en JS à chaque interaction. Un aria-expanded figé à false alors que le menu est ouvert est le bug ARIA le plus courant — et l’un des plus trompeurs.

🎯 Objectifs

  • Faire la différence entre un état ARIA et une propriété ARIA.
  • Connaître les états et propriétés les plus courants et ce qu’ils annoncent.
  • Comprendre pourquoi un état doit être synchronisé avec la logique du composant.
  • Tenir un état à jour proprement en React (état ARIA piloté par le state).

États vs propriétés

Les deux sont des attributs aria-*, mais ils ne vivent pas au même rythme.

  • Un état décrit une condition variable : ouvert/fermé, coché/décoché, sélectionné/non, pressé/non. Il change au fil des interactions.
  • Une propriété décrit une caractéristique stable : le libellé, la description, la relation vers un autre élément. Elle change rarement, voire jamais.

La frontière n’est pas toujours nette (aria-label peut changer), mais le réflexe utile est : un état, ça se met à jour ; une propriété, ça se pose et ça s’oublie.

Les états courants

AttributCe qu’il décritValeurs typiques
aria-expandedZone dépliée ou repliéetrue / false
aria-checkedCase ou switch cochétrue / false / mixed
aria-selectedÉlément sélectionné (onglet, option)true / false
aria-pressedBouton bascule enfoncétrue / false
aria-disabledÉlément désactivé (mais présent)true / false
aria-currentÉlément courant d’un ensemblepage, step, true
aria-invalidChamp en erreurtrue / false

Les propriétés courantes

AttributCe qu’il décrit
aria-labelNom accessible (texte direct)
aria-labelledbyNom accessible (référence un id)
aria-describedbyDescription complémentaire (référence un id)
aria-controlsÉlément piloté par celui-ci
aria-hiddenÉlément masqué aux technologies d’assistance

L’état ARIA doit suivre l’état réel

C’est le cœur du chapitre. Quand tu ajoutes aria-expanded à un bouton de menu, tu promets au lecteur d’écran d’annoncer fidèlement si le menu est ouvert. Si tu poses la valeur en dur et que tu oublies de la changer, l’utilisateur entend une information fausse.

<!-- ❌ aria-expanded figé : il restera « réduit » même menu ouvert --> <button aria-expanded="false" onclick="toggleMenu()">Menu</button>

Ici, cliquer ouvre bien le menu visuellement, mais aria-expanded reste false. Le lecteur d’écran annonce « réduit » alors que tout est déployé : l’utilisateur ne se fie plus à rien. Il faut écrire l’état à chaque bascule.

<!-- ✅ L'attribut est réécrit à chaque interaction --> <button aria-expanded="false" onclick="toggleMenu(this)">Menu</button> <script> function toggleMenu(btn) { const ouvert = btn.getAttribute('aria-expanded') === 'true'; btn.setAttribute('aria-expanded', String(!ouvert)); document.getElementById('menu').hidden = ouvert; } </script>

⚠️ Piège — L’aria-expanded figé. C’est le bug ARIA n°1. On ajoute l’attribut « pour faire bien » à l’initialisation, puis on pilote l’ouverture par une classe CSS sans jamais réécrire l’attribut. Résultat : l’état visuel et l’état annoncé divergent. Le même piège guette aria-selected sur les onglets et aria-checked sur les faux boutons radio.

En React : l’état ARIA dérive du state

En React, la solution est naturelle et robuste : ne pilote jamais l’attribut à la main, fais-le dériver de ton state. Comme ça, l’annonce ne peut pas diverger du rendu — c’est la même source de vérité.

// ❌ Attribut figé : ne reflète jamais l'ouverture réelle function Menu() { const [ouvert, setOuvert] = useState(false); return ( <> <button aria-expanded="false" onClick={() => setOuvert(o => !o)}> Menu </button> {ouvert && <ul id="menu">…</ul>} </> ); }
// ✅ aria-expanded DÉRIVE du state : impossible de désynchroniser function Menu() { const [ouvert, setOuvert] = useState(false); return ( <> <button aria-expanded={ouvert} aria-controls="menu" onClick={() => setOuvert(o => !o)} > Menu </button> {ouvert && <ul id="menu">…</ul>} </> ); }

Note deux points : aria-expanded={ouvert} reçoit un booléen (React le sérialise en "true"/"false"), et aria-controls="menu" pointe vers l’id de la zone contrôlée — une propriété stable, elle, qui se pose une fois.

💡 Réflexe — L’état ARIA doit suivre l’état réel, jamais l’inverse. En React, la règle est simple : si une valeur ARIA peut changer, elle dérive du statearia-expanded={ouvert}, aria-checked={coché}, aria-selected={actif}. Tu n’appelles jamais setAttribute à la main : tu laisses le rendu réconcilier l’attribut avec la vérité.

aria-disabled vs disabled

Petit piège fréquent d’états. L’attribut natif disabled (sur <button>, <input>…) retire l’élément de l’ordre de tabulation et bloque l’interaction. aria-disabled="true", lui, annonce « désactivé » mais laisse l’élément focusable et cliquable — c’est à toi de bloquer l’action en JS. On l’utilise quand on veut qu’un élément désactivé reste atteignable au clavier (pour que l’utilisateur sache qu’il existe), typiquement dans une barre d’outils. Ne les confonds pas : aria-disabled décrit, disabled empêche.

🧭 Sur A11yLearn — Sur la page « Mon profil » d’A11yLearn, le bouton « Enregistrer » passait à aria-invalid="true"… et n’en bougeait plus, même une fois le formulaire corrigé. Un utilisateur de lecteur d’écran entendait « invalide » sur un champ pourtant valide. Le correctif a été de dériver l’attribut de la validation : aria-invalid={Boolean(erreur)}. Depuis, l’annonce colle toujours à la réalité — parce que la seule source de vérité, c’est l’état du composant.

📚 Aller plus loin — La spec WAI-ARIA liste chaque attribut avec sa nature (state ou property) et ses valeurs autorisées (w3.org/TR/wai-aria). Côté WCAG, le critère 4.1.2 (Nom, rôle, valeur) impose que la valeur/l’état d’un composant soit exposé et tenu à jour aux technologies d’assistance — un état figé est une non-conformité, pas juste une maladresse.

✏️ Exercices

Exercice 1 — Repère le bug. Que reproches-tu à ce composant d’accordéon ?

function Accordeon() { const [ouvert, setOuvert] = useState(false); return ( <button aria-expanded="false" onClick={() => setOuvert(o => !o)}> Détails </button> ); }

✅ Solution

aria-expanded="false" est une chaîne figée : elle ne reflète jamais l’état ouvert. Le lecteur d’écran annoncera toujours « réduit », même panneau ouvert. Il faut dériver l’attribut du state :

<button aria-expanded={ouvert} onClick={() => setOuvert(o => !o)}> Détails </button>

Bonus : ajoute aria-controls="id-du-panneau" (propriété stable) pointant vers la zone dépliée.

Exercice 2 — État ou propriété ? Classe ces attributs : aria-label, aria-pressed, aria-describedby, aria-selected, aria-controls, aria-checked.

✅ Solution

États (variables, à mettre à jour) : aria-pressed, aria-selected, aria-checked. Propriétés (stables, posées une fois) : aria-label, aria-describedby, aria-controls.

Le test mental : « est-ce que ça change quand l’utilisateur interagit ? » Si oui → état → doit dériver du state.

🧠 Quiz de révision

1. Quelle est la différence entre un état et une propriété ARIA ?

Un état décrit une condition variable (ouvert, coché, sélectionné) et doit être mis à jour à chaque interaction. Une propriété décrit une caractéristique stable (libellé, description, relation) posée une fois. Concrètement : un état se synchronise, une propriété s’oublie.

2. Pourquoi un aria-expanded figé est-il un bug grave ?

Parce qu’il ment : l’attribut annonce « réduit » alors que le menu est ouvert. L’utilisateur de lecteur d’écran perd toute confiance dans les annonces. C’est le bug ARIA le plus courant. Il faut réécrire l’état à chaque bascule.

3. En React, comment éviter de désynchroniser un état ARIA ?

En le faisant dériver du state : aria-expanded={ouvert}, aria-checked={coché}. On ne pilote jamais l’attribut à la main avec setAttribute — le rendu réconcilie l’attribut avec la source de vérité unique.

4. Quelle différence entre disabled et aria-disabled="true" ?

disabled (natif) retire l’élément de la tabulation et bloque l’interaction. aria-disabled="true" annonce « désactivé » mais laisse l’élément focusable et cliquable — c’est à toi de bloquer l’action. On choisit aria-disabled quand on veut garder l’élément atteignable au clavier.

5. aria-controls est-il un état ou une propriété ?

Une propriété : c’est une relation stable entre un déclencheur et l’élément qu’il pilote (par id). On la pose une fois, elle ne change pas au gré des interactions — contrairement à aria-expanded, qui est l’état associé.


Chapitre suivant : Les live regions — annoncer un changement dynamique sans déplacer le focus.

Last updated on