Panneau dépliant

Le panneau dépliant permet d'afficher ou de masquer du contenu supplémentaire via un clic sur un bouton. Contrairement à l'accordéon, le panneau dépliant ne fait pas partie d'une liste d'éléments identiques.

Règles d’implémentation pour l’accessibilité

  • Le titre du panneau dépliant est contenu dans un élément button avec un attribut aria-controls qui fait référence à l’id de l’élément qu’il contrôle et un attribut aria-expanded qui prend la valeur true quand la zone de contenu est visible et false quand la zone de contenu est masquée.
  • La zone de contenu détaillé du panneau a
    • un id avec la valeur de aria-controls du bouton qui permet de voir ou de masquer le contenu ;
    • un tabindex avec la valeur -1 quand le contenu est masqué afin de s’assurer que la prise de focus n’est pas possible ; la valeur de tabindex passe à 0 lorsque le contenu est affiché et le focus clavier est positionné sur le contenu affiché.
    • le rôle region. La région de rôle est particulièrement utile pour la perception de la structure par les utilisateurs de lecteurs d’écran lorsque les panneaux contiennent des éléments d’en-tête ou un accordéon imbriqué.

Visuel

Le contenu du panneau dépliant.

Extraits de code

Code HTML

L’attribut aria-label contiendra au départ le texte « Déplier » suivi du contenu de l’élément button.
Le script javascript présenté après remplacera le mot « Déplier » par « Replier » suivi du contenu de l’élément button afin que la vocalisation du bouton soit la plus précise possible (le contenu de aria-label est lu à la place du contenu du button).

<button type="button" class="depliant" aria-expanded="false" aria-controls="panno" aria-label="Déplier Panneau dépliant">
    Panneau dépliant
</button>
<div id="panno" role="region" tabindex="-1">
    <p>Le contenu du panneau dépliant.</p>
</div>

Code CSS

Pour adapter la couleur de la barre de navigation et des liens, modifier les codes couleurs dans la déclaration :root.
L’image d’arrière-plan utilisé pour symboliser la flèche est une icône de la police symbolique Fontawesome.

:root {
    --FRONT-color: #333;
    --BACKGROUND-color : #C3C3C3;
    --BACKGROUND-LIGHT-color: #E3E3E3;
    --FOCUS-color : #F9C233;
}
button.depliant {
  font-weight: normal;
  color: var(--FRONT-color);
  background-color: var(--BACKGROUND-color);
  border: 0;
  border-radius: .25em;
  white-space: nowrap;
  font-size: 1em;
  width: 100%;
  text-align: left;
  margin: 0.5em 0 0 0;
}
button.depliant::after {
  --icon-size: 1.25rem;
  content: "";
  background-color: currentColor;
  float: right;
  flex: 0 0 auto;
  height: var(--icon-size);
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  vertical-align: calc((0.55em - var(--icon-size)) * 0.5);
  width: var(--icon-size);
  -webkit-mask-image: url(../images/caret-right-fill.svg);
  mask-image: url(../images/caret-right-fill.svg);
  transform: rotate(90deg);
  transition: transform .2s ease-out;
}
button[aria-expanded=false].depliant  + div {
  display: none;
}
button[aria-expanded=true].depliant::after {
  transform: rotate(-90deg);
}
button[aria-expanded="true"].depliant {
  border-radius: .25em .25em 0 0 ;
}
button[aria-expanded=true].depliant + div {
  display: block;
  background-color: var(--BACKGROUND-LIGHT-color);
  overflow: auto;
  border-radius: 0 0 .25em .25em;
  padding: 1em;
}
button[aria-expanded=true],
button.depliant:focus,
button.depliant:hover {
  background-color: var(--FOCUS-color);
  outline: 3px solid var(--FOCUS-color);
  font-weight: bold;
  color: var(--BLACK-color);
  cursor: pointer;
}
button[aria-expanded="false"].depliant,
button.depliant + div   {
  margin-bottom: 1.5em;
}
button.depliant  + div:focus {
  outline: 3px solid var(--FOCUS-color);
}
button.depliant  + div a:focus{
  outline: 0  ;
}

Code javascript

/************ Panneau dépliant ***********/
function PlierDeplier() {

  var lesButtons = document.querySelectorAll('button.depliant');
  var i;
  for (i = 0; i < lesButtons.length; i++) {
    //initialisation aria-label
    var textBoutonsDeplier = "Déplier " + lesButtons[i].innerText;
    lesButtons[i].setAttribute('aria-label', textBoutonsDeplier);
      // apparition/disparition du sous-menu au clic
      lesButtons[i].addEventListener('click', function (e) {
          var leBouton = e.target;
          var lePanneau = leBouton.nextElementSibling;
          
          // La boucle "for" ci-après referme tous les panneaux ouverts.
          // Supprimer cette boucle "for" pour laisser les panneaux déjà ouverts dans cet état (ouvert).
          for (j = 0; j < lesButtons.length; j++) {
              if (leBouton !== lesButtons[j]) 
              lesButtons[j].setAttribute('aria-expanded', 'false')
          }   

          var stateButton = leBouton.getAttribute('aria-expanded') === 'false' ? true : false;
          leBouton.setAttribute('aria-expanded', stateButton);
          var textBoutonDeplier = "Déplier " + leBouton.innerText;
          var textBoutonReplier = "Replier " + leBouton.innerText;
          var labelButton = leBouton.getAttribute('aria-expanded') === 'false' ? textBoutonDeplier : textBoutonReplier;
          leBouton.setAttribute('aria-label', labelButton);
          var indexPanneau = leBouton.getAttribute('aria-expanded') === 'false' ? -1 : 0 ;
          lePanneau.setAttribute('tabindex', indexPanneau);
          lePanneau.focus();
      });
  }
}
if (document.querySelectorAll('button.depliant')) {
  PlierDeplier();
}

Utilisation des éléments HTML details et summary

Il existe deux éléments HTML qui permettent d’obtenir l’aeffet « panneau dépliant ».
Inconvénient : ils ne sont pas bien interprétés par les synthèse vocale.

On pourra toutefois les utiliser en rajoutant du code javascript.

Visuel

Afficher plus d'informations

Les informations supplémentaires à afficher

Code HTML

<details>
    <summary class="details-summary">
      <span>Afficher plus d'informations</span> 
    </summary>
    <div class="details-contenu" role="region" tabindex="-1">
      <p>Les informations supplémentaires à afficher</p>
    </div>
</details>

Code CSS

summary {
  cursor: pointer;
  font-weight: bold;
  font-size: 1rem;
  color: black;
  margin-bottom: 0.5rem;
  display: inline-block;
}
summary::before {
  --icon-size: 1rem;
  background-color: currentColor;
  display: inline-block;
  flex: none;
  margin-left: .25em;
  height: var(--icon-size);
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  vertical-align: calc((.75em - var(--icon-size)) * 1);
  width: var(--icon-size);
  content: "";
  -webkit-mask-image: url(../images/caret-right-fill.svg);
  mask-image: url(../images/caret-right-fill.svg);
  transition: transform .2s ease-out;
}
details[open] > summary::before {
  transform: rotate(90deg);
}
summary span {
  border-bottom: 1px solid currentColor;
}
summary:focus,
summary:hover {
  outline: 3px solid black;
}

Code javascript

/** Panneau dépliant avec details et summary */
function allyDetails(detailsElements) {
    for (var i = 0; i < detailsElements.length; i++) {
      var detail = detailsElements[i];
      detail.setAttribute("id", "det-" + i);
      var libelle = document.getElementById("det-" + i).getElementsByClassName("details-summary")[0];
      libelle.setAttribute("aria-controls", "det-" + i);
      libelle.setAttribute("aria-expanded", false);
    }
  }
  
  function handleClickOnDetails() {
    let allDetails = document.querySelectorAll("details");
    for (const item of allDetails) {
      var libelle = item.getElementsByClassName("details-summary")[0];
      var contenu = item.getElementsByClassName("details-contenu")[0];
      if (this === item && this.open === false) {
        libelle.setAttribute("aria-expanded", true);
        contenu.setAttribute("tabindex", 0);
      } 
      if (this === item && this.open === true) {
        libelle.setAttribute("aria-expanded", false);
        contenu.setAttribute("tabindex", -1);
      } 
    }
  }

const detailsElements = document.querySelectorAll("details");
if(detailsElements) {
 allyDetails(detailsElements);
 detailsElements.forEach(item => {item.addEventListener("click", handleClickOnDetails);});
}