Menu type « menubar »

Extrait de code pour un menu déroulant horizontal utilisant les rôles aria 'menubar', 'menu' et 'menuitem'.

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

  • L’exemple proposé ici utilise uniquement des listes imbriquées ; les rôles aria suivants sont utilisés pour fournir la sémantique nécessaire d’un menu d’application :
    • role="menubar" : Représente une barre de menus (généralement horizontale).
    • role="menu" : Représente un ensemble de liens ou de commandes dans une barre de menus, il est utilisé pour les menus déroulants.
    • role="menuitem": représente un élément de menu individuel.
    • role="separator" : représente un séparateur entre deux groupes d’éléments de menu dans un menu.
  • L’item de liste parent qui permet d’afficher le sous-menu correspondant :
    • porte l’attribut aria aria-haspopup="true" pour signifier aux technologies d’assistance qu’un sous-menu est présent ;
    • porte l’attribut aria aria-expanded qui prend la valeur true si le sous-menu est affiché, false si le sous-menu n’est pas affiché.
  • Pour que ce menu fonctionne, du code javascript sera nécessaire. Le script gère :
    • le changement d’état aria-expanded ; l’affichage/disparition du sous-menu est alors géré en CSS ;
    • la disparition du sous-menu quand l’utilisateur appuie sur la touche “Echap” ou clique n’importe où dans la page. de plus, le focus revient sur le bouton à l’origine de l’action.

Visuel du menu proposé

Extraits de code

Code HTML

<nav role='navigation' aria-label="Menu principal">
<ul role="menubar" id="menuListe">
     <li role="menuitem">Accueil</li>
	<li role="menuitem" aria-haspopup="true">
		Coder accessible
		<ul role="menu">
			<li role="menuitem">Contenu</li>
			<li role="menuitem">Couleurs</li>
			<li role="menuitem">Navigation</li>
		</ul>
	</li>
	<li role="menuitem" aria-haspopup="true">
		Ressources
		<ul role="menu">
               <li role="menuitem">Accessibilité bureautique</li>
			<li role="menuitem">Accessibilité web</li>
		</ul>
	</li>
	<li role="menuitem">Contact</li>
</ul>
</nav>

Code CSS

Pour adapter la couleur de la barre de navigation et des liens, modifier les codes couleurs dans la déclaration :root.

:root {
	--BACKGROUND-color: #292929;
	--FRONT-color: #FFF;
	--FOCUS-color: #575757;
 }
 #menuliste {
	width:100%;
	display: flex;
	margin: 0;
	padding: 0;
	color: var(--FRONT-color);
	background-color: var(--BACKGROUND-color);
	padding: .25em;
}
#menuliste li {
	white-space: nowrap;
	display:block;
	padding: .25em .75em;
	margin: 0;
	border: 1px solid var(--FRONT-color);
}
#menuliste li[aria-haspopup="true"]::after {
	--icon-size: 1em;
	content: "";
	background-color: currentColor;
	float: right;
	padding-top: 1.5em;
	margin-left: .5em;
	height: var(--icon-size);
	-webkit-mask-size: 100% 100%;
	mask-size: 100% 100%;
	width: var(--icon-size);
	-webkit-mask-image: url(../images/chevron.svg);
	mask-image: url(../images/chevron.svg);
}
#menuliste > li {
	float: left;
	background-color: var(--BACKGROUND-color);
	text-align: center;
	position:relative;
	cursor: pointer;
}
#menuliste li:hover,
#menuliste li:focus {
	background-color: var(--FRONT-color);
	color: var(--BACKGROUND-color);
	border: 1px solid var(--BACKGROUND-color);
	text-decoration: underline;
}

#menuliste li:hover li,
#menuliste li:focus li {
	color: var(--FRONT-color);
	background-color: var(--BACKGROUND-color);
}

#menuliste > li > ul {
	display: none;
	position:absolute;
	left:0;
	right:0;
	top:100%;
	padding:0;
	margin:0;
	background-color: var(--BACKGROUND-color);
	width: 200%;
	text-align: left;
}

#menuliste > li[aria-expanded="true"] > ul {
	display:block;
}
#menuliste > li > ul a:hover,
#menuliste > li > ul a:focus {
	text-decoration: underline;
}

Code javascript

Le script ci-dessous sera intégré dans la page HTML via l’écriture suivante :

<script src="/js/script.js"></script>

Fichier js/script.js

var appsMenuItems = document.querySelectorAll('#menuliste > li');
var subMenuItems = document.querySelectorAll('#menuliste > li li');
var keys = {
tab:    9,
enter:  13,
esc:    27,
space:  32,
left:   37,
up:     38,
right:  39,
down:   40
};
var currentIndex, subIndex;

var gotoIndex = function(idx) {
if (idx == appsMenuItems.length) {
idx = 0;
} else if (idx < 0) {
idx = appsMenuItems.length - 1;
}
appsMenuItems[idx].focus();
currentIndex = idx;
};

var gotoSubIndex = function (menu, idx) {
var items = menu.querySelectorAll('li');
if (idx == items.length) {
idx = 0;
} else if (idx < 0) {
idx = items.length - 1;
}
items[idx].focus();
subIndex = idx;
}

Array.prototype.forEach.call(appsMenuItems, function(el, i){
if (0 == i) {
el.setAttribute('tabindex', '0');
el.addEventListener("focus", function() {
currentIndex = 0;
});
} else {
el.setAttribute('tabindex', '-1');
}
el.addEventListener("focus", function() {
subIndex = 0;
Array.prototype.forEach.call(appsMenuItems, function(el, i){
el.setAttribute('aria-expanded', "false");
});
});
el.addEventListener("click",  function(event){
if (this.getAttribute('aria-expanded') == 'false' || this.getAttribute('aria-expanded') ==  null) {
this.setAttribute('aria-expanded', "true");
} else {
this.setAttribute('aria-expanded', "false");
}
event.preventDefault();
return false;
});
el.addEventListener("keydown", function(event) {
var prevdef = false;
switch (event.keyCode) {
case keys.right:
gotoIndex(currentIndex + 1);
prevdef = true;
break;
case keys.left:
gotoIndex(currentIndex - 1);
prevdef = true;
break;
case keys.tab:
if (event.shiftKey) {
gotoIndex(currentIndex - 1);
} else {
gotoIndex(currentIndex + 1);
}
prevdef = true;
break;
case keys.enter:
case keys.down:
this.click();
subindex = 0;
gotoSubIndex(this.querySelector('ul'), 0);
prevdef = true;
break;
case keys.up:
this.click();
var submenu = this.querySelector('ul');
subindex = submenu.querySelectorAll('li').length - 1;
gotoSubIndex(submenu, subindex);
prevdef = true;
break;
case keys.esc:
document.querySelector('#escape').setAttribute('tabindex', '-1');
document.querySelector('#escape').focus();
prevdef = true;
}
if (prevdef) {
event.preventDefault();
}
});
});

Array.prototype.forEach.call(subMenuItems, function(el, i){
el.setAttribute('tabindex', '-1');
el.addEventListener("keydown", function(event) {
switch (event.keyCode) {
case keys.tab:
if (event.shiftKey) {
gotoIndex(currentIndex - 1);
} else {
gotoIndex(currentIndex + 1);
}
prevdef = true;
break;
case keys.right:
gotoIndex(currentIndex + 1);
prevdef = true;
break;
case keys.left:
gotoIndex(currentIndex - 1);
prevdef = true;
break;
case keys.esc:
gotoIndex(currentIndex);
prevdef = true;
break;
case keys.down:
gotoSubIndex(this.parentNode, subIndex + 1);
prevdef = true;
break;
case keys.up:
gotoSubIndex(this.parentNode, subIndex - 1);
prevdef = true;
break;
case keys.enter:
case keys.space:
alert(this.innerText);
prevdef = true;
break;
}
if (prevdef) {
event.preventDefault();
event.stopPropagation();
}
return false;
});
el.addEventListener("click", function(event) {
alert(this.innerHTML);
event.preventDefault();
event.stopPropagation();
return false;
});
});