Comment créer des menus animés qui restent accessibles ?

Les effets de navigation rendent menus, onglets et infobulles plus faciles à suivre : soulignement qui glisse vers le lien actif, menu hamburger qui se transforme en croix, menus déroulants et accordéons qui s'ouvrent en douceur. Ils utilisent des transitions CSS, l'élément details ou de petits scripts, et le positionnement d'ancrage CSS, là où il est pris en charge, pour accrocher une infobulle à son déclencheur.

Comment choisir, performances et accessibilité
  • Underline Expand et Pill Indicator conviennent à un en-tête de site, Morphing Hamburger à un menu mobile, Tab Slider à un sélecteur de formules ou de fonctionnalités, et Sticky Header Detection à une page longue.
  • Chaque menu doit s'ouvrir, se fermer avec Échap et déplacer correctement le focus au clavier, exposer son état avec aria-expanded et rester utilisable sans animation.
  • Underline Expand, Accordion Basic, Smart Tooltip System et Connected Nodes sont gratuits avec leur code complet ; les autres composants de navigation sont inclus dans Premium.

Underline Expand

Ligne qui s'étend depuis le centre.

CSS Center
Page de l’effet →
Voir le code source
HTML
<div class="demo-preview">
  <div class="nav-demo">
    <span class="nav-link-demo link-underline-expand">Accueil</span>
    <span class="nav-link-demo link-underline-expand">Services</span>
    <span class="nav-link-demo link-underline-expand">Contact</span>
  </div>
</div>
CSS
.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
  box-sizing: border-box
}

.demo-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 250px;
  overflow: hidden;
  position: relative;
  background: rgb(26, 26, 36);
  color: rgb(255, 255, 255);
  font-family: sans-serif;
}

/* Variables du design system du site, dont ce code dépend (valeurs de css/style.css — à adapter librement). */
.demo-preview {
  --gradient-primary: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #d946ef 100%);
  --space-2: 0.5rem;
  --space-8: 2rem;
  --text-primary: #ffffff;
  --text-secondary: #a1a1aa;
}

.nav-demo {
  display: flex;
  gap: var(--space-8);
  align-items: center;
}

.nav-link-demo {
  color: var(--text-secondary);
  font-weight: 500;
  padding: var(--space-2) 0;
  position: relative;
  cursor: pointer;
  transition: color 0.3s;
}

.nav-link-demo:hover {
  color: var(--text-primary);
}

.link-underline-expand::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 50%;
  width: 0;
  height: 2px;
  background: var(--gradient-primary);
  transition: all 0.3s ease;
  transform: translateX(-50%);
}

.link-underline-expand:hover::after {
  width: 100%;
}
JavaScript
/* CSS pur — aucun JavaScript requis */

Underline Slide

Ligne qui glisse de droite à gauche.

CSS Slide

Background Fill

Fond qui se remplit au survol.

CSS Fill

Bracket Hover

Crochets qui apparaissent au survol.

CSS Text

Hamburger Menu

Animation hamburger vers croix.

CSS Toggle

Pill Indicator

Indicateur animé qui suit la sélection.

JS Tab

Dropdown Reveal

Menu déroulant avec animation fade/slide.

CSS Dropdown

Morphing Hamburger

Icône hamburger qui se transforme en X avec animation fluide.

CSS Morph

Tab Slider

Onglets avec indicateur glissant qui suit l'onglet actif.

JS Slider

Breadcrumb Animated

Fil d'Ariane animé avec chevrons interactifs.

CSS Breadcrumb

Sticky Header Detection

Header sticky avec Intersection Observer qui détecte le scroll et change de style.

JS Sticky Observer

Custom Bezier Transition

Transition de couleur avec courbe cubic-bezier personnalisée pour un mouvement fluide.

CSS Bezier Smooth

Floating Indicator

Indicateur flottant avec transition cubic-bezier fluide. Inspiré de CTA Gallery.

JS Nav Smooth

Stagger Menu Reveal

Menu avec animation staggered - chaque item apparaît avec un délai progressif.

CSS Stagger Reveal
Contenu de la première section avec animation de hauteur fluide.
Deuxieme section avec du contenu different.
Troisieme et dernière section.

Accordion Basic

Sections extensibles avec animation de hauteur fluide. Plusieurs sections peuvent être ouvertes.

CSS JS Interactive
Page de l’effet →
Voir le code source
HTML
<div class="demo-preview">
  <div class="component-demo">
    <div class="accordion" id="accordionBasic">
      <div class="accordion-item active">
        <button class="accordion-header" onclick="toggleAccordion(this, 'basic')">
          Section 1
          <svg class="accordion-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
            <path d="M6 9l6 6 6-6"/>
          </svg>
        </button>
        <div class="accordion-content">
          <div class="accordion-body">
            Contenu de la première section avec animation de hauteur fluide.
          </div>
        </div>
      </div>
      <div class="accordion-item">
        <button class="accordion-header" onclick="toggleAccordion(this, 'basic')">
          Section 2
          <svg class="accordion-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
            <path d="M6 9l6 6 6-6"/>
          </svg>
        </button>
        <div class="accordion-content">
          <div class="accordion-body">
            Deuxieme section avec du contenu different.
          </div>
        </div>
      </div>
      <div class="accordion-item">
        <button class="accordion-header" onclick="toggleAccordion(this, 'basic')">
          Section 3
          <svg class="accordion-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
            <path d="M6 9l6 6 6-6"/>
          </svg>
        </button>
        <div class="accordion-content">
          <div class="accordion-body">
            Troisieme et dernière section.
          </div>
        </div>
      </div>
    </div>
  </div>
</div>
CSS
.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
  box-sizing: border-box
}

.demo-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 250px;
  overflow: hidden;
  position: relative;
  background: rgb(26, 26, 36);
  color: rgb(255, 255, 255);
  font-family: sans-serif;
}

.component-demo {
  width: 100%;
  min-height: 200px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
}

.accordion {
  width: 100%;
  max-width: 350px;
}

.accordion-item {
  background: rgba(255, 255, 255, 0.05);
  border-radius: 8px;
  margin-bottom: 8px;
  overflow: hidden;
}

.accordion-header {
  width: 100%;
  padding: 16px 20px;
  background: transparent;
  border: none;
  color: white;
  font-size: 0.95rem;
  font-weight: 500;
  font-family: inherit;
  display: flex;
  justify-content: space-between;
  align-items: center;
  cursor: pointer;
  transition: background 0.3s ease;
}

.accordion-header:hover {
  background: rgba(255, 255, 255, 0.05);
}

.accordion-icon {
  transition: transform 0.3s ease;
}

.accordion-item.active .accordion-icon {
  transform: rotate(180deg);
}

.accordion-content {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.4s ease, padding 0.3s ease;
}

.accordion-item.active .accordion-content {
  max-height: 200px;
}

.accordion-body {
  padding: 0 20px 16px;
  color: rgba(255, 255, 255, 0.6);
  font-size: 0.9rem;
  line-height: 1.6;
}
JavaScript
(function() {
  function toggleAccordion(header, type) {
    const item = header.parentElement;
    const accordion = item.parentElement;

    if (type === 'single') {
      // Close all other items in single mode
      accordion.querySelectorAll('.accordion-item').forEach(otherItem => {
        if (otherItem !== item) {
          otherItem.classList.remove('active');
        }
      });
    }

    item.classList.toggle('active');
  }
  window.toggleAccordion = toggleAccordion;
})();

Accordion Single

Une seule section ouverte à la fois. Les autres se ferment automatiquement.

CSS JS Exclusive

Tabs Animated

Onglets avec indicateur qui glisse entre les sélections.

CSS JS Transition

Toast Success

Notification de succès verte avec animation de glissement. Disparait après 3 secondes.

JS Animation Feedback

Toast Error

Notification d'erreur rouge pour alerter l'utilisateur. Auto-dismiss après 3 secondes.

JS Animation Alert

Toast Stack

Plusieurs toasts s'empilent avec un espacement élégant.

JS Stack Queue

Skeleton Card

Placeholder de carte avec effet de scintillement pendant le chargement.

CSS Loading Shimmer

Skeleton Text

Placeholder de texte avec animation de chargement élégante.

CSS Loading Placeholder

Tooltip Fade

Infobulle qui apparait en fondu au survol.

CSS Hover Fade

Tooltip Arrow

Infobulle avec pointeur flèche et animation de levée.

CSS Hover Arrow

Modal Slide

Modal qui glisse depuis le bas de l'écran avec overlay flou.

CSS JS Overlay

Dropdown Animated

Menu déroulant avec animation d'apparition en cascade.

CSS JS Stagger

Smart Tooltip System

Tooltips qui s'inversent automatiquement selon la position dans le viewport avec position-try-fallbacks.

CSS JS Anchor
Page de l’effet →
Voir le code source
HTML
<div class="demo-preview">
  <div class="anchor-demo">
    <div class="tooltip-scene" id="tooltipScene">
      <button class="anchor-tooltip-trigger" data-tooltip-pos="top">
        Top
        <span class="smart-tooltip">Info au-dessus</span>
      </button>
      <button class="anchor-tooltip-trigger" data-tooltip-pos="top">
        Edge
        <span class="smart-tooltip">Auto-flip si hors viewport</span>
      </button>
      <button class="anchor-tooltip-trigger" data-tooltip-pos="top">
        Smart
        <span class="smart-tooltip">Position adaptative</span>
      </button>
    </div>
  </div>
</div>
CSS
.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
  box-sizing: border-box
}

.demo-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 250px;
  overflow: hidden;
  position: relative;
  background: rgb(26, 26, 36);
  color: rgb(255, 255, 255);
  font-family: sans-serif;
}

.anchor-demo {
  position: relative;
  width: 100%;
  height: 220px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.anchor-demo * {
  box-sizing: border-box;
}

.tooltip-scene {
  display: flex;
  gap: 32px;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  padding: 20px;
}

.anchor-tooltip-trigger {
  position: relative;
  padding: 10px 18px;
  background: linear-gradient(135deg, #6366f1, #8b5cf6);
  color: #fff;
  border: none;
  border-radius: 8px;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
  font-family: inherit;
}

.anchor-tooltip-trigger .smart-tooltip {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(100% + 10px);
  padding: 8px 14px;
  background: #1e1b4b;
  color: #e0e7ff;
  font-size: 0.78rem;
  font-weight: 500;
  border-radius: 8px;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s ease, transform 0.2s ease;
  z-index: 100;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
}

.anchor-tooltip-trigger .smart-tooltip::after {
  content: '';
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  top: 100%;
  border: 6px solid transparent;
  border-top-color: #1e1b4b;
}

.anchor-tooltip-trigger:hover .smart-tooltip,
.anchor-tooltip-trigger:focus .smart-tooltip {
  opacity: 1;
}
JavaScript
/* CSS pur — aucun JavaScript requis */
A
B
C
D

Connected Nodes

Noeuds draggables connectés par des lignes SVG qui se mettent à jour en temps réel.

SVG JS Drag
Page de l’effet →
Voir le code source
HTML
<div class="demo-preview">
  <div class="anchor-demo">
    <div class="nodes-scene" id="nodesScene">
      <svg class="node-lines">
        <line id="line-ab" x1="0" y1="0" x2="0" y2="0"/>
        <line id="line-bc" x1="0" y1="0" x2="0" y2="0"/>
        <line id="line-cd" x1="0" y1="0" x2="0" y2="0"/>
        <line id="line-da" x1="0" y1="0" x2="0" y2="0"/>
      </svg>
      <div class="node-point node-a" data-node="a">A</div>
      <div class="node-point node-b" data-node="b">B</div>
      <div class="node-point node-c" data-node="c">C</div>
      <div class="node-point node-d" data-node="d">D</div>
    </div>
  </div>
</div>
CSS
.anchor-demo  {
   position: relative;
   width: 100%;
   height: 220px;
   display: flex;
   align-items: center;
   justify-content: center;
   overflow: hidden;
   }

.anchor-demo *  {
   box-sizing: border-box;
   }

.nodes-scene  {
   position: relative;
   width: 100%;
   height: 100%;
   }

.nodes-scene svg.node-lines  {
   position: absolute;
   inset: 0px;
   width: 100%;
   height: 100%;
   pointer-events: none;
   }

.nodes-scene svg.node-lines line  {
   stroke: rgb(129, 140, 248);
   stroke-width: 2;
   stroke-dasharray: 6, 4;
   opacity: 0.6;
   }

.node-point  {
   position: absolute;
   width: 48px;
   height: 48px;
   border-radius: 50%;
   display: flex;
   align-items: center;
   justify-content: center;
   font-size: 0.75rem;
   font-weight: 700;
   color: rgb(255, 255, 255);
   cursor: grab;
   user-select: none;
   z-index: 2;
   transition: box-shadow 0.2s;
   }

.node-point:active  {
   cursor: grabbing;
   }

.node-point:hover  {
   box-shadow: rgba(129, 140, 248, 0.6) 0px 0px 20px;
   }

.node-a  {
   background: linear-gradient(135deg, rgb(99, 102, 241), rgb(129, 140, 248));
   top: 20%;
   left: 15%;
   }

.node-b  {
   background: linear-gradient(135deg, rgb(236, 72, 153), rgb(244, 114, 182));
   top: 60%;
   left: 55%;
   }

.node-c  {
   background: linear-gradient(135deg, rgb(16, 185, 129), rgb(52, 211, 153));
   top: 25%;
   left: 70%;
   }

.node-d  {
   background: linear-gradient(135deg, rgb(245, 158, 11), rgb(251, 191, 36));
   top: 70%;
   left: 20%;
   }
JavaScript
(function() {
  // ============================================
  // 2. Connected Nodes - draggable with SVG lines
  // ============================================
  (function(){
    var scene = document.getElementById('nodesScene');
    if(!scene) return;
    var nodes = scene.querySelectorAll('.node-point');
    var lineAb = document.getElementById('line-ab');
    var lineBc = document.getElementById('line-bc');
    var lineCd = document.getElementById('line-cd');
    var lineDa = document.getElementById('line-da');
    if(!lineAb || !lineBc || !lineCd || !lineDa) return;
    var lines = { ab: lineAb, bc: lineBc, cd: lineCd, da: lineDa };
    var nodeMap = {};
    nodes.forEach(function(n){ nodeMap[n.dataset.node] = n; });

    function getCenter(el){
      var r = el.getBoundingClientRect();
      var sr = scene.getBoundingClientRect();
      return { x: r.left - sr.left + r.width/2, y: r.top - sr.top + r.height/2 };
    }
    function updateLines(){
      var a=getCenter(nodeMap.a), b=getCenter(nodeMap.b), c=getCenter(nodeMap.c), d=getCenter(nodeMap.d);
      lines.ab.setAttribute('x1',a.x); lines.ab.setAttribute('y1',a.y);
      lines.ab.setAttribute('x2',b.x); lines.ab.setAttribute('y2',b.y);
      lines.bc.setAttribute('x1',b.x); lines.bc.setAttribute('y1',b.y);
      lines.bc.setAttribute('x2',c.x); lines.bc.setAttribute('y2',c.y);
      lines.cd.setAttribute('x1',c.x); lines.cd.setAttribute('y1',c.y);
      lines.cd.setAttribute('x2',d.x); lines.cd.setAttribute('y2',d.y);
      lines.da.setAttribute('x1',d.x); lines.da.setAttribute('y1',d.y);
      lines.da.setAttribute('x2',a.x); lines.da.setAttribute('y2',a.y);
    }
    updateLines();

    var dragging = null, offsetX = 0, offsetY = 0;
    nodes.forEach(function(n){
      n.addEventListener('mousedown', function(e){
        e.preventDefault();
        dragging = n;
        var r = n.getBoundingClientRect();
        offsetX = e.clientX - r.left;
        offsetY = e.clientY - r.top;
      });
      n.addEventListener('touchstart', function(e){
        e.preventDefault();
        dragging = n;
        var r = n.getBoundingClientRect();
        var t = e.touches[0];
        offsetX = t.clientX - r.left;
        offsetY = t.clientY - r.top;
      }, {passive:false});
    });
    function onMove(cx, cy){
      if(!dragging) return;
      var sr = scene.getBoundingClientRect();
      var x = cx - sr.left - offsetX;
      var y = cy - sr.top - offsetY;
      x = Math.max(0, Math.min(sr.width - 48, x));
      y = Math.max(0, Math.min(sr.height - 48, y));
      dragging.style.left = x + 'px';
      dragging.style.top = y + 'px';
      updateLines();
    }
    document.addEventListener('mousemove', function(e){ onMove(e.clientX, e.clientY); });
    document.addEventListener('touchmove', function(e){ if(dragging){ e.preventDefault(); onMove(e.touches[0].clientX, e.touches[0].clientY); }}, {passive:false});
    document.addEventListener('mouseup', function(){ dragging = null; });
    document.addEventListener('touchend', function(){ dragging = null; });

    // Animate initial line draw
    setTimeout(updateLines, 100);
  })();
})();

Floating Action Labels

Labels flottants ancrés aux boutons d'action qui apparaissent au survol avec repositionnement au scroll.

CSS Hover Position

Popover Menu Chain

Menus popover chainés avec sous-menus positionnés relativement au parent via le Popover API pattern.

JS Popover Menu

Annotation Layer

Annotations flottantes ancrées à des zones de contenu spécifiques avec empilage intelligent.

CSS Hover Annotation

Dynamic Badge Positioning

Badges de notification ancrés aux coins des avatars, s'adaptant au redimensionnement.

CSS Badge Responsive

Guided Tour Spotlight

Onboarding pas-à-pas avec spotlight ancré aux éléments UI et indicateur fléché.

JS Tour Anchor

Context Menu

Menu contextuel positionné au curseur avec logique d'inversion aux bords du viewport.

JS Click Position

Footnote Popover

Notes de bas de page académiques apparaissant en popover près des marqueurs de référence.

CSS Hover Popover

Chart Data Labels

Labels de données ancrés aux points du graphique avec évitement automatique des chevauchements.

CSS Hover DataViz

Command Palette ⌘K

Palette de commandes ⌘K construite sur l'API popover native : recherche filtrante, navigation clavier, light-dismiss. Le pattern SaaS moderne par excellence, sans usine à gaz JS.

💡 Idéal pour : navigation rapide d'app SaaS, recherche globale, raccourcis power-user, menu d'actions, back-office.

Popover API ⌘K Navigation Premium

Questions fréquentes

Comment créer un menu hamburger animé en CSS ? +

Utilisez 3 spans dans un bouton. Au clic, appliquez des transforms : le span du milieu disparait (opacity:0), le premier tourne de 45deg et le troisième de -45deg. Utilisez transition: all 0.3s pour une animation fluide.

Comment faire un mega-menu en CSS ? +

Placez le mega-menu en position absolute sous l'item parent. Utilisez une div invisible (pseudo-élément ::before) entre le lien et le menu pour éviter que le hover ne se perde. Animez l'apparition avec opacity et transform.

Quelle est la meilleure pratique pour un menu mobile ? +

Utilisez un panneau latéral (sidebar) qui glisse depuis la gauche ou la droite avec transform: translateX(). Ajoutez un overlay semi-transparent. Fermez au clic sur l'overlay, au bouton fermer et à la touche Echap. Bloquez le scroll du body.

Comment rendre un menu accessible au clavier ? +

Chaque item doit être navigable avec Tab. Les sous-menus s'ouvrent avec Entrée ou Flèche bas. Echap ferme le sous-menu. Ajoutez aria-expanded sur les boutons toggle et role=navigation sur le nav.

Comment créer des tabs animés en CSS ? +

Utilisez des boutons radio hidden (:checked) ou JavaScript pour gérer l'état actif. Animez un indicateur (pseudo-élément) sous l'onglet actif avec transition sur left et width. Le contenu change avec un fade ou un slide.

Catégories similaires