Menu Desktop :

Menu Mobile :

Menu Mobile déroulé :

Ce que l’on va construire
Ce chapitre est différent des précédents : ce n’est pas un exercice où vous devez trouver la solution seul, mais une construction guidée, étape par étape. On va assembler des briques déjà connues (Flexbox, media queries, position) avec une toute nouvelle brique : un peu de JavaScript.
Le résultat final, en langage naturel : sur un grand écran, un menu de navigation s’affiche horizontalement, avec le nom du site à gauche et les liens centrés dans la barre. Sur un petit écran (mobile), ce menu horizontal disparaît et laisse place à un simple bouton ☰ en haut à droite. En cliquant sur ce bouton, la liste des liens apparaît en dessous, empilée verticalement ; en cliquant à nouveau, elle se referme.
Étape 0 : Pourquoi JavaScript, ici et maintenant ?
Jusqu’ici, tout ce que vous avez fait réagissait automatiquement : une couleur qui change au survol avec :hover, une transition qui se déclenche… le navigateur gère ça tout seul, sans qu’on ait besoin de lui dire quoi faire à chaque fois.
Mais un clic qui doit déclencher une action précise — afficher ou masquer un menu — c’est différent. Le CSS seul ne sait pas nativement réagir à un clic pour changer l’affichage d’un autre élément de la page. C’est exactement ce que JavaScript sait faire : écouter un événement précis (ici, un clic sur le bouton) et modifier la page en réaction.
Note : il existe une astuce permettant de faire un menu burger en CSS pur (sans JavaScript), mais elle repose sur un détournement d’une case à cocher invisible, plus difficile à comprendre pour un début que quelques lignes de JavaScript claires. Ce cours choisit donc la solution JavaScript.
Étape 1 : Le menu desktop
On commence par construire le HTML, avec les balises sémantiques déjà vues au chapitre 700 : <header> pour l’en-tête du site, et <nav> pour le bloc de navigation.
<!-- header : l'en-tête du site, contient le logo et la navigation -->
<header>
<!-- nav : le bloc de navigation -->
<nav>
<!-- nom du site -->
<div class="logo">Mon Site</div>
<!-- liste des liens de navigation -->
<ul class="nav-links">
<li><a href="#accueil">Accueil</a></li>
<li><a href="#projets">Projets</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</nav>
</header>
Côté CSS, on commence par deux réglages généraux, valables pour toute la page :
/* reset de base, déjà vu au chapitre 105 */
* {
box-sizing: border-box;
}
/* supprime la marge par défaut du navigateur autour de la page, et applique une police sans-serif */
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
}
Pourquoi ces deux lignes ? Sans font-family, le navigateur affiche le texte avec sa police par défaut, qui est souvent une police avec empattements (des petits traits en bout de lettre, comme Times New Roman) — pas très adaptée à une interface web moderne. On force donc une police sans-serif (sans empattements), avec la même pile de polices système déjà utilisée dans les cours HTML/CSS. Quant à margin: 0; sur body, il supprime une petite marge blanche que certains navigateurs ajoutent par défaut autour de toute la page, ce qui évite un décalage disgracieux.
Vient ensuite le CSS du menu lui-même :
/* fond sombre de l'en-tête */
header {
background-color: #1e1e1e;
padding: 15px 20px;
/* nécessaire pour que .nav-links puisse se centrer par rapport à toute la largeur du header, voir plus bas */
position: relative;
}
/* logo et liens sur la même ligne, alignés verticalement au centre */
nav {
display: flex;
justify-content: space-between;
align-items: center;
}
/* nom du site */
.logo {
color: #fff;
font-weight: bold;
font-size: 1.2rem;
}
/* liste des liens, alignés horizontalement avec un espace entre eux en utilisant GAP */
.nav-links {
display: flex;
gap: 20px;
list-style: none;
margin: 0;
padding: 0;
/* sort l'élément du flux normal, pour pouvoir le positionner librement par rapport au header (voir position:relative sur header ci-dessus) */
position: absolute;
/* place le BORD GAUCHE de l'élément à 50% de la largeur du header (donc au milieu) */
left: 50%;
/* recule ensuite l'élément vers la gauche de la moitié de SA PROPRE largeur, pour que ce soit son CENTRE (et non son bord gauche) qui tombe au milieu du header */
transform: translateX(-50%);
}
/* couleur et suppression du soulignement sur les liens */
.nav-links a {
color: #fff;
text-decoration: none;
}
Nouvelle propriété : gap. Elle n’a pas été vue explicitement dans les chapitres précédents sur Flexbox. Elle sert à définir un espace fixe entre les enfants d’un conteneur flex, sans avoir besoin d’ajouter des margin sur chaque élément un par un. Ici, gap: 20px; place 20 pixels d’espace entre chaque lien de la liste.
Comment centrer les liens, en détail : on veut que .nav-links soit toujours pile au centre du header, peu importe la largeur du logo à gauche. La technique utilise trois ingrédients :
position: relative;surheader— comme vu au chapitre 140, ça ne déplace pas le header, mais ça en fait le parent positionné de référence pour tout enfant enposition: absolute;à l’intérieur.position: absolute;etleft: 50%;sur.nav-links— ça place le bord gauche de la liste de liens exactement au milieu du header. Mais à ce stade, ce n’est pas encore centré : c’est le bord gauche qui est au milieu, pas le centre de l’élément.transform: translateX(-50%);— cette ligne est nouvelle. Elle décale l’élément vers la gauche d’une distance égale à la moitié de sa propre largeur (contrairement àleft, qui se base sur la largeur du parent). C’est ce petit rattrapage qui transforme « bord gauche au milieu » en « élément réellement centré ».
À ce stade, vous avez un menu horizontal avec le logo à gauche et les liens centrés, qui s’affiche pareil quelle que soit la taille de l’écran. C’est la prochaine étape qui va introduire la différence desktop/mobile.
Étape 2 : Ajouter le bouton burger
On ajoute un bouton dans le HTML, juste après la liste de liens :
<!-- bouton visible uniquement sur mobile (voir étape 3) -->
<!-- aria-label donne un nom vocal au bouton pour les lecteurs d'écran -->
<button class="burger" aria-label="Menu">☰</button>
Deux précisions importantes :
- Le symbole
☰n’est pas une image ni une police spéciale à installer : c’est un simple caractère de texte, comme une lettre, qui existe dans le clavier Unicode. Il s’écrit et se traite exactement comme n’importe quel texte entre les balises. aria-label="Menu": ce bouton n’a pas de texte lisible (juste le symbole ☰), ce qui ne veut rien dire pour un lecteur d’écran. L’attributaria-labelfournit un nom accessible invisible à l’écran, mais annoncé vocalement — ici, la personne entendra « Menu, bouton » plutôt que rien de compréhensible.
Comment obtenir ce caractère concrètement :
- Le plus simple : copier-coller le caractère directement depuis le code fourni dans ce chapitre, comme pour toute la syntaxe déjà donnée jusqu’ici.
- Si vous devez le retaper vous-même plus tard, vous pouvez copier le caractère voulu depuis un site comme unicode-table.com.
- Alternative fiable directement dans le code : l’entité HTML équivalente,
☰, qui produit exactement le même symbole sans avoir à le taper ou le copier — même logique que©vue au chapitre 700.
Côté CSS, on stylise ce bouton et on le cache par défaut — il ne doit apparaître que sur petit écran, ce qu’on gérera à l’étape suivante :
/* caché par défaut : ne s'affichera que sur mobile, voir étape 3 */
.burger {
display: none;
background: none;
border: none;
/* supprime le padding interne que certains navigateurs ajoutent par défaut sur un bouton */
padding: 0;
/* supprime le style natif (cadre/relief) que certains navigateurs appliquent aux boutons */
appearance: none;
-webkit-appearance: none;
/* évite un espace vertical superflu autour du caractère ☰ */
line-height: 1;
color: #fff;
font-size: 1.8rem;
cursor: pointer;
}
Pourquoi ce nettoyage supplémentaire ? Un élément <button> reçoit, par défaut, un peu d’espace intérieur (padding) et parfois un léger relief ou cadre visuel propre au navigateur — même après avoir mis border: none; et background: none;. La propriété appearance: none; (avec sa variante -webkit-appearance pour compatibilité) demande au navigateur de retirer tout son style natif, pour qu’on parte d’une base neutre et qu’on contrôle nous-mêmes l’apparence du bouton.
Étape 3 : Basculer entre desktop et mobile
On utilise une media query (déjà vue au chapitre 170) pour inverser l’affichage sous un certain seuil de largeur d’écran :
/* en dessous de 768px de large : on inverse le comportement */
@media (max-width: 768px) {
/* on cache la liste de liens */
.nav-links {
display: none;
}
/* on affiche le bouton burger à la place */
.burger {
display: block;
}
}
Ce que ça fait : en dessous de 768px de largeur, la liste de liens (.nav-links) se cache, et le bouton burger (.burger) apparaît à sa place. Au-dessus de 768px, c’est l’inverse (le comportement par défaut défini aux étapes 1 et 2).
Si vous testez maintenant en réduisant la largeur de votre navigateur, le menu disparaît et le bouton ☰ apparaît — mais cliquer dessus ne fait encore rien. C’est normal, on n’a pas encore ajouté de JavaScript.
Étape 4 : Préparer le menu mobile caché
On ajoute une règle CSS supplémentaire, toujours à l’intérieur de la même media query, pour définir à quoi ressemblera le menu une fois ouvert sur mobile — empilé verticalement, sous le bouton :
@media (max-width: 768px) {
.nav-links {
display: none;
/* empile les liens verticalement au lieu d'horizontalement */
flex-direction: column;
position: absolute;
top: 60px;
left: 0;
/* on annule le centrage desktop (left: 50% / transform) vu à l'étape 1 */
transform: none;
width: 100%;
background-color: #1e1e1e;
margin: 0;
padding: 20px;
/* espace entre chaque lien empilé, comme vu à l'étape 1 */
gap: 15px;
}
/* affiché uniquement quand la classe "active" est présente */
.nav-links.active {
display: flex;
}
.burger {
display: block;
}
}
Le point clé à comprendre : .nav-links reste caché (display: none;) par défaut sur mobile. Mais une nouvelle règle .nav-links.active dit : « si cet élément possède en plus la classe active, affiche-le (display: flex;) ». Cette classe active n’existe pas encore dans votre HTML — c’est exactement ce que le JavaScript va ajouter et retirer au clic, à l’étape suivante.
Pourquoi transform: none; ici ? À l’étape 1, on a centré .nav-links sur desktop avec left: 50%; et transform: translateX(-50%);. Sur mobile, on veut au contraire que le menu déroulant occupe toute la largeur, collé à gauche (left: 0;) — donc on annule explicitement ce décalage hérité du desktop, sinon le menu mobile serait mal positionné.
Étape 5 : Le JavaScript
Phase 1 — Le JS directement dans la page
On ajoute le code suivant, tel quel, juste avant la balise fermante </body> de votre page :
<script>
// attend que toute la page soit chargée avant d'exécuter le code
document.addEventListener("DOMContentLoaded", function() {
// on récupère le bouton et la liste de liens
const burger = document.querySelector(".burger");
const navLinks = document.querySelector(".nav-links");
// au clic sur le bouton : bascule la classe "active" sur le menu
burger.addEventListener("click", function() {
navLinks.classList.toggle("active");
});
});
</script>
Ce code écoute le clic sur le bouton, et bascule (ajoute si absente, retire si présente) la classe active sur la liste de liens — exactement la classe utilisée dans le CSS de l’étape 4 pour afficher le menu.
Comme annoncé au tout début, ce cours reste HTML/CSS : ce code JavaScript est fourni tel quel, sans détailler sa syntaxe ligne par ligne. Ce qui compte ici, c’est de savoir où le placer et pourquoi.
Pourquoi juste avant </body> ? Ce script cherche des éléments (.burger, .nav-links) qui doivent déjà exister dans la page au moment où le script s’exécute. En le plaçant à la toute fin du <body>, on est certain que tout le HTML a déjà été chargé avant que le script ne s’exécute.
Attention à un malentendu : ce n’est pas la seule position possible ni une règle absolue. On pourrait aussi placer ce script dans le <head>, à condition de prendre certaines précautions techniques qu’on ne détaille pas dans ce cours. Ici, on choisit « juste avant </body> » parce que c’est la solution la plus simple à comprendre pour un premier contact avec JavaScript, pas parce que c’est la seule solution qui existe.
Phase 2 — Sortir le JS dans un fichier externe
Exactement comme pour le CSS au chapitre 35, on peut sortir ce code JavaScript de la page et le placer dans un fichier séparé, par exemple menu.js, situé au même niveau que index.html.
Contenu du fichier menu.js (exactement le même code, sans les balises <script> autour) :
// attend que toute la page soit chargée avant d'exécuter le code
document.addEventListener("DOMContentLoaded", function() {
// on récupère le bouton et la liste de liens
const burger = document.querySelector(".burger");
const navLinks = document.querySelector(".nav-links");
// au clic sur le bouton : bascule la classe "active" sur le menu
burger.addEventListener("click", function() {
navLinks.classList.toggle("active");
});
});
Dans index.html, on remplace le bloc <script>...</script> par un simple appel vers ce fichier, toujours juste avant </body> :
<!-- src pointe vers le fichier JS externe, même logique que href pour le CSS -->
<script src="menu.js"></script>
src="menu.js" fonctionne exactement comme href pour un fichier CSS ou une image : c’est le même système de chemin relatif vu au chapitre 720. Ici, menu.js signifie que le fichier est au même niveau que index.html. Si vous le rangiez dans un sous-dossier js, il faudrait écrire js/menu.js.
Étape 6 : Assembler le tout
Vous avez vu chaque brique séparément (HTML, CSS, JavaScript). Il est temps de tout assembler dans un seul fichier HTML complet. Pour cette étape d’assemblage, on garde le JavaScript directement dans la page (la Phase 1 de l’étape 5), pas en fichier externe — c’est plus simple pour tester rapidement en un seul fichier.
Voici le squelette du fichier complet. À vous de copier-coller, aux bons endroits indiqués en commentaire, les blocs de code déjà vus aux étapes précédentes :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Mon Site</title>
<style>
/* collez ici tout le CSS des étapes 1 à 4 */
</style>
</head>
<body>
<!-- collez ici le HTML (header + nav) des étapes 1 et 2 -->
<script>
/* collez ici le JavaScript de l'étape 5, Phase 1 */
</script>
</body>
</html>
Étape 7 : Tester
Ouvrez votre fichier assemblé dans le navigateur, puis réduisez progressivement la largeur de la fenêtre (ou utilisez le mode responsive de votre navigateur). Sur grand écran, le logo doit être à gauche et les liens centrés dans la barre. En dessous de 768px, le menu horizontal doit disparaître et le bouton ☰ apparaître. Un clic sur ce bouton doit faire apparaître la liste des liens empilée verticalement, sur toute la largeur ; un second clic doit la refermer.
Bloqué, ou envie de comparer votre résultat ? Cliquez sur le bouton ci-dessous pour afficher le fichier complet assemblé.