Skip to content

Menu

A dropdown menu with full keyboard support, typeahead, and positioning.

html
<div data-controller="zag-menu" class="relative inline-block text-left">
  <button
    data-part="trigger"
    class="border-input bg-popover text-foreground hover:bg-accent focus-visible:ring-ring data-[state=open]:bg-accent inline-flex items-center gap-2 rounded-lg border px-4 py-2 text-sm font-medium shadow-sm transition focus:outline-none focus-visible:ring-2"
  >
    Actions
    <span
      data-part="indicator"
      class="text-muted-foreground transition-transform data-[state=open]:rotate-180"
      aria-hidden="true"
      >▾</span
    >
  </button>

  <div data-part="positioner">
    <div
      data-part="content"
      hidden
      class="border-border bg-popover z-50 min-w-44 rounded-lg border p-1 shadow-lg focus:outline-none"
    >
      <button
        data-part="item"
        data-value="edit"
        class="text-foreground data-[highlighted]:bg-primary data-[highlighted]:text-primary-foreground flex w-full cursor-pointer items-center rounded-md px-3 py-2 text-sm"
      >
        Edit
      </button>
      <button
        data-part="item"
        data-value="duplicate"
        class="text-foreground data-[highlighted]:bg-primary data-[highlighted]:text-primary-foreground flex w-full cursor-pointer items-center rounded-md px-3 py-2 text-sm"
      >
        Duplicate
      </button>
      <hr data-part="separator" class="border-border my-1" />
      <button
        data-part="item"
        data-value="delete"
        data-disabled
        class="text-destructive data-[highlighted]:bg-destructive flex w-full items-center rounded-md px-3 py-2 text-sm data-[disabled]:cursor-not-allowed data-[highlighted]:text-white data-[disabled]:opacity-40"
      >
        Delete
      </button>
    </div>
  </div>
</div>

Parts

trigger, indicator, positioner, content, item, separator, arrow, arrow-tip.

Each item carries a data-value; add data-disabled to disable one.

Values

AttributeTypeDefaultDescription
data-zag-menu-placement-valueString"bottom-start"Popper placement of the content.
data-zag-menu-gutter-valueNumber8Gap (px) between trigger and content.
data-zag-menu-close-on-select-valueBooleantrueClose the menu after choosing an item.
data-zag-menu-loop-focus-valueBooleanfalseWrap keyboard focus at the ends.

Events

Emits zag-menu:select — a CustomEvent whose detail is { value } for the chosen item.

js
element.addEventListener("zag-menu:select", (e) => console.log(e.detail.value))

Programmatic control

From another Stimulus controller (via outlets or getControllerForElementAndIdentifier): open(), close(), isOpen.