Menu horizontal CSS : les sprites, c’était malin en 2009, beaucoup moins en 2026

La technique des sprites CSS a longtemps été une excellente solution pour optimiser les menus à base d’image, mais en plus de 15 ans, le Web a bien évolué. En 2026, on peut (on doit 🤔) construire des menus horizontaux plus souples, responsives et accessibles. Dans cet article, on va voir comment recréer l’idée d’un « menu horizontal avec sprites CSS » en utilisant des techniques modernes, en gardant les mêmes objectifs qu’à l’époque : esthétique et performance.

Pourquoi les sprites CSS ne sont plus la solution par défaut

La technique des sprites consistait à regrouper tous les états graphiques (normal, survol, actif) dans une seule grande image, puis à afficher la bonne portion via background-position. Ça réduisait le nombre de requêtes HTTP nécessaires, ce qui était crucial avant l’arrivée de HTTP/2.

Aujourd’hui, cette approche a plusieurs limites pour un menu de navigation, même si elle fonctionne toujours :

  • Maintenance lourde : chaque changement de pictogramme ou de texte implique de recalculer les positions.
  • Mauvaise adaptation aux écrans haut densité (Retina, 4K), sauf en multipliant les versions d’images.
  • Accessibilité limitée : le texte est souvent masqué au profit d’images purement décoratives.
  • Utilité réduite : HTTP/2, les formats d’image modernes (WebP, SVG) rendent ce gain de requêtes moins critique.

La technique des sprites reste pertinente pour certains cas (animations, jeux en HTML5), mais pour un simple menu de navigation, des alternatives modernes font clairement mieux le job aujourd’hui.

Structure HTML moderne

La base n’a pas fondamentalement changé depuis 2009 : une liste non ordonnée reste un excellent choix pour construire une navigation. En revanche, on va profiter d’HTML5, des attributs ARIA et des bonnes pratiques en matière d’accessibilité.

<nav class="main-nav" aria-label="Navigation principale">
    <a class="main-nav__logo" href="/">
        <span class="sr-only">Retour à l'accueil</span>
        <!-- Logo SVG inline ou image -->
        <svg aria-hidden="true" viewBox="0 0 100 20">
            <!-- ... -->
        </svg>
    </a>
    
    <button class="main-nav__toggle" aria-expanded="false" aria-controls="main-menu">
        <span class="sr-only">Ouvrir le menu</span>
        <span class="main-nav__toggle-bar"></span>
        <span class="main-nav__toggle-bar"></span>
        <span class="main-nav__toggle-bar"></span>
    </button>

    <ul id="main-menu" class="main-nav__list">
        <li class="main-nav__item">
            <a class="main-nav__link is-active" href="/" aria-current="page">Home</a>
        </li>
        <li class="main-nav__item">
            <a class="main-nav__link" href="/services">Services</a>
        </li>
        <li class="main-nav__item">
            <a class="main-nav__link" href="/references">Références</a>
        </li>
        <li class="main-nav__item">
            <a class="main-nav__link" href="/contact">Contact</a>
        </li>
    </ul>
</nav>

Quelques points importants :

  • Utilisation de <nav> et aria-label pour expliciter le rôle de la zone.
  • Un bouton de type « hamburger » pour le mobile, relié à la liste via aria-controls.
  • Une classe is-active pour gérer l’onglet actif côté CSS.

Mise en page avec Flexbox (bye-bye float !)

Là où mon article d’origine utilisait float: left sur les <li>, on peut aujourd’hui aligner le menu avec Flexbox. Plus simple et plus… flexible ! (ouais, je suis un marrant 🤣).

.main-nav {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 2rem;
    padding: 0.75rem 1.5rem;
    background: linear-gradient(90deg, #1f2933, #111827);
    color: #ffffff;
}

.main-nav__logo {
    display: inline-flex;
    align-items: center;
    text-decoration: none;
    color: inherit;
}

.main-nav__list {
    display: flex;
    align-items: center;
    gap: 1.5rem;
    list-style: none;
    margin: 0;
    padding: 0;
}

.main-nav__link {
    position: relative;
    display: inline-flex;
    align-items: center;
    padding: 0.25rem 0;
    font-weight: 500;
    color: #e5e7eb;
    text-decoration: none;
    transition: color 200ms ease-out;
}

Ici, la liste devient une simple rangée flex, ce qui rend l’ajout ou la suppression d’éléments beaucoup plus naturel que la gestion de coordonnées de sprites. On peut aussi, si besoin, passer sur CSS Grid pour gérer un en-tête plus complexe.

Effets de survol modernes

Au lieu de gérer les états normal/hover/actif via une image sprite, on peut recréer des effets visuels sophistiqués en pur CSS : soulignement animé, fond dégradé, « pill » qui se déplace, etc.

Exemple : soulignement animé avec un pseudo-élément

.main-nav__link::after {
    content: "";
    position: absolute;
    left: 0;
    bottom: -0.2rem;
    width: 100%;
    height: 2px;
    border-radius: 999px;
    background: linear-gradient(90deg, #f59e0b, #ec4899);
    transform-origin: center;
    transform: scaleX(0);
    transition: transform 0.2s ease-out;
}

.main-nav__link:hover::after,
.main-nav__link:focus-visible::after,
.main-nav__link.is-active::after {
    transform: scaleX(1);
}

.main-nav__link:hover,
.main-nav__link:focus-visible,
.main-nav__link.is-active {
    color: #fff;
}

Avec cette approche, on reproduit l’idée d’un état survolé distinct, sans aucune image et avec un rendu parfaitement net sur tous les écrans. On améliore aussi l’accessibilité en stylant :focus-visible pour la navigation au clavier.

Icônes : du sprite bitmap au SVG

Autre gros virage : la façon d’intégrer des icônes. Là où l’on utilisait auparavant une sprite PNG, on privilégie maintenant :

  • une « Icon Font » (de moins en moins recommandée),
  • des SVG inline (avec <svg> dans le HTML),
  • ou une sprite SVG via <symbol> et <use>

Exemple avec une sprite SVG

<svg aria-hidden="true" style="display:none">
    <symbol id="icon-home" viewBox="0 0 24 24">
        <!-- … -->
    </symbol>
    <symbol id="icon-services" viewBox="0 0 24 24">
        <!-- … -->
    </symbol>
</svg>

<nav class="main-nav" aria-label="Navigation principale">
    <ul class="main-nav__list">
        <li class="main-nav__item">
            <a class="main-nav__link is-active" href="#home">
                <svg class="main-nav__icon" aria-hidden="true">
                    <use href="#icon-home"></use>
                </svg>
                <span>Home</span>
            </a>
        </li>
        <!-- … -->
    </ul>
</nav>
.main-nav__icon {
    width: 1.1rem;
    height: 1.1rem;
    margin-right: 0.4rem;
    flex-shrink: 0;
}

Les avantages sont nombreux : un rendu vectoriel, une couleur facilement personnalisable en CSS (via fill et optionnellement currentColor), et plus aucune gymnastique de background-position !

Rendre le menu responsive

Un menu horizontal moderne doit s’adapter à toutes les tailles de fenêtre. On peut combiner CSS à un petit peu de JavaScript pour gérer l’ouverture et la fermeture du menu sur mobile.

CSS : basculer en mode « hamburger » sur mobile

.main-nav__toggle {
    display: none;
    border: none;
    background: transparent;
    cursor: pointer;
    padding: 0.25rem;
}

.main-nav__toggle-bar {
    display: block;
    width: 1.5rem;
    height: 2px;
    margin: 0.2rem 0;
    border-radius: 999px;
    background-color: #e5e7eb;
    transition: transform 0.2s ease-out, opacity 0.2s ease-out;
}

/* Mobile */
@media (max-width: 768px) {
    .main-nav {
        flex-wrap: wrap;
        align-items: center;
        row-gap: 0;
    }

    .main-nav__toggle {
        display: flex;
        flex-direction: column;
        margin-left: auto;
    }

    .main-nav__list {
        flex-basis: 100%;
        flex-direction: column;
        align-items: flex-start;
        gap: 0.75rem;
        max-height: 0;
        overflow: hidden;
        transition: max-height 0.25s ease-out;
    }

    .main-nav__list.is-open {
        max-height: unset;
        overflow: initial;
    }
}

JavaScript : gestion de l’état « ouvert »

const navToggle = document.querySelector('.main-nav__toggle');
const navList = document.querySelector('#main-menu');

if (navToggle && navList) {
    navToggle.addEventListener('click', () => {
        const isOpen = navToggle.getAttribute('aria-expanded') === 'true';
        navToggle.setAttribute('aria-expanded', String(!isOpen));
        navList.classList.toggle('is-open', !isOpen);
    });
}

Ce petit script suffit amplement pour un menu responsive classique, tout en respectant les attributs ARIA pour les lecteurs d’écran. Des implémentations plus avancées peuvent gérer le focus, l’échappement au clavier ou les sous-menus (dropdown, mega menu, etc.)

Accessibilité et UX, à ne surtout plus ignorer

Si mon article d’origine se concentrait surtout sur la technique des sprites et l’optimisation des requêtes HTTP, un menu moderne doit aussi intégrer de vraies bonnes pratiques d’accessibilité (et on aurait dû s’en rendre compte déjà à l’époque des mises en page en <table> 😱).

Quelques points clés :

  • Utiliser des textes visibles plutôt que de les masquer derrière une image.
  • Prévoir des styles de :focus-visible suffisamment contrastés pour la navigation clavier.
  • S’assurer que le contraste texte/fond est suffisant pour une lecture confortable.
  • Gérer l’état actif (aria-current="page") pour que l’utilisateur sache où il se trouve.

17 ans de progrès en un menu

En 2009, les sprites CSS étaient une astuce magique pour booster les performances d’un menu horizontal un peu « design ». Aujourd’hui, grâce à Flexbox, SVG, des CSS modernes et un peu de JavaScript (bye jQuery ! 🤭), on obtient un résultat plus performant, plus accessible et infiniment plus maintenable. 😎

See the Pen Untitled by Ange Chierchia (@nighcrawl) on CodePen.

Commentaires

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur la façon dont les données de vos commentaires sont traitées.

To respond on your own website, enter the URL of your response which should contain a link to this post’s permalink URL. Your response will then appear (possibly after moderation) on this page. Want to update or remove your response? Update or delete your post and re-enter your post’s URL again. (Find out more about Webmentions.)