Petit topo sur les menus déroulants en CSS

Point de départ : menu pas déroulant

Il s'agit d'un menu dont seuls les items secondaires sont des liens (pas les titres de catégorie). J'ai choisi d'utiliser des sections contenant un titre et une liste, plutôt que de faire une liste de listes comme on le voit souvent.

	<nav class="menu">
		<section class="categorie">
			<h3>Rubans</h3>
			<ul>
				<li><a href="#">Tagliatelle</a></li>
				<li><a href="#">Fettuccine</a></li>
				<li><a href="#">Lasagne</a></li>
			</ul>
		</section>
		<section class="categorie">
			<h3>Ficelles</h3>
			<ul>
				<li><a href="#">Spaghetti</a></li>
				<li><a href="#">Spaghettoni</a></li>
				<li><a href="#">Vermicelli</a></li>
				<li><a href="#">Capellini</a></li>
			</ul>
		</section>
		<section class="categorie">
			<h3>Tubes</h3>
			<ul>
				<li><a href="#">Macaroni</a></li>
				<li><a href="#">Cannelloni</a></li>
				<li><a href="#">Penne</a></li>
				<li><a href="#">Cavattapi</a></li>
			</ul>
		</section>
		<section class="categorie">
			<h3>Autres</h3>
			<ul>
				<li><a href="#">Farfalle</a></li>
				<li><a href="#">Fusilli</a></li>
				<li><a href="#">Ravioli</a></li>
				<li><a href="#">Gnocchi</a></li>
				<li><a href="#">Conchigliette</a></li>
			</ul>
		</section>
	</nav>

Pour le CSS, j'utilise du inline-block pour simplifier l'exemple.

.menu {
	border: 1px solid black;
}

.menu .categorie {
	border: 1px solid red;
	display: inline-block;
	vertical-align: top;
}

.menu .categorie ul {
	background-color: lightgreen;
}

NB : j'ai mis une classe categorie aux catégories, que j'utilise dans le CSS au lieu du sélecteur d'élément section. Cela rend le CSS plus clair et moins dépendant du HTML.

Menu déroulant de base

On met tout simplement le sous-menu en display:none par défaut, et en display:block quand son parent (la section de classe categorie) est survolé.

.menu .categorie ul {
	display: none;
}

.menu .categorie:hover ul {
	display: block;
}

Cependant, ça ne suffit pas, car l'apparition des sous-menus décale le reste de la page.

Menu déroulant propre

.menu .categorie ul {
	position: absolute;
}

Pour éviter le problème de la section précédente, j'ai mis les sous-menus en position:absolute. Comme je n'ai pas utilisé de décalage (avec les propriétés top, left, etc.), ils ne changent pas de position ; cependant, ils sont sortis du « flot » normal de la page, leur apparition ne fait donc rien bouger.

On peut cependant remarquer que leur largeur ne dépend plus de celle de leur parent. On va voir deux solutions dans la suite.


Habillage du menu

Le CSS ci-dessous reprend les quelques règles permettant d'obtenir un menu déroulant, et ajoute un peu d'habillage au menu. En particulier, on prend soin de présenter de la même façon les éléments h3 (titres des catégories) et les liens eux-mêmes (sauf en ce qui concerne le survol).

On utilise un width fixé (ici à 10em) pour que les sous-menus soient de même taille que les titres de catégorie.

.menu {
	text-align: center;
}

.menu .categorie {
	display: inline-block;
	vertical-align: top;
	background: lightblue;
}

.menu .categorie ul {
	display: none;
	position: absolute;
	padding: 0;
	margin: 0;
	list-style: none;
}

.menu .categorie:hover ul {
	display: block;
}

.menu h3,
.menu a {
	display: block;
	margin: 0;
	padding: .5em 1.5em;
	font-size: inherit;
	color: black;
	background-color: lightblue;
	text-decoration: none;
	box-sizing: border-box;
	width: 10em;
}

.menu .categorie:hover h3 {
	color: white;
	background-color: steelblue;
}

.menu a:hover {
	color: white;
	background-color: skyblue;
}

Même chose avec des flottants

Utiliser inline-block est pratique, mais n'est pas très propre pour une mise en page précise, car les espaces dans le HTML apparaissent dans le rendu entre les items.

Le CSS ci-dessous utilise des flottants pour créer un menu déroulant dont chaque item fait exactement 25% de la largeur disponible.

.menu {
	text-align: center;
}

.menu::after {
	/* «clearfix», pour que le menu contienne les flottants */
	display: block;
	content: "";
	clear: both;
}

.menu .categorie {
	box-sizing: border-box;
	width: 25%;
	float: left;
	background: lightblue;
	position: relative;  /* pour être la «racine» du sous-menu en absolute */
}

.menu .categorie ul {
	display: none;
	position: absolute;
	padding: 0;
	margin: 0;
	list-style: none;
	width: 100%;  /* pour avoir la même largeur que la «racine» */
}

.menu .categorie:hover ul {
	display: block;
}

.menu h3,
.menu a {
	display: block;
	margin: 0;
	padding: .5em 1.5em;
	font-size: inherit;
	color: black;
	background-color: lightblue;
	text-decoration: none;
	box-sizing: border-box;
}

.menu .categorie:hover h3 {
	color: white;
	background-color: steelblue;
}

.menu a:hover {
	color: white;
	background-color: skyblue;
}

Plusieurs points à noter :

Pour bien comprendre tout cela, le plus efficace est de tester soi-même : enlevez le clearfix et mettez le menu en overflow:auto ; enlevez le position:relative des catégories ; et regardez ce qui se passe !


Avec flexbox et des animations

Cet exemple utilise flexbox au lieu des flottants (ça ne change pas grand'chose, à part qu'on n'a plus besoin du clearfix), et ajoute une animation au déroulement du menu. Comme on ne peut pas animer le passage de display:none à display:block, il faut choisir un autre moyen de cacher le menu. Une façon classique est de mettre le max-height à 0 par défaut, et à une grande valeur au survol (ici 50em : si votre sous-menu est plus haut que 50 lignes de texte, vous avez de toute façon un problème d'ergonomie à régler). Évidemment il faut aussi cacher le contenu qui dépasse (overflow:hidden), pour que rien n'apparaisse par défaut.

NB : j'ai utilisé une transition instantanée pour le repliement, c'est plus agréable à manipuler. De manière générale, c'est une bonne idée éviter de continuer à montrer trop longtemps à l'internaute quelque chose qu'il ou elle ne veut plus voir.

.menu {
	text-align: center;
	display: flex;
}

.menu .categorie {
	flex: 1;
	background: lightblue;
	position: relative;  /* pour être la «racine» du sous-menu en absolute */
}

.menu .categorie ul {
	max-height: 0;
	overflow: hidden;
	transition: 0;
	position: absolute;
	padding: 0;
	margin: 0;
	list-style: none;
	width: 100%;  /* pour avoir la même largeur que la «racine» */
}

.menu .categorie:hover ul {
	max-height: 50em;
	transition: 1s;
}

.menu h3,
.menu a {
	display: block;
	margin: 0;
	padding: .5em 1.5em;
	font-size: inherit;
	color: black;
	background-color: lightblue;
	text-decoration: none;
	box-sizing: border-box;
}

.menu .categorie:hover h3 {
	color: white;
	background-color: steelblue;
}

.menu a:hover {
	color: white;
	background-color: skyblue;
}