Dropdown

Composant de menu déroulant positionné en position: fixed par rapport au déclencheur. Le positionnement est calculé en JS via getBoundingClientRect() et se recalcule automatiquement au scroll et au resize. Supporte 4 directions d'ouverture avec alignements, et gère automatiquement les collisions avec les bords de la page. Un seul dropdown peut être ouvert à la fois.

Stable JS requis Accessible

Aperçu


LIVE PREVIEW — DROPDOWN PAR DÉFAUT

Structure HTML


CODE HTML

💡 Indispensable

Chaque data-target doit correspondre exactement à l'id du .dropdown-body associé. Accepte #id ou id.


⚠️ Le .dropdown-body doit être un enfant direct du .dropdown pour que le bouton déclencheur soit correctement retrouvé lors du calcul de position.

👉 Pour l'accessibilité

Les attributs suivants sont ajoutés automatiquement par Dropdown.init() :

L’attribut suivant doit être posé manuellement dans le HTML :

Classes CSS


CLASSE ÉLÉMENT DESCRIPTION
.dropdown <div> Conteneur principal. S'affiche en inline-flex et sert de référence pour retrouver le bouton déclencheur lors du calcul de position.
.dropdown-body <div> Panneau déroulant en position: fixed. Positionné par le JS via getBoundingClientRect(). Gère la bordure, le fond, le z-index et la transition d'opacité.
.show <div> Ajoutée par le JS à l'ouverture. Applique opacity: 1.
.hide <div> Ajoutée par le JS à la fermeture et à l'initialisation. Applique opacity: 0. L'attribut hidden est reposé à la fin de la transition.

Positionnement


Direction d'ouverture

Définit de quel côté du déclencheur le panneau s'ouvre. Le JS lit ces classes dans Dropdown.position() pour calculer top et left. Ces classes s'appliquent sur .dropdown-body.

CLASSE DESCRIPTION
.drop-bottom Ouvre le panneau en dessous du déclencheur. Si l'espace en bas de la page est insuffisant, bascule automatiquement au-dessus.
.drop-top Ouvre le panneau au-dessus du déclencheur. Si l'espace en haut de la page est insuffisant, bascule automatiquement en dessous.
.drop-right / .drop-end Ouvre le panneau à droite du déclencheur. Si pas de place à droite, essaie à gauche. Si ni gauche ni droite, bascule en bas (ou en haut si pas de place en bas).
.drop-left / .drop-start Ouvre le panneau à gauche du déclencheur. Si pas de place à gauche, essaie à droite. Si ni gauche ni droite, bascule en bas (ou en haut si pas de place en bas).

ℹ️ Détection de collision

L'espace disponible est calculé par rapport à la hauteur totale de la page via document.documentElement.scrollHeight — pas le viewport. Ainsi, si le bouton est en bas de la page et qu'il ne reste plus de place pour scroller, le dropdown bascule automatiquement vers le haut. Pour la largeur, c'est window.innerWidth qui est utilisé.

Alignement horizontal

Se combine avec .drop-top et .drop-bottom. Définit l'alignement du panneau sur l'axe horizontal.

CLASSE DESCRIPTION
.drop-align-left / .drop-align-start Aligne le bord gauche du panneau sur le bord gauche du déclencheur.
.drop-align-center / .drop-align-middle Centre le panneau horizontalement sous/au-dessus du déclencheur. Défaut si aucun alignement n'est spécifié pour .drop-top et .drop-bottom.
.drop-align-right / .drop-align-end Aligne le bord droit du panneau sur le bord droit du déclencheur.

Alignement vertical

Se combine avec .drop-left et .drop-right. Définit l'alignement du panneau sur l'axe vertical.

CLASSE DESCRIPTION
.drop-align-top Aligne le bord haut du panneau sur le bord haut du déclencheur.
.drop-align-middle / .drop-align-center Centre le panneau verticalement par rapport au déclencheur. Défaut si aucun alignement n'est spécifié pour .drop-left et .drop-right.
.drop-align-bottom Aligne le bord bas du panneau sur le bord bas du déclencheur.
EXEMPLES DE COMBINAISONS HTML

Défaut (sans classe de direction)

Si aucune classe de direction n'est posée, le JS place le panneau en bas centré sous le déclencheur.

États


ÉTAT ÉLÉMENT DESCRIPTION
Fermé [hidden].hide État initial. L'attribut hidden masque le panneau. La classe .hide applique opacity: 0.
Ouvert .show hidden retiré. La classe .show applique opacity: 1. aria-expanded="true" mis à jour. Les listeners scroll et resize sont actifs.
En fermeture .hide .show retiré, .hide ajouté. Transition CSS en cours. hidden reposé à la fin de transitionend. Les listeners sont retirés.

JavaScript


Le composant est piloté par la classe Dropdown. Elle s'appuie sur la délégation d'événements (un seul listener sur document), le positionnement en position: fixed via getBoundingClientRect(), et des listeners scroll / resize avec { passive: true } actifs uniquement quand un dropdown est ouvert.

INITIALISATION

SCRIPT D'INITIALISATION JS
import Dropdown from './Dropdown.js';

// A single instance is enough for the entire page.
new Dropdown();

ℹ️ Singleton

La classe implémente un guard Dropdown.initialized : instancier Dropdown plusieurs fois n'entraîne aucun doublon de listeners.

ATTRIBUTS DE DONNÉES REQUIS

ATTRIBUT ÉLÉMENT DESCRIPTION
data-ui="dropdown" .btn Identifie le bouton comme déclencheur du dropdown.
data-target .btn Sélecteur du .dropdown-body cible. Accepte #id ou id.

MÉTHODES STATIQUES

MÉTHODE DESCRIPTION
Dropdown.init() Parcourt tous les boutons [data-ui="dropdown"], initialise aria-controls, aria-expanded et les classes .show / .hide. Si un dropdown est ouvert par défaut au chargement, branche également les listeners scroll et resize.
Dropdown.isOpen(dropdown) Retourne true si le dropdown n'a pas l'attribut hidden.
Dropdown.toggleDropdown(dropdown, button) Bascule l'état du dropdown. Appelle openDropdown ou closeDropdown selon Dropdown.isOpen().
Dropdown.openDropdown(dropdown, button) Retire hidden, calcule la position via Dropdown.position(), échange .hide contre .show, met à jour aria-expanded="true" et branche les listeners scroll / resize.
Dropdown.closeDropdown(dropdown, button) Échange .show contre .hide, met à jour aria-expanded="false", retire les listeners et repose hidden à la fin de transitionend.
Dropdown.closeAllDropdowns(exception) Ferme tous les dropdowns ouverts sauf le bouton passé en paramètre. Appelé avant chaque ouverture et à chaque clic en dehors d'un .dropdown-body.
Dropdown.position(dropdown, button) Calcule et applique top et left selon les classes de direction et d'alignement. Gère les fallbacks de collision via document.documentElement.scrollHeight. Appelée à l'ouverture et à chaque événement scroll / resize.
Dropdown.createReposition(dropdown, button) Crée et retourne la fonction de repositionnement associée à un dropdown. Utilise requestAnimationFrame et un flag rafPending pour limiter les recalculs à 60 par seconde maximum. Appelée dans openDropdown() et init() pour brancher les listeners scroll et resize.

UTILISATION MANUELLE

UTILISATION MANUELLE (OPTIONNELLE) JS
// Open a dropdown programmatically
const dropdown = document.getElementById('my-dropdown');
const btn = document.querySelector('[data-target="#my-dropdown"]');
Dropdown.openDropdown(dropdown, btn);

// Close a dropdown programmatically
Dropdown.closeDropdown(dropdown, btn);

// Close all dropdowns
Dropdown.closeAllDropdowns();

CSS Custom Properties


TOKEN RÔLE
--dropdown-body-background-color Couleur de fond du panneau
--dropdown-body-border-radius Rayon des coins du panneau
--dropdown-body-border-color Couleur de la bordure
--dropdown-body-border-style Style de bordure (solid, dashed…)
--dropdown-body-border-width Épaisseur de la bordure
EXEMPLE DE PERSONNALISATION CSS
/* Global variables */
:root {
  --dropdown-body-border-radius: 8px;
}

/* Light theme */
:root[data-theme="light"] {
  color-scheme: light;
  --dropdown-body-border-color: #e5e5e5;
  --dropdown-body-background-color: #ffffff;
}

/* Dark theme */
:root[data-theme="dark"] {
  color-scheme: dark;
  --dropdown-body-border-color: #404040;
  --dropdown-body-background-color: #262626;
}

Accessibilité


PRATIQUE DÉTAIL
aria-expanded Initialisé par Dropdown.init() et mis à jour à chaque toggle. Communique l'état ouvert/fermé aux lecteurs d'écran.
aria-controls Initialisé par Dropdown.init(). Lie le bouton à son panneau via l'id du .dropdown-body.
hidden L'attribut HTML natif masque le panneau aux technologies d'assistance. Géré automatiquement par le JS.
Élément <button> Utilisez toujours un <button> comme déclencheur pour assurer la navigation clavier native (Tab, Entrée, Espace).
Focus visible Stylez :focus-visible sur le bouton déclencheur et les éléments du menu pour les utilisateurs clavier.

⚠️ Attention

Le JS ne gère pas nativement la fermeture à la touche Escape ni le piège du focus dans le panneau. Pour une accessibilité complète, il est recommandé d'ajouter un listener sur keydown pour fermer le dropdown sur Escape.

Notes & bonnes pratiques


✅ À faire

Toujours ajouter hidden sur le .dropdown-body au chargement si le dropdown doit être fermé par défaut. Sans cet attribut, Dropdown.isOpen() considère le panneau comme ouvert.

✅ À faire

Combiner une classe de direction (.drop-top, .drop-bottom, .drop-left, .drop-right) avec une classe d'alignement pour un positionnement précis. Sans classe de direction, le panneau s'ouvre en bas centré par défaut.

❌ À éviter

Ne pas instancier Dropdown avant le chargement du DOM. Utilisez DOMContentLoaded ou placez le script en bas de page.

ℹ️ Fermeture automatique

Tout clic en dehors d'un .dropdown-body ferme automatiquement tous les dropdowns ouverts. Les clics à l'intérieur du panneau sont ignorés.

ℹ️ Repositionnement automatique

Quand un dropdown est ouvert, des listeners scroll et resize avec { passive: true } recalculent la position en temps réel. Ils sont automatiquement retirés à la fermeture.


Dropdown · WEBDEV-UI DOCS / V1