Menu a discesa sensibili all'area visibile

Un riquadro delle azioni mostrato o un gruppo di pulsanti popover è un pattern utile per consentire agli utenti di accedere a funzionalità aggiuntive occupando uno spazio minimo. Questo pattern di overlay comporta una complessità del layout, poiché il riquadro deve rimanere collegato a un elemento di attivazione adattandosi al contempo ai vincoli della visualizzazione. In passato, per calcolare le posizioni e gestire le collisioni erano necessarie librerie JavaScript complesse (come Popper.js o Floating UI).

Il posizionamento degli ancoraggi CSS fornisce un modo dichiarativo e ottimizzato per le prestazioni per gestire queste relazioni interamente in CSS, consentendo ai browser di gestire in modo nativo la logica di posizionamento e overflow.

1. Definisci la relazione tra pulsante e riquadro

Il primo passaggio consiste nel creare un pulsante di attivazione che apra il container in overlay utilizzando l'API Popover.

Distinzione di accessibilità OBBLIGATORIA: questo pattern modella esplicitamente un gruppo di pulsanti mostrato all'interno di un popover anziché un vero menu ARIA. Non applicare role="menu" o role="menuitem" a meno che non implementi completamente il contratto di navigazione da tastiera corrispondente (ad esempio, la gestione della navigazione spaziale con i tasti Freccia tra gli elementi). Per lo stesso motivo, non aggiungere aria-haspopup al trigger: il suo valore (menu, listbox, tree, grid o il valore legacy true) è una promessa che la destinazione espone un ruolo corrispondente, cosa che questo pattern non fa.

<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>

In questo modo viene creata un'associazione di ancoraggio implicita tra il pulsante e il riquadro, in modo che il riquadro possa essere posizionato in modo nativo rispetto al pulsante senza bisogno di anchor-name sul pulsante o position-anchor sul riquadro.

2. Posizionamento con position-area

Anziché utilizzare gli offset manuali top/left, utilizza position-area per posizionare il target su una griglia 3x3 rispetto all'ancoraggio.

.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;
}

Preferisci le parole chiave logiche (span-inline-end, block-start) a quelle fisiche (left, top) per supportare automaticamente la scrittura da destra a sinistra e diverse modalità di scrittura.

OBBLIGATORIO: non combinare parole chiave fisiche e logiche in position-area.

3. Implementa la resilienza perimetrale (fallback)

Per evitare che il riquadro venga tagliato sul bordo dello schermo, definisci le "tattiche di prova" che il browser deve tentare se la posizione predefinita causa un overflow.

.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;
}

Se il menu a discesa o il menu flyout include una freccia direzionale o un bordo che deve cambiare stile a seconda della posizione di riserva scelta, consulta la sezione Tooltip sensibili alla posizione. Per l'attivazione dichiarativa di finestre di dialogo e popup senza JavaScript, consulta Controllo dichiarativo di finestre di dialogo e popup.

Strategie di riserva

Alternative e supporto del browser per Popover

L'API Popover è per lo più un potenziamento progressivo, ma i suoi comportamenti distintivi (promozione al livello superiore, chiusura con un clic e attivazione di popovertarget) non hanno un equivalente solo CSS. I browser meno recenti richiedono un polyfill o un fallback manuale se preferisci non fornirne uno.

Polyfill: per supportare l'attributo popover nei browser meno recenti, carica @oddbird/popover-polyfill in modo condizionale. OBBLIGATORIO: esegui il rilevamento delle funzionalità controllando la proprietà popover su HTMLElement.prototype e carica il polyfill solo quando manca il supporto nativo; NON caricarlo incondizionatamente.

Con un bundler o una mappa di importazione:

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

Senza un bundler, importa da una CDN all'interno di 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>

Avvertenza sullo stile: il polyfill non può definire la pseudo-classe :popover-open reale, quindi applica una classe .\:popover-open. OBBLIGATORIO: combina i due elementi con :is() o :where(), altrimenti i browser che non dispongono di :popover-open ignorano l'intera regola:

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

In alternativa, per un fallback legacy senza un polyfill, utilizza position: fixed e calcola manualmente le coordinate tramite getBoundingClientRect() o affidati al posizionamento predefinito con inset: auto se è accettabile per il caso d'uso.

Per supportare i browser senza posizionamento di ancoraggio, devi impostare una posizione ragionevole. Per impostazione predefinita, i popover sono centrati al centro dello schermo, il che potrebbe essere adatto al tuo caso d'uso.

Per alcuni casi d'uso, potresti essere in grado di utilizzare il polyfill @oddbird/css-anchor-positioning, che aggiunge il supporto per alcuni casi d'uso di posizionamento degli ancoraggi. Non supporta gli ancoraggi impliciti, quindi DEVI aggiungere i nomi degli ancoraggi all'attivatore. Inoltre, position-area non è supportato sui popover dal polyfill, quindi DEVI utilizzare anchor() sugli inset desiderati.

<!-- 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;
}