Sur les appareils mobiles, l'écran est étroit et une barre de menu horizontale tient rarement. C'est précisément là que le menu hamburger prend tout son sens : un petit bouton composé de trois traits qui, une fois touché, révèle les liens de navigation. Dans cet article, nous allons construire pas à pas une navigation mobile accessible et fluide en HTML, CSS et JavaScript pur, sans aucune bibliothèque.
Le squelette : un HTML sémantique
Un bon menu commence par un balisage sémantique qui garde du sens même lorsque JavaScript est désactivé. Écrivez le déclencheur comme un véritable <button>, et non un <div>. Ajoutez aria-expanded et aria-controls pour que les lecteurs d'écran comprennent l'état.
<header class="site-header">
<a href="/" class="logo">aslain</a>
<button id="navToggle" class="nav-toggle"
aria-expanded="false"
aria-controls="primaryNav"
aria-label="Ouvrir le menu">
<span class="bar"></span>
<span class="bar"></span>
<span class="bar"></span>
</button>
<nav id="primaryNav" class="nav">
<ul>
<li><a href="/#about">À propos</a></li>
<li><a href="/#projects">Projets</a></li>
<li><a href="/#contact">Contact</a></li>
</ul>
</nav>
</header>
Les trois <span class="bar"> sont les traits de l'icône. Les dessiner en CSS est plus léger qu'utiliser un fichier image et permet de contrôler facilement la couleur et l'animation.
Dessiner l'icône en CSS
Nous utilisons une media query pour n'afficher le bouton que sur les écrans étroits. Sur les grands écrans, le menu est déjà horizontal et le bouton hamburger reste masqué.
.nav-toggle {
display: none; /* masqué sur ordinateur */
width: 44px;
height: 44px; /* cible tactile d'au moins 44px */
background: none;
border: 0;
cursor: pointer;
}
.nav-toggle .bar {
display: block;
width: 24px;
height: 2px;
margin: 5px auto;
background: currentColor;
transition: transform .25s ease, opacity .25s ease;
}
@media (max-width: 760px) {
.nav-toggle { display: block; }
}
Visez une cible tactile d'au moins 44×44 pixels ; c'est une taille sûre, facile à atteindre du doigt et celle que recommandent les guides d'accessibilité.
La couche d'ouverture et de fermeture
Sur les écrans étroits, nous masquons le panneau du menu par défaut et le révélons quand une classe .is-open est ajoutée. Utiliser transform et visibility plutôt que display: none garde la transition de glissement fluide.
@media (max-width: 760px) {
.nav {
position: fixed;
inset: 64px 0 0 0; /* commence sous l'en-tête */
background: #0f0f12;
transform: translateX(100%);
visibility: hidden;
transition: transform .3s ease, visibility .3s;
}
.nav.is-open {
transform: translateX(0);
visibility: visible;
}
.nav ul {
display: flex;
flex-direction: column;
gap: 1.5rem;
padding: 2rem;
}
}
Transformer les trois traits en « X » indique clairement que le menu est ouvert. Nous le faisons uniquement en CSS : les barres du haut et du bas pivotent, celle du milieu disparaît.
.nav-toggle.is-active .bar:nth-child(1) {
transform: translateY(7px) rotate(45deg);
}
.nav-toggle.is-active .bar:nth-child(2) {
opacity: 0;
}
.nav-toggle.is-active .bar:nth-child(3) {
transform: translateY(-7px) rotate(-45deg);
}
JavaScript : état et accessibilité
Le seul rôle de JavaScript est de basculer les classes et de mettre à jour aria-expanded. Garder l'état dans aria-expanded comme source unique de vérité maintient le code propre.
const toggle = document.getElementById('navToggle');
const nav = document.getElementById('primaryNav');
function setOpen(open) {
toggle.setAttribute('aria-expanded', String(open));
toggle.classList.toggle('is-active', open);
nav.classList.toggle('is-open', open);
toggle.setAttribute('aria-label', open ? 'Fermer le menu' : 'Ouvrir le menu');
}
toggle.addEventListener('click', () => {
const open = toggle.getAttribute('aria-expanded') === 'true';
setOpen(!open);
});
// Fermer le menu lorsqu'un lien est touché
nav.addEventListener('click', (e) => {
if (e.target.closest('a')) setOpen(false);
});
// La touche Échap ferme le menu
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') setOpen(false);
});
Trois détails petits mais importants à remarquer :
- Fermeture au clic sur un lien : sur un site d'une seule page, il est gênant que le menu reste ouvert après que l'utilisateur a sauté vers une section.
- Prise en charge d'Échap : les utilisateurs au clavier s'attendent à ce que la touche
Escferme le menu. - Mise à jour de l'aria-label : alterner le libellé entre « ouvrir » et « fermer » donne la bonne information au lecteur d'écran.
Gestion du focus et défilement du corps
Empêcher la page derrière le menu de défiler tant qu'il est ouvert est un détail important sur mobile. Vous pouvez ajouter une classe à <body> et appliquer overflow: hidden. Déplacer le focus vers le premier lien à l'ouverture rend aussi le parcours naturel pour les utilisateurs de clavier et de lecteur d'écran.
function setOpen(open) {
/* ...lignes précédentes... */
document.body.classList.toggle('nav-locked', open);
if (open) nav.querySelector('a')?.focus();
else toggle.focus();
}
body.nav-locked { overflow: hidden; }
Renvoyer le focus vers le bouton hamburger à la fermeture permet à l'utilisateur de reprendre là où il s'était arrêté. Ce petit retour de focus est la signature d'un composant accessible.
Respecter la préférence de mouvement réduit
Certains utilisateurs choisissent de réduire les animations dans les réglages de leur système. Couper les animations pour honorer cette préférence est à la fois courtois et accessible.
@media (prefers-reduced-motion: reduce) {
.nav, .nav-toggle .bar { transition: none; }
}
Ces quelques lignes permettent aux utilisateurs sensibles au mouvement d'utiliser le menu sans transitions brusques, sans perte de fonctionnalité.
Questions fréquentes
Pourquoi utiliser un <button> plutôt qu'un <div> ?
Un vrai <button> est focalisable au clavier, fonctionne avec les touches Entrée et Espace, et est annoncé comme un « bouton » aux lecteurs d'écran. Avec un <div>, vous devez imiter tout cela à la main, ce qui signifie du code en plus et une source de bugs.
Image ou CSS pour l'icône hamburger ?
Dans la plupart des cas, dessiner trois <span> en CSS est préférable : pas de requête supplémentaire, la couleur suit le thème via currentColor, et vous pouvez animer la transformation en X directement. Le SVG est aussi un choix valable.
À quelle largeur le menu doit-il se replier ?
Il n'y a pas de règle fixe ; observez votre contenu. Placer le point de rupture là où le menu horizontal commence à ne plus tenir (souvent autour de 700–800px) donne de bons résultats. Testez votre design en rétrécissant le navigateur.
Vous voulez un menu mobile à la fois élégant et accessible ? Revoyons ensemble la navigation de votre site. Contactez-moi et parlons de votre projet.