Dropdown menu
A menu of actions anchored to a trigger. Built on the native Popover API: top layer, light dismiss and Escape are free.
Basic
<button class="mn-btn mn-btn-outline" popovertarget="dd-account">My account <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></button>
<div class="mn-menu" popover id="dd-account">
<div class="mn-menu-label">Signed in as [email protected]</div>
<button class="mn-menu-item"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/></svg> Profile <span class="mn-menu-shortcut">⌘P</span></button>
<button class="mn-menu-item"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 10h18M7 15h3"/></svg> Billing</button>
<button class="mn-menu-item"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1Z"/></svg> Settings <span class="mn-menu-shortcut">⌘,</span></button>
<hr class="mn-menu-sep">
<button class="mn-menu-item mn-danger"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 4h3a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1h-3M10 16l4-4-4-4M14 12H4"/></svg> Log out</button>
</div>Checkable items
<button class="mn-btn mn-btn-outline" popovertarget="dd-cols">Columns <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></button>
<div class="mn-menu" popover id="dd-cols">
<div class="mn-menu-label">Visible columns</div>
<button class="mn-menu-item" aria-checked="true">Name</button>
<button class="mn-menu-item" aria-checked="true">Email</button>
<button class="mn-menu-item" aria-checked="false">Phone</button>
<button class="mn-menu-item" aria-checked="false" aria-disabled="true">ID (always shown)</button>
</div>Icon trigger, aligned to the end
<button class="mn-btn mn-btn-icon mn-btn-ghost" aria-label="Row actions" popovertarget="dd-row"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="5" cy="12" r="1.2"/><circle cx="12" cy="12" r="1.2"/><circle cx="19" cy="12" r="1.2"/></svg></button>
<div class="mn-menu" popover id="dd-row" data-placement="bottom-end">
<button class="mn-menu-item"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 20h4L19 9l-4-4L4 16v4Z"/><path d="m14 6 4 4"/></svg> Edit</button>
<button class="mn-menu-item"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="12" height="12" rx="2"/><path d="M5 15H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1"/></svg> Duplicate</button>
<hr class="mn-menu-sep">
<button class="mn-menu-item mn-danger"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 6h18M8 6V4h8v2M6 6l1 14h10l1-14"/></svg> Delete</button>
</div>Installation
Everything ships in one stylesheet and one small script. There is nothing to import per component.
<link rel="stylesheet" href="https://mian.ro/dist/mian.css">
<script src="https://mian.ro/dist/mian.js" defer></script>Prefer to own the files? /r/dropdown.json is a registry item with this component's CSS and example markup, ready to copy into your project with any tool that reads the open registry-item format.
Class reference
| Class / attribute | Description |
|---|---|
| mn-menu + popover | The menu panel. Give it an id and point a button's popovertarget at it. |
| mn-menu-item | A menu item: button or link. Add mn-danger for destructive items. |
| mn-menu-label · mn-menu-sep · mn-menu-shortcut | Group label, separator, trailing shortcut hint. |
| data-placement | bottom-start (default), bottom-end, top-start, top-end, right-start, left-start… |
| aria-checked | Makes an item a checkable menuitemcheckbox; the menu stays open when toggled. |
Accessibility
- mian.js adds role="menu" and role="menuitem", wires aria-expanded and aria-haspopup on the trigger.
- Focus moves to the first item on open. Arrow keys, Home, End and type-ahead navigate. Escape and Tab close.
- Focus returns to the trigger after choosing an item.
- The menu emits mn:select with the chosen item.