Aller au contenu
Me contacter

Toutes les briques

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

  1. h3 Le titre qui contient la question et la range dans la liste des titres de la page.
  2. button La question. Son attribut aria-expanded donne l'état, réduit ou développé.
  3. div La réponse, masquée par hidden="until-found" tant que la question est fermée : la recherche du navigateur trouve quand même son texte et l'ouvre.
  4. aria-controls Relie le bouton à sa réponse, par l'id de la réponse.
  5. button::after Le « + » qui devient « − » à l'ouverture. Écris content: "+" / "" : sans ce texte alternatif vide, le navigateur ajoute le signe au nom de la question.

Code HTML

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>
CSS
/* 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 div cliquable au lieu d'un button : ni rôle, ni état, ni accès au clavier.
  • Le rôle region sur chaque réponse : l'APG le déconseille au-delà de 6 réponses ouvertes, et avec hidden="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émentDescription
aria-expanded="false"buttonDit si la réponse est fermée (false) ou ouverte (true). Le script le met à jour à chaque clic.
aria-controls="accordion-panel-1"buttonRelie la question à sa réponse, par l'id de la réponse.
hidden="until-found"div de la réponseMasque 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::afterLe 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

ToucheAction
TabPasse d'une question à l'autre, puis dans la réponse ouverte si elle contient un lien
Entrée ou EspaceOuvre ou ferme la réponse de la question qui a le focus

Ordre de tabulation

  1. « Comment modifier mon mot de passe ? »
  2. « Comment changer mon adresse mail ? ». Sa réponse ouverte ne contient pas de lien : Tab passe directement à la question suivante.
  3. « Où trouver mes factures ? »
  4. « Comment supprimer mon compte ? »

Avec un lecteur d'écran

Exemple sur Chrome avec VoiceOver. Les mots changent d'un lecteur d'écran à l'autre.

ActionAnnonce
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

Au-delà de l'APG

NouveautéApportNavigateursDans 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 nameUn accordéon exclusif sans script, au prix du niveau de titre.Chrome 120+, Firefox 130+, Safari 17.2+Variation
::details-contentAnime 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.

Code
// 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.

Code
// 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.

Code
<!-- 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>