Component
<dom-dropdown>
custom elementA menu that opens from a button. The menu panel uses the same native Popover API as dom-popover (top layer, light-dismiss, Esc), plus menu keyboard navigation and dom:select.
Demo
Account menu
Menu items use role="menuitem" and data-value — selection emits dom:select.
basic.htmlhtml
<dom-dropdown>
<button
slot="trigger"
type="button"
class="inline-flex h-10 items-center gap-2 rounded-full bg-secondary px-4 text-sm font-medium text-fg ring-1 ring-border hover:bg-accent"
>
Account ▾
</button>
<div slot="menu" class="dom-dropdown-menu min-w-[10rem] rounded-2xl border border-border bg-background p-1 shadow-lg">
<button role="menuitem" data-value="profile" class="block w-full rounded-xl px-3 py-2 text-left text-sm text-fg hover:bg-secondary">Profile</button>
<button role="menuitem" data-value="billing" class="block w-full rounded-xl px-3 py-2 text-left text-sm text-fg hover:bg-secondary">Billing</button>
<button role="menuitem" data-value="signout" class="block w-full rounded-xl px-3 py-2 text-left text-sm text-fg hover:bg-secondary">Sign out</button>
</div>
</dom-dropdown>
<script type="module">
import '@getdom/studio/headless/dropdown.js';
</script>
Usage
Plain HTML
<dom-dropdown>
<button slot="trigger">Account ▾</button>
<div slot="menu">
<button role="menuitem" data-value="profile">Profile</button>
<button role="menuitem" data-value="billing">Billing</button>
<button role="menuitem" data-value="signout">Sign out</button>
</div>
</dom-dropdown>
<script type="module">
import '@getdom/studio/headless';
document.querySelector('dom-dropdown')
.addEventListener('dom:select', (e) => console.log(e.detail.value));
</script> Register every <dom-*> in one import: import '@getdom/studio/headless'.
Slots
| Name | Scope | Description |
|---|---|---|
| #trigger | — | Button that opens the menu. |
| #menu | — | Menu panel (popover="auto"). Items inside must carry role="menuitem". data-value flows through dom:select. |
Attributes
| Name | Type | Description |
|---|---|---|
| open | boolean | Reflects open state (toggle by setting / removing). |
| placement | 'top' | 'right' | 'bottom' | 'left' | '<side>-<align>' | Preferred placement before collision handling (default bottom). |
| align | 'left' | 'right' | Horizontal alignment under the trigger (default left). |
| offset | number | Gap in pixels between trigger and menu (default 4). |
| collision-padding | number | Viewport padding used when flipping or shifting the menu (default 8). |
| floating-mode | 'viewport' | 'anchor' | viewport keeps the menu inside the browser; anchor keeps it attached to the trigger while scrolling. |
| lock-scroll | boolean | Lock document scrolling while the menu is open. |
| data-menu-id | string | Id of an external (teleported) menu element. Used when the menu lives outside the host — e.g. portalled to <body>. |
From dom-dropdown.__doc.attributes.
Events
| Name | Payload | Description |
|---|---|---|
| dom:open | — | Fired when the menu opens. |
| dom:close | — | Fired when the menu closes. |
| dom:select | { value, event } | Fired when a menu item is chosen. |
Names auto-detected from defineEmits and source emit() calls; payload and description from __doc.events when present.
Keyboard
- Enter / Space / ↓Open menu (when trigger is focused).
- ↑ / ↓Move active item.
- Home / EndJump to first / last item.
- EnterSelect active item.
- Esc / TabClose menu and return focus.
Related