Dropdown

A menu of actions anchored to a trigger, built with the native popover API and a <menu>.

Composed of

Popover
The surface the menu opens in
Menu
The list of actions
Button
The trigger that opens it

Standalone

<link rel="stylesheet" href="https://esm.sh/@erikt/ui/dropdown.css" />

Default

<button popovertarget="my-dropdown">Actions</button>

<div id="my-dropdown" popover>
  <menu>
    <li><button class="ghost">Edit</button></li>
    <li><button class="ghost">Duplicate</button></li>
    <li><button class="ghost">Share</button></li>
    <li><button class="ghost">Archive</button></li>
  </menu>
</div>

With separator

Use an <hr> inside a <li> to visually group actions.

<button popovertarget="my-dropdown">Actions</button>

<div id="my-dropdown" popover>
  <menu>
    <li><button class="ghost">Edit</button></li>
    <li><button class="ghost">Duplicate</button></li>
    <li><button class="ghost">Share</button></li>
    <li><hr /></li>
    <li><button class="ghost destructive">Delete</button></li>
  </menu>
</div>

With section labels

A <li> whose only child is a text-only <small> is automatically styled as a section label. No extra class needed.

<div id="my-dropdown" popover>
  <menu>
    <li><small>Actions</small></li>
    <li><button class="ghost">New File</button></li>
    <li><button class="ghost">New Folder</button></li>
    <li><hr /></li>
    <li><small>Danger zone</small></li>
    <li><button class="ghost destructive">Delete</button></li>
  </menu>
</div>

With radios

Radio inputs work inside a dropdown as they do anywhere. The checked state survives between opens, and CSS draws the checkmark. The popover stays open after a pick so the choice is visible before dismissing.

<button popovertarget="my-dropdown">Sort by</button>

<div id="my-dropdown" popover>
  <menu>
    <li>
      <label>
        <input type="radio" name="sort" value="newest" checked /> Newest
      </label>
    </li>
    <li>
      <label> <input type="radio" name="sort" value="oldest" /> Oldest </label>
    </li>
    <li>
      <label> <input type="radio" name="sort" value="name" /> Name </label>
    </li>
    <li>
      <label> <input type="radio" name="sort" value="size" /> Size </label>
    </li>
  </menu>
</div>

With checkboxes

Checkboxes let more than one item be picked. Each shows an empty square until it's selected, then a filled one with a checkmark.

<button popovertarget="my-dropdown">Columns</button>

<div id="my-dropdown" popover>
  <menu>
    <li>
      <label>
        <input type="checkbox" name="col" value="name" checked /> Name
      </label>
    </li>
    <li>
      <label>
        <input type="checkbox" name="col" value="size" checked /> Size
      </label>
    </li>
    <li>
      <label> <input type="checkbox" name="col" value="type" /> Type </label>
    </li>
    <li>
      <label>
        <input type="checkbox" name="col" value="modified" /> Modified
      </label>
    </li>
  </menu>
</div>

Custom select

Use radio inputs inside <label>s to build a styled select. A checkmark appears on the chosen option via CSS. A small script updates the trigger text and closes the dropdown on pick.

  • <button
      popovertarget="my-select"
      style="min-width: 10rem; justify-content: space-between"
    >
      <span>Choose a fruit</span>
      <svg><!-- chevron --></svg>
    </button>
    
    <div
      id="my-select"
      popover
      onchange="
        document.querySelector(`[popovertarget=${this.id}] span`).textContent =
          event.target.closest('label').textContent;
        this.hidePopover();
      "
    >
      <menu>
        <li>
          <label> <input type="radio" name="fruit" /> Apple </label>
        </li>
        <li>
          <label> <input type="radio" name="fruit" /> Banana </label>
        </li>
        <li>
          <label> <input type="radio" name="fruit" /> Cherry </label>
        </li>
      </menu>
    </div>

    In a form

    Wrap the trigger and popover in a <form> to participate in form submission. The change event bubbles up so you can handle it on the form.

  • <form onchange="alert('Selected: ' + event.target.value)">
      <button
        type="button"
        popovertarget="my-select"
        style="min-width: 10rem; justify-content: space-between"
      >
        <span>Choose a color</span>
        <svg><!-- chevron --></svg>
      </button>
      <div
        id="my-select"
        popover
        onchange="
          document.querySelector(`[popovertarget=${this.id}] span`).textContent =
            event.target.closest('label').textContent;
          this.hidePopover();
        "
      >
        <menu>
          <li>
            <label> <input type="radio" name="color" value="red" /> Red </label>
          </li>
          <li>
            <label> <input type="radio" name="color" value="green" /> Green </label>
          </li>
          <li>
            <label> <input type="radio" name="color" value="blue" /> Blue </label>
          </li>
        </menu>
      </div>
    </form>

    API reference

    Type Classes / attributes What it does
    Placement [data-placement="bottom left"]
    [data-placement="bottom right"]
    [data-placement="top left"]
    [data-placement="top right"]
    [data-placement="left top"]
    [data-placement="left bottom"]
    [data-placement="right top"]
    [data-placement="right bottom"]
    Sets which side of the trigger the menu opens on

    Search 5021 icons

    Type a name to find icons from the Tabler icon set.

        Move Open Esc Close