Menus déroulants adaptés à la fenêtre d'affichage

Fonctionnalités Web : Positionnement des ancres Popover

Un panneau d'actions ou un groupe de boutons popover qui s'affiche est un modèle utile pour permettre aux utilisateurs d'accéder à des fonctionnalités supplémentaires tout en occupant un minimum d'espace. Ce modèle de superposition s'accompagne d'une mise en page complexe, car le panneau doit rester associé à un élément déclencheur tout en s'adaptant aux contraintes de la fenêtre d'affichage. Traditionnellement, cela nécessitait des bibliothèques JavaScript complexes (comme Popper.js ou Floating UI) pour calculer les positions et gérer les collisions.

Le positionnement des ancres CSS offre un moyen déclaratif et optimisé pour les performances de gérer ces relations entièrement en CSS, ce qui permet aux navigateurs de gérer la logique de positionnement et de dépassement de manière native.

1. Définir la relation entre le bouton et le panneau

La première étape consiste à créer un bouton de déclenchement qui ouvre le conteneur de superposition à l'aide de l'API Popover.

DISTINCTION OBLIGATOIRE EN MATIÈRE D'ACCESSIBILITÉ : ce modèle représente explicitement un groupe de boutons affiché dans un popover plutôt qu'un véritable menu ARIA. N'appliquez pas role="menu" ni role="menuitem", sauf si vous implémentez entièrement le contrat de navigation au clavier correspondant (par exemple, en gérant la navigation spatiale entre les éléments à l'aide des touches fléchées). Pour la même raison, n'ajoutez pas aria-haspopup au déclencheur. Sa valeur (menu, listbox, tree, grid ou l'ancienne valeur true) est une promesse que la cible expose un rôle correspondant, ce que ce modèle ne fait pas.

<button popovertarget="action-panel">
  Open Actions
</button>
<!-- Use the Popover API (`popover="auto"`) for the overlay to ensure it is placed in the top layer and handled accessibly by the browser. -->
<div id="action-panel" popover="auto" class="panel">
  <button class="action-item" type="button">Edit</button>
</div>

Cela crée une association d'ancrage implicite entre le bouton et le panneau, de sorte que le panneau puisse être positionné par rapport au bouton de manière native, sans avoir besoin de anchor-name sur le bouton ni de position-anchor sur le panneau.

2. Positionnement avec position-area

Au lieu d'utiliser des décalages top/left manuels, utilisez position-area pour placer la cible sur une grille de 3x3 par rapport à l'ancrage.

.panel {
  /* 
     Position the panel below the anchor (block-end), 
     aligned to the start of the anchor and spanning to its end (span-inline-end).
  */
  position-area: block-end span-inline-end;
  
  /* Reset insets to allow the grid to take control */
  inset: auto;
}

Privilégiez les mots clés logiques (span-inline-end, block-start) aux mots clés physiques (left, top) pour prendre en charge automatiquement les langues qui se lisent de droite à gauche et les différents modes d'écriture.

OBLIGATOIRE : Ne mélangez pas les mots clés physiques et logiques dans position-area.

3. Implémenter la résilience en périphérie (créations de remplacement)

Pour éviter que le panneau ne soit coupé au bord de l'écran, définissez des "tactiques d'essai" que le navigateur doit tenter si la position par défaut déborde.

.panel {
  /* 
     If the panel overflows the bottom, flip it to the top (flip-block).
     If it overflows the inline edges, flip it horizontally (flip-inline).
     If it overflows both (e.g., in a corner), flip on both axes (flip-block flip-inline).
  */
  position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
}

Si votre menu déroulant ou menu volant inclut une flèche directionnelle ou une bordure dont le style doit changer en fonction de la position de remplacement choisie, consultez Info-bulles tenant compte de la position. Pour l'appel déclaratif de boîtes de dialogue et de popovers sans JavaScript, consultez Contrôle déclaratif des boîtes de dialogue et des popovers.

Stratégies de remplacement

Solutions de remplacement et compatibilité des navigateurs pour Popover

L'API Popover est principalement une amélioration progressive, mais ses comportements déterminants (promotion de la couche supérieure, fermeture par clic en dehors de l'élément et appel de popovertarget) n'ont pas d'équivalent CSS uniquement. Les anciens navigateurs ont besoin d'un polyfill ou d'un remplacement manuel si vous préférez ne pas en fournir.

Polyfill : pour que l'attribut popover soit compatible avec les anciens navigateurs, chargez @oddbird/popover-polyfill de manière conditionnelle. OBLIGATOIRE : Détectez la fonctionnalité en vérifiant la propriété popover sur HTMLElement.prototype, et chargez le polyfill uniquement lorsque la compatibilité native est manquante. Ne le chargez PAS de manière inconditionnelle.

Avec un bundler ou une carte d'importation :

// MANDATORY: Feature detect 'popover' on HTMLElement.prototype.
if (!("popover" in HTMLElement.prototype)) {
  import("@oddbird/popover-polyfill");
}

Sans bundler, importez depuis un CDN dans un <script type="module"> :

<script type="module">
  if (!("popover" in HTMLElement.prototype)) {
    import("https://unpkg.com/@oddbird/popover-polyfill@latest/dist/popover.min.js");
  }
</script>

Mise en garde concernant l'application de styles : le polyfill ne peut pas définir la véritable pseudo-classe :popover-open. Il applique donc une classe .\:popover-open à la place. OBLIGATOIRE : Combinez les deux avec :is() ou :where(), sinon les navigateurs qui ne disposent pas de :popover-open ignorent l'intégralité de la règle :

[popover]:is(:popover-open, .\:popover-open) {
  display: block;
}

Vous pouvez également utiliser position: fixed pour une solution de remplacement sans polyfill et calculer manuellement les coordonnées via getBoundingClientRect(), ou vous appuyer sur le positionnement par défaut avec inset: auto si cela convient à votre cas d'utilisation.

Pour que les navigateurs ne prenant pas en charge le positionnement d'ancrage puissent afficher l'élément, vous devez définir une position raisonnable. Par défaut, les popovers sont centrés au milieu de l'écran, ce qui peut convenir à votre cas d'utilisation.

Pour certains cas d'utilisation, vous pouvez utiliser le polyfill @oddbird/css-anchor-positioning, qui ajoute la compatibilité avec certains cas d'utilisation du positionnement des ancres. Il n'est pas compatible avec les ancres implicites. Vous DEVEZ donc ajouter des noms d'ancres au déclencheur. De plus, position-area n'est pas compatible avec les popovers par le polyfill. Vous DEVEZ donc utiliser anchor() sur les marges intérieures souhaitées.

<!-- MANDATORY: Conditionally install the anchor positioning polyfill -->
<script type="module">
  if (!("anchorName" in document.documentElement.style)) {
    await import("https://unpkg.com/@oddbird/css-anchor-positioning");
  }
</script>
.panel {
  /* Mandatory: use explicit anchor name */
  position-anchor: --kebab-anchor;
  /* Mandatory: use insets rather than position-area for positioning */
  bottom: auto;
  right: auto;
  top: anchor(bottom);
  left: anchor(left);
  margin: 0;
}