Bac à sable
L'accordéon
Un accordéon en div cliquables n'a ni rôle de bouton ni état, et Tab passe à côté. Ici, chaque question est un vrai bouton placé dans un titre h3 : le lecteur d'écran la retrouve dans la liste des titres, et aria-expanded dit si la réponse est ouverte.
- Technique
- h3, button et aria-expanded
- Navigateurs
- Chrome 105+, Firefox 121+, Safari 15.4+
- JavaScript
- 13 lignes, pour ouvrir et fermer
Démo
Désactive JavaScript : les 4 réponses restent affichées sous leur question.
Dans Mon compte, ouvrez Sécurité, puis Modifier le mot de passe. Un mail confirme le changement.
Dans Mon compte, ouvrez Profil et saisissez la nouvelle adresse. Elle devient active après un clic sur le lien reçu.
Dans Mon compte, ouvrez Commandes : chaque commande propose sa facture en PDF.
Écrivez au service client depuis la page Contact. Le compte et ses données sont supprimés sous 30 jours.
Anatomie
h3Le titre qui contient la question et la range dans la liste des titres de la page.buttonLa question. Son attributaria-expandeddonne l'état, réduit ou développé.divLa réponse, masquée parhidden="until-found"tant que la question est fermée : la recherche du navigateur trouve quand même son texte et l'ouvre.aria-controlsRelie le bouton à sa réponse, par l'idde la réponse.button::afterLe « + » qui devient « − » à l'ouverture. Écriscontent: "+" / "": sans ce texte alternatif vide, le navigateur ajoute le signe au nom de la question.
Code HTML
<div class="accordion">
<!-- Question 1 : le titre contient le bouton qui ouvre la réponse -->
<h3>
<button
type="button"
id="accordion-button-1"
aria-expanded="true"
aria-controls="accordion-panel-1"
>
Comment modifier mon mot de passe ?
</button>
</h3>
<!-- Réponse 1 : reliée par aria-controls, fermée par le script.
Pas de role="region" : Chrome garderait chaque réponse
fermée (until-found) dans la liste des repères, vide. -->
<div id="accordion-panel-1">
<p>
Dans Mon compte, ouvrez Sécurité, puis Modifier
le mot de passe. Un mail confirme le changement.
</p>
</div>
<!-- Questions 2 à 4 : même structure -->
</div>
<script>
// Sans JavaScript, toutes les réponses restent lisibles :
// le script les ferme au chargement, puis gère chaque bouton
const buttons = document.querySelectorAll(
'.accordion button[aria-controls]'
);
const panelOf = (button) =>
document.getElementById(button.getAttribute('aria-controls'));
// until-found : Ctrl+F trouve le texte d'une réponse fermée
const toggle = (button, open) => {
button.setAttribute('aria-expanded', String(open));
if (open) {
panelOf(button).removeAttribute('hidden');
} else {
panelOf(button).setAttribute('hidden', 'until-found');
}
};
for (const button of buttons) {
toggle(button, false);
button.addEventListener('click', () => {
const open = button.getAttribute('aria-expanded') !== 'true';
toggle(button, open);
});
// La recherche du navigateur ouvre la réponse qui contient le texte
panelOf(button).addEventListener('beforematch', () => {
toggle(button, true);
});
}
</script>/* Le bouton prend toute la largeur du titre */
.accordion h3 button {
display: flex;
width: 100%;
align-items: center;
text-align: left;
}
/* Un « + » qui devient « − ». Le texte alternatif vide garde
le signe hors du nom de la question. La 1re déclaration sert
de repli aux navigateurs qui ignorent ce texte alternatif. */
.accordion h3 button::after {
content: "−";
content: "−" / "";
margin-left: auto;
}
.accordion h3 button[aria-expanded="false"]::after {
content: "+";
content: "+" / "";
}Usages
Recommandé pour
- Une FAQ ou des précisions que tout le monde ne lit pas.
- Des réponses courtes, qu'on lit une par une.
- Des questions qu'on retrouve par les titres, avec la liste des titres du lecteur d'écran.
Déconseillé pour
- Cacher un tarif, ou une condition qui décide d'un achat.
- Retirer le marqueur sans le remplacer : rien n'indique plus que la question s'ouvre.
- Un
divcliquable au lieu d'unbutton: ni rôle, ni état, ni accès au clavier. - Le rôle
regionsur chaque réponse : l'APG le déconseille au-delà de 6 réponses ouvertes, et avechidden="until-found"Chrome garde les réponses fermées dans la liste des repères, vides.
Accessibilité
Rôles et attributs ARIA
Le titre et le bouton viennent du HTML. ARIA ajoute l'état du bouton et le lien vers la réponse. Les réponses n'ont pas le rôle region, que l'APG laisse facultatif.
| Attribut | Élément | Description |
|---|---|---|
aria-expanded="false" | button | Dit si la réponse est fermée (false) ou ouverte (true). Le script le met à jour à chaque clic. |
aria-controls="accordion-panel-1" | button | Relie la question à sa réponse, par l'id de la réponse. |
hidden="until-found" | div de la réponse | Masque la réponse fermée et la retire de l'arbre d'accessibilité. La recherche du navigateur (Ctrl+F) trouve son texte, déclenche l'événement beforematch, et le script ouvre la question. Les navigateurs qui ne le connaissent pas le traitent comme hidden. |
content: "+" / "" | button::after | Le texte alternatif vide garde le signe hors du nom : le lecteur d'écran reçoit « Comment modifier mon mot de passe ? », sans le « + ». |
Navigation au clavier
| Touche | Action |
|---|---|
| Tab | Passe d'une question à l'autre, puis dans la réponse ouverte si elle contient un lien |
| Entrée ou Espace | Ouvre ou ferme la réponse de la question qui a le focus |
Ordre de tabulation
- « Comment modifier mon mot de passe ? »
- « Comment changer mon adresse mail ? ». Sa réponse ouverte ne contient pas de lien : Tab passe directement à la question suivante.
- « Où trouver mes factures ? »
- « Comment supprimer mon compte ? »
Avec un lecteur d'écran
Exemple sur Chrome avec VoiceOver. Les mots changent d'un lecteur d'écran à l'autre.
| Action | Annonce |
|---|---|
| Tab sur une question | « Comment modifier mon mot de passe ? », « réduit », « bouton » |
| Entrée | « développé » |
| Flèche suivante (VO + →) | La réponse, « Dans Mon compte, ouvrez Sécurité… » |
| Liste des titres (rotor) | Les 4 questions, au niveau 3 |
Critères RGAA concernés
- Critère 7.1 : Chaque script est-il, si nécessaire, compatible avec les technologies d’assistance ?
- Chaque question est un bouton avec un nom et l'état
aria-expanded, relié à sa réponse. - Critère 7.3 : Chaque script est-il contrôlable par le clavier et par tout dispositif de pointage (hors cas particuliers) ?
- Chaque question s'ouvre et se ferme au clavier, avec Entrée ou Espace.
- Critère 9.1 : Dans chaque page web, l’information est-elle structurée par l’utilisation appropriée de titres ?
- Chaque question est un titre
h3, sous le titre de la section. - Critère 10.7 : Dans chaque page web, pour chaque élément recevant le focus, la prise de focus est-elle visible ?
- Le focus reste visible sur chaque question, ouverte ou fermée.
- Critère 10.8 : Pour chaque page web, les contenus cachés ont-ils vocation à être ignorés par les technologies d’assistance ?
- Une réponse fermée est ignorée par les technologies d'assistance.
Autres références
WCAG 2.2
- Understanding SC 1.3.1: Info and Relationships (en anglais)
- Understanding SC 2.1.1: Keyboard (en anglais)
- Understanding SC 2.4.7: Focus Visible (en anglais)
- Understanding SC 4.1.2: Name, Role, Value (en anglais)
EN 301 549
- Accessibility requirements for ICT products and services (V4.1.1, PDF) (en anglais)Clauses 9.1.3.1, 9.2.1.1, 9.2.4.7, 9.4.1.2
Pattern APG
- Accordion Pattern (Sections With Show/Hide Functionality) (en anglais)
- Accordion Example (en anglais)
Au-delà de l'APG
| Nouveauté | Apport | Navigateurs | Dans la démo |
|---|---|---|---|
hidden="until-found" | La recherche du navigateur (Ctrl+F) trouve le texte d'une réponse fermée, et le script ouvre sa question. | Chrome 102+, Firefox 148+ | Oui |
details name | Un accordéon exclusif sans script, au prix du niveau de titre. | Chrome 120+, Firefox 130+, Safari 17.2+ | Variation |
::details-content | Anime en CSS l'ouverture d'un details. | Chrome 131+, Firefox 143+, Safari 18.4+ | Non |
Variations
Une réponse ouverte à la fois
Ouvrir une question referme celle qui était ouverte. Le script parcourt les autres boutons et les repasse à aria-expanded="false".
Dans Mon compte, ouvrez Sécurité, puis Modifier le mot de passe. Un mail confirme le changement.
Dans Mon compte, ouvrez Profil et saisissez la nouvelle adresse. Elle devient active après un clic sur le lien reçu.
Dans Mon compte, ouvrez Commandes : chaque commande propose sa facture en PDF.
Écrivez au service client depuis la page Contact. Le compte et ses données sont supprimés sous 30 jours.
// Dans le script : ouvrir une réponse referme les autres
if (open) {
for (const other of buttons) {
if (other !== button) {
toggle(other, false);
}
}
}Ouvert au chargement
La 1re réponse reste ouverte quand la page s'affiche : son bouton garde aria-expanded="true".
Dans Mon compte, ouvrez Sécurité, puis Modifier le mot de passe. Un mail confirme le changement.
Dans Mon compte, ouvrez Profil et saisissez la nouvelle adresse. Elle devient active après un clic sur le lien reçu.
Dans Mon compte, ouvrez Commandes : chaque commande propose sa facture en PDF.
Écrivez au service client depuis la page Contact. Le compte et ses données sont supprimés sous 30 jours.
// Dans le script : la 1re réponse reste ouverte au chargement
buttons.forEach((button, index) => toggle(button, index === 0));Version native avec details
Sans JavaScript, details et summary donnent le bouton et son état, et le même name n'ouvre qu'une réponse à la fois. Le summary ne porte pas de niveau de titre : la question manque dans la liste des titres. Un h3 placé dans le summary ne règle rien, puisque selon le couple navigateur et lecteur d'écran on perd soit le titre, soit le rôle de bouton.
Comment modifier mon mot de passe ?
Dans Mon compte, ouvrez Sécurité, puis Modifier le mot de passe. Un mail confirme le changement.
Comment changer mon adresse mail ?
Dans Mon compte, ouvrez Profil et saisissez la nouvelle adresse. Elle devient active après un clic sur le lien reçu.
Où trouver mes factures ?
Dans Mon compte, ouvrez Commandes : chaque commande propose sa facture en PDF.
Comment supprimer mon compte ?
Écrivez au service client depuis la page Contact. Le compte et ses données sont supprimés sous 30 jours.
<!-- Le summary sert de bouton, sans niveau de titre -->
<details name="faq">
<summary>Comment modifier mon mot de passe ?</summary>
<p>…</p>
</details>
<details name="faq">
<summary>Comment changer mon adresse mail ?</summary>
<p>…</p>
</details>