Niveau Niveau confirmé

Menu accessible sur WordPress : Sous-menu

Tutorieldéveloppement

Publié par le , mis à jour le (178 lectures)

menu accessibilité wordpress sous-menu

Ajouter un sous-menu

Dans la précédente partie nous avions vu la mise en place d’un menu simple. Qu’en est-il de notre menu s’il possède plusieurs niveaux ? C’est ce que nous allons voir !

Préciser la page parente de la page courante

Pour rappel, dans le cas où notre menu possède plusieurs niveaux, la page parente de la page courante doit posséder l’attribut aria-current="true".

<a href="#" aria-current="true">Actualités</a>
<ul class="sub-menu">
  <li>
    <a href="#" aria-current="page">Sous-actualité</a>
  </li>
  <li>
    <a href="#">Sous-actualité</a>
  </li>
</ul>

Adapter sur WordPress

Nous l’avions vu dans la partie 1, notre objet $menu_item regroupe plusieurs clés. Ici, deux vont nous intéresser :

  • current_item_parent = le parent direct de la page courante.
  • current_item_ancestor = tous les ancêtres de la page courante.
Le schéma montre un menu à 2 niveaux. La classe current_item_ancestor est appliquée 2 fois tandis que current_item_parent n’apparait qu’une seule fois sur le parent direct.

Nous allons vérifier si l'élément possède la classe current_item_parent. Si la condition est respectée nous ajouterons l'attribut aria-current avec pour valeur true. Puis $atts se chargera d'ajouter l'attribut grâce au filtre nav_menu_link_attributes.

Reprenons donc notre fichier menus.php et ajoutons ceci à la fonction start_el() :

if ( $menu_item->current_item_parent ) {
    $atts['aria-current'] = $menu_item->current_item_parent ? 'true' : '';
}

Et voilà, l’attribut aria-current est ajouté dans le code lorsque $menu_item possède la clé current_item_parent.

Signaler qu’un élément possède un sous-menu

L’indication d’un sous-menu est souvent indiquée via une icône (flèche descendante ou "+", par exemple).

Exemple d’un élément de menu possédant un sous-menu : une flèche sert d’indication.

Ok, visuellement c’est compréhensible, mais comment restituer cette information aux personnes utilisant un lecteur d’écran ? Il faut une alternative HTML en plus de l’indication visuelle.

La combinaison aria-expanded/aria-controls arrive à la rescousse. Je vous invite à lire l'article sur le modèle de conception "disclosure" pour plus d'explications.

Attention à utiliser l’attribut aria-expanded sur un bouton, et non sur un lien a. Comme le dit Adrian Roselli, il n’est pas interdit de l’ajouter sur un lien mais est à éviter car est contraire à sa fonction première qui est de renvoyer vers une page.

Adapter ce comportement dans un menu WordPress

Sur WordPress nous allons cibler tous les éléments possédants un sous-menu. Pour cela, comme expliqué dans la partie 1, nos modifications seront faites au niveau de la fonction start_el().

Étape 1

Si vous avez regardé en détail les clés de $menu_item vous remarquerez qu’il n’en existe pas si l’élément possède un sous-menu.

Néanmoins, la clé classes va nous aider dans ce cas de figure.

Rappel, retrouvez dans le codex toutes les clés de $menu_item.

WordPress génère des classes CSS dont .menu-item-has-children qui, comme son nom l’indique, existe lorsque l’élément possède des enfants. On pourra vérifier l’existence de cette classe pour ajouter nos correctifs.

Étape 2

Pour indiquer que le lien possède un sous-menu nous allons donc ajouter un bouton <button>, adjacent au lien <a>. Donc, après la balise fermante </a>.

Dans notre walker, il faut modifier la fonction start_el() comme ceci :

// On vérifie si l'élément possède un sous-menu via la classe CSS "menu-item-has-children"
$item_has_children = in_array( 'menu-item-has-children', $menu_item->classes );

if ( $item_has_children ) {
  // On ajoute un bouton d'ouverture/fermeture du sous-menu
  $item_output .= '<button type="button" aria-expanded="false" aria-controls="sub-menu-item-' . $menu_item->ID . '">
                      <span aria-hidden="true">+</span>
                      <span class="sr-only">'. __('Ouvrir le sous-menu', 'text-domain') .'</span>
                  </button>';

  // On transmet l'id de l'élément dans la fonction start_lvl()
  apply_filters( 'walker_nav_menu_start_lvl', $item_output, $menu_item, $depth, $args->parent_item=$menu_item->ID );
}

Décryptons le code du bouton :

  • On lui attribue un aria-expanded="false" par défaut. Nous verrons ensuite le code JavaScript à ajouter pour passer la valeur à true lorsque le menu sera ouvert.
  • L’attribut aria-controls correspond à la valeur de l’id du sous-menu à afficher. Mais, aucun id n’est actuellement défini. Pas de panique c’est ce que nous allons faire juste après. Pour le moment nous allons faire en sorte que l’id soit sous la forme suivante : sub-menu-item-IDID est la valeur générée par $menu_item->ID.
  • Le contenu du bouton possède un texte caché (via la classe .sr-only) destiné aux lecteurs d'écran afin de restituer l'action du bouton. L’icône peut ainsi être masquée aux lecteurs d’écran (aria-hidden=”true”) car elle n’est plus que décorative.

Explication de la dernière ligne apply_filters() :

C’est ici que nous allons transmettre à la fonction start_lvl() l’id du sous-menu à afficher. Et oui, rappelez-vous c’est cette fonction qui correspond à générer le début d’un sous-menu <ul> et son contenu.

Nous allons utiliser un filtre, ici walker_nav_menu_start_lvl, qui nous permet de transmettre un argument parent_item avec pour valeur $menu_item->ID. Nous pourrons alors récupérer cet argument dans la fonction start_lvl().

Étape 3

Récupérons donc l’id dans la fonction start_lvl() et retournons le dans le code HTML :

$id = 'id="sub-menu-item-' . $args->parent_item .'"';

$output .= "{$n}{$indent}<ul$class_names{$id}>{$n}";

L’attribut aria-controls du bouton est maintenant lié à son sous-menu !

Ajouter du JavaScript pour modifier la valeur d’aria-expanded

Pour cela W3C propose un script JS pour son tutoriel “Fly-out Menus”. Nous allons utiliser ce code comme base et l'adapter pour :

  • Modifier la valeur d’aria-expanded au clic sur le bouton : true lorsque le sous-menu est ouvert, false lorsqu’il est fermé.
  • Ajouter une classe CSS open qui servira à afficher, ou non, le menu via un fichier de style.
  • Ajouter de la documentation.

/** @type {HTMLElement[]} */ const menuItems = Array.from( document.querySelectorAll( 'li.menu-item-has-children' ) ); /** * @param {HTMLElement} el * @param {string} attr * @param {any} value */ const setAttr = ( el, attr, value ) => el.setAttribute( attr, value ); menuItems.forEach( ( el ) => { const button = el.querySelector( 'button' ); if ( button ) { button .addEventListener( 'click', function( event ) { const parent = this.parentNode; /** * Si le sous-menu est ouvert : * - on retire la classe "open" au sous-menu * - on passe l'attribut "aria-expanded" à false * - On indique visuellement que le bouton est fermé */ if ( parent && parent.classList.contains( 'open' ) ) { parent.classList.remove( 'open' ); setAttr( parent.querySelector( 'button' ), 'aria-expanded', 'false' ); parent.querySelector( 'span[aria-hidden="true"]' ).innerHTML = '+'; } else if ( parent ) { /** * Si le sous-menu est fermé : * - on ajoute la classe "open" au sous-menu * - on passe l'attribut "aria-expanded" à true * - On indique visuellement que le bouton est ouvert */ parent.classList.add( 'open' ); setAttr( parent.querySelector( 'button' ), 'aria-expanded', 'true' ); parent.querySelector( 'span[aria-hidden="true"]' ).innerHTML = '-'; } event.preventDefault(); } ); } } );

Commenter

Vous devez être inscrit et identifié pour utiliser cette fonction.

Connectez-vous (déjà inscrit)

Oubli de mot de passe ? Pas de panique, on va le retrouver

Pas encore inscrit ? C'est très simple et gratuit.