J’ai eu l’immense plaisir de réaliser un site pour le Haut-Commissariat des Droits de l’Homme, ce site s’intitule « People with albinism, not ghosts but human beings ».
Je vous propose deux billets montrant quelques astuces sympathiques mêlant accessibilité, amélioration progressive, orthogonalité et intégration CSS. En voici le premier.
Un burger-icon avec de l’ARIA et des transitions CSS
Le site comporte un « burger-icon » qui apparait à partir de la version tablette. Grosso modo (je vous passe tous les détails), c’est un lien qui pointe vers une ancre (avant que JavaScript ne s’en mêle :) ). Un détail cependant : il doit faire apparaitre le menu de navigation avec une transition CSS. Donc, en quelque sorte, cela devient un bouton avec JavaScript.
En se basant sur ARIA comme descripteur d’interface, c’est extrêmement simple, naturel et intuitif. Via jQuery, cela nous donne à l’initialisation :
$('#displaymenu').attr({
'aria-controls' : 'navigation-container',
'aria-expanded' : 'false',
'role' : 'button'
});
// ce qui donne
<a id="displaymenu" href="#navigation" aria-controls="navigation-container" aria-expanded="false" role="button">
aria-controls
indique qu’il contrôle le conteneur ayant l’id
navigation-container
, et aria-expanded
indiquera si le menu est ouvert ou non.
Ensuite, il faudra mettre aria-hidden
sur #navigation-container
, qui indiquera que le menu est fermé. À partir de ces états, on a tout ce qu’il faut pour poser les styles nécessaires à la transition CSS.
Au début, mon idée était de faire une transition sur height
: c’est somme tout logique, la hauteur varie de height: 0
(état fermé) à height: auto
. Sauf que… cela n’est pas possible, on ne peut pas animer ainsi, il faut indiquer une valeur numérique (dura lex sed lex).
La solution est alors d’animer la propriété max-height
, elle variera de max-height: 0
(état fermé) à max-height: <une valeur suffisante mais pas trop>
pour que l’animation soit complète mais pas trop longue.
.navigation-container {
/* ici les préfixes ! */
transition: max-height 1s ease;
}
.navigation-container[aria-hidden="true"] {
max-height: 0; /* caché */
}
.navigation-container[aria-hidden="false"] {
max-height: 25em; /* ouvert */
}
Comme vous pouvez le voir, les styles pour effectuer la transition sont d’une simplicité à l’épreuve des balles, la transition et les deux états (ouvert/fermé).
Maintenant, il ne reste plus qu’à créer le code pour transformer le lien vers une ancre en un bouton. Si l’on reprend le comportement du bouton, il doit s’activer :
- quand on clique dessus ;
- quand on tape entrée dessus ;
- et quand on tape espace dessus.
Avec jQuery, l’événement click
est déclenché quand on clique dessus, et comme la balise est un lien, quand on tape entrée. Il faudra donc que j’écoute quand la touche espace sera pressée sur l’élément #displaymenu
.
Autre point de détail, les liens du menu restent focusables même s’ils sont pris dans un conteneur avec max-height: 0
(il faudrait que le conteneur soit en display: none
pour qu’ils ne soient plus focusables). Autrement dit, les liens peuvent être atteints au clavier quand le menu est fermé, ce qui n’est pas très logique.
Deux solutions :
- ajouter un style comme
display: none
une fois la transition terminée (ce que je fais sur mon carrousel accessible) ; - ou rendre les liens non focusables via
tabindex="-1"
.
La seconde solution étant très simple à faire et particulièrement adaptée à mon cas, c’est celle que j’ai retenue. Ce qui nous donne :
// si l’on frappe une touche clavier
// ou si l’on clique sur le #displaymenu
$('#displaymenu').on( "click keydown", function( event ) {
var $this = $(this),
$navigation_container = $('.navigation-container'), // le conteneur de la navigation
$navigation_links = $('.navigation__link'); // les liens de navigation
// soit un click = clic ou entrée
// soit une touche = keycode 32 => espace
if ( event.type==='click' || ( event.type==='keydown' && event.keyCode == 32 ) ) {
// si le menu est fermé
if ($this.attr('aria-expanded') === 'false') {
// on l’ouvre
$this.attr('aria-expanded', 'true');
// navigation ouverte
$navigation_container.attr('aria-hidden', 'false');
// on vire les tabindex pour les liens
$navigation_links.removeAttr('tabindex');
}
else {
// on ferme
$this.attr('aria-expanded', 'false');
$navigation_container.attr('aria-hidden', 'true');
// liens non focusables
$navigation_links.attr('tabindex', '-1');
}
// on évite la propagation dans ces cas uniquement
event.preventDefault();
}
});
Et voilà comment un lien se transforme en bouton. Bien entendu, ce n’est pas parfait, c’est sûrement améliorable. J’aurais pu utiliser la balise button
qui m’aurait évité de faire tout cela : cette balise inclut par défaut les événements que j’ai dû recoder en jQuery. Autrement dit avec un button
, pour chopper le clic, la touche entrée ou la touche espace, cela se fait juste ainsi :
$('button').on( "click", function( event ) {
Et pas besoin d’ajouter role="button"
. Là, vous venez sûrement de comprendre pourquoi j’ai utilisé cette balise dans mon script hide/show (collapsible regions). Avec la sémantique, beaucoup moins d’efforts !
Voilà, nous sommes à la fin de ce premier billet. Le second est disponible : Amélioration progressive, capacités et CSS (2/2).