Popover Menu

A dropdown menu with no JavaScript, built on the HTML popover attribute. Escape and clicks outside close it. Where CSS anchor positioning is supported the menu opens below its button; elsewhere it opens in the center of the screen.

Source on GitHub

Usage

label is the button text. The default slot holds the menu content, usually links or buttons.

examples/components/popover-menu/popover-menu.html
html
<popover-menu data-bascik-prop-label="Account">
  <a href="/example-components">All example components</a>
  <a href="/example-components/tab-group">Tab group</a>
</popover-menu>
html examples/components/popover-menu/popover-menu.html
<!-- @bascik
A dropdown menu with no JavaScript, using the HTML popover attribute. Escape and clicks outside close it. Where CSS anchor positioning is supported the menu opens below its button; elsewhere it opens centered.
@prop label - Text of the button that opens the menu.
@slot default - Menu content, usually links or buttons.
-->
<style>
  .trigger {
    padding: 0.5rem 1rem;
    border: 1px solid var(--color-border, color-mix(in srgb, currentColor 30%, transparent));
    border-radius: var(--radius, 0.5rem);
    background: var(--color-surface, color-mix(in srgb, currentColor 8%, transparent));
    color: inherit;
    font: inherit;
    cursor: pointer;
  }

  .trigger:hover {
    border-color: var(--color-accent, currentColor);
  }

  .menu {
    min-width: 12rem;
    margin: 0;
    padding: 0.5rem;
    border: 1px solid var(--color-border, color-mix(in srgb, currentColor 30%, transparent));
    border-radius: var(--radius, 0.5rem);
    background: var(--color-bg, Canvas);
    color: var(--color-text, CanvasText);
    box-shadow: 0 0.5rem 1.5rem rgb(0 0 0 / 0.25);
  }

  /* The button that opens a popover is its implicit anchor, so each instance anchors to its own button. Do not
     use anchor-name here: Bascik scopes it per component, so every instance would share one anchor. */
  @supports (position-area: bottom) {
    .menu {
      inset: auto;
      margin-top: 0.25rem;
      position-area: bottom span-right;
      position-try-fallbacks: flip-block;
    }
  }
</style>
<button class="trigger" type="button" popovertarget="menu" data-bascik-prop-label>Menu</button>
<div class="menu" id="menu" popover>
  <div data-bascik-slot></div>
</div>
Output is shown unminified (HTML, CSS, JS, and identifier names) for readability.

How It Works

  • popovertarget="menu" on the button opens the element with id="menu" popover. Bascik rewrites the ID and the reference together, so each menu on a page opens its own panel.
  • The button that opens a popover is its implicit anchor. position-area: bottom span-right inside @supports (position-area: bottom) places the menu under that button.
  • The component does not use anchor-name. Bascik scopes it per component type, not per instance, so every menu would anchor to the same button. See Compatibility.

Adapt It

  • For site navigation, pass a <ul> of links. For actions, pass <button> elements.
  • popover="manual" turns off light dismiss, for panels that should stay open until a button closes them.