Retour au catalogue

Breadcrumb Dropdown Nav

Fil d'Ariane avec menus deroulants sur chaque segment pour naviguer entre les pages soeurs.

breadcrumbmedium Both Responsive a11y
minimalcorporatesaasecommerceuniversalstacked
Theme

Creer un fil d'Ariane React avec menus deroulants par segment

Un fil d'Ariane React avec menus deroulants rend chaque segment de chemin comme un bouton ; les segments ayant des enfants ouvrent un panneau flottant anime avec AnimatePresence de Framer Motion, tandis qu'une detection de clic exterieur via ref le ferme automatiquement. Chaque declencheur porte aria-expanded et aria-haspopup pour une accessibilite complete.

  • Stack : React + Framer Motion 11 + Lucide React, ~225 lignes, zero dependance supplementaire.
  • API cles : AnimatePresence, motion.div avec scale + opacity + y, pattern useRef clic exterieur.
  • Accessible : aria-expanded, aria-haspopup sur chaque bouton declencheur, nav aria-label sur le conteneur.
  • Les segments s'animent en decalage au montage (0.08s de delai par index) pour un effet de construction gauche-droite.
  • Fonctionne sur mobile (layout en retour a la ligne), mais les dropdowns requierent un tap, les etats hover sont reserves au bureau.

Breadcrumb Dropdown Nav transforme un simple indicateur de chemin en outil de navigation dans la page. Chaque segment peut exposer une liste flottante de pages soeurs ou enfants, permettant aux utilisateurs de sauter lateralement dans la hierarchie sans revenir a une page de listing. Toute la barre s'anime a l'entree, et chaque panneau deroulant s'ouvre et se ferme avec une physique de spring.

Anatomie

La racine est un element nav semantique avec aria-label. A l'interieur, une rangee flex rend un DropdownSegment par entree de chemin. Chaque DropdownSegment contient trois parties : un separateur slash (ignore pour le premier segment), un bouton declencheur affichant une icone optionnelle, le libelle du segment et un ChevronDown en rotation, puis un div flottant conditionne par AnimatePresence listant les liens enfants comme balises anchor.

Comment ça marche

Chaque segment gere son propre etat ouvert/ferme avec useState. Un useEffect attache un ecouteur mousedown au document et compare la cible du clic avec le ref du segment, si l'exterieur est clique, le panneau se ferme. Le panneau de dropdown s'anime avec initial `{ opacity: 0, y: -4, scale: 0.96 }` et animate `{ opacity: 1, y: 0, scale: 1 }` via Framer Motion, utilisant l'easing type spring `[0.16, 1, 0.3, 1]`. L'icone ChevronDown elle-meme est une motion.span avec `animate={{ rotate: open ? 180 : 0 }}`, offrant un retour visuel clair sur l'etat du panneau.

Comment le coder en React

  1. Definir les types de donnees et la carte d'icones

    Commencer par deux interfaces : BreadcrumbChild (label + href) et BreadcrumbSegment (label, href optionnel, cle d'icone optionnelle, tableau children optionnel). Construire un objet iconMap qui associe les cles 'home', 'folder', 'file' a leurs composants Lucide, pour que les segments declarent une icone par nom plutot que par import direct.

    interface BreadcrumbSegment {
      label: string;
      href?: string;
      icon?: "home" | "folder" | "file";
      children?: { label: string; href: string }[];
    }
    const iconMap: Record<string, React.ElementType> = {
      home: Home, folder: Folder, file: FileText,
    };
  2. Construire le DropdownSegment avec detection du clic exterieur

    Donner a chaque segment son propre useState(false) pour ouvert/ferme. Attacher un ref au motion.div englobant, puis dans un useEffect ajouter un ecouteur mousedown sur document. Dans le handler, verifier si la cible de l'evenement est en dehors du ref, si oui, appeler setOpen(false). Retourner un cleanup qui supprime l'ecouteur. Ce pattern isole chaque segment pour que plusieurs dropdowns ne conflictualisent jamais.

    const ref = useRef<HTMLDivElement>(null);
    useEffect(() => {
      function handler(e: MouseEvent) {
        if (ref.current && !ref.current.contains(e.target as Node))
          setOpen(false);
      }
      document.addEventListener("mousedown", handler);
      return () => document.removeEventListener("mousedown", handler);
    }, []);
  3. Animer le panneau deroulant avec AnimatePresence

    Envelopper le panneau conditionnel dans AnimatePresence pour que Framer Motion puisse jouer l'animation de sortie avant le demontage. Donner au motion.div des etats initial et exit de opacity 0, y -4, et scale 0.96, animate les restaurant a 1/0/1. Utiliser le tableau d'easing [0.16, 1, 0.3, 1] pour un ressenti spring sans depassement, sans configuration spring reelle.

    <AnimatePresence>
      {open && (
        <motion.div
          initial={{ opacity: 0, y: -4, scale: 0.96 }}
          animate={{ opacity: 1, y: 0, scale: 1 }}
          exit={{ opacity: 0, y: -4, scale: 0.96 }}
          transition={{ duration: 0.2, ease: [0.16, 1, 0.3, 1] }}
          style={{ position: "absolute", top: "calc(100% + 4px)", left: 0 }}
        >
          {children}
        </motion.div>
      )}
    </AnimatePresence>
  4. Decaler l'entree de la barre au montage

    Passer l'index du segment a DropdownSegment et l'utiliser dans le delai de transition du motion.div : index * 0.08 secondes. Combine a initial opacity 0 et y -8, cela donne l'impression que le fil d'Ariane s'assemble de gauche a droite au premier rendu, sans avoir besoin d'un composant orchestrateur separe.

    <motion.div
      initial={{ opacity: 0, y: -8 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ delay: index * 0.08, duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
    >

Quand l'utiliser

A utiliser sur les sites de documentation, les arborescences de categories e-commerce, les tableaux de bord SaaS a hierarchie profonde, ou tout panneau d'administration ou les utilisateurs naviguent frequemment en lateral. A eviter sur les sites plats a deux ou trois niveaux, un fil d'Ariane simple est plus propre. Sur mobile les dropdowns fonctionnent au tap, mais s'assurer que les zones de tap font au moins 44px de hauteur pour un usage confortable.

Utilisé par

  • Notion, Utilise un fil d'Ariane inline avec des dropdowns de pages parentes cliquables pour naviguer dans les hierarchies d'espace de travail.
  • Vercel, Les fils d'Ariane du tableau de bord exposent des selecteurs de projet et d'equipe sous forme de menus deroulants pour un changement de contexte rapide.
  • GitHub, Le navigateur de fichiers du depot utilise un fil d'Ariane de chemin ou chaque segment pointe ou bifurque vers les repertoires freres.
  • Linear, Les fils d'Ariane du detail d'un ticket offrent des selecteurs d'equipe et de projet inline sous forme d'overlays deroulants compacts.

FAQ

Comment fonctionne la fermeture au clic exterieur sans librairie d'etat globale ?

Chaque DropdownSegment attache son propre ecouteur mousedown au document dans un useEffect. Il compare la cible du clic avec le ref du segment via Node.contains. Si la cible est en dehors, setOpen(false) se declenche. Le cleanup supprime l'ecouteur au demontage, il n'y a donc pas de fuites memoire meme quand les segments sont rendus conditionnellement.

Deux dropdowns peuvent-ils etre ouverts en meme temps ?

Oui, dans l'implementation actuelle chaque segment est totalement independant. Pour forcer le comportement un seul ouvert, remonter l'etat ouvert vers le parent et passer un callback onOpen qui ferme tous les autres segments quand l'un est declenche.

Le fil d'Ariane est-il accessible au clavier ?

Le declencheur utilise un element button natif focusable par defaut et recoit les attributs aria-expanded et aria-haspopup. Le conteneur nav porte aria-label. Pour un support complet du clavier, ajouter des gestionnaires keydown pour Escape (fermer le panneau) et les touches fleches (parcourir les elements du dropdown).

Comment remplacer les donnees mock par de vrais liens de routeur ?

Remplacer les balises anchor dans le panneau dropdown par le composant Link de votre routeur (Next.js Link, React Router Link, etc.) et passer le tableau segments depuis votre contexte de routage ou un plan de site statique. Le composant est purement presentationnel, donc remplacer l'element de lien ne necessite aucun changement dans la logique d'animation.

"use client";

import { useState, useRef, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { ChevronDown, Home, Folder, FileText } from "lucide-react";
import React from "react";

interface BreadcrumbChild {
  label: string;
  href: string;
}

interface BreadcrumbSegment {
  label: string;
  href?: string;
  icon?: "home" | "folder" | "file";
  children?: BreadcrumbChild[];
}

interface BreadcrumbDropdownNavProps {
  segments?: BreadcrumbSegment[];
}

Code complet réservé à Pro

Code source intégral, export multi-framework et playground.

Passer en Pro, 9,99€/mois

Avis

Fil d'Ariane React avec menus deroulants, Code + Tutoriel