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 valeurtrue
si le sous-menu est affiché,false
si le sous-menu n’est pas affiché.
- porte l’attribut aria
- 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.
- le changement d’état
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;
});
});