Dropmenu
<loomi-dropmenu> is for quick actions: profile menus, row actions, account menus,
and small command lists. It is different from <loomi-select>: a select
stores a value for a form, while a dropmenu simply shows actions the user can choose.
npm install @loomidev/dropmenu litimport "@loomidev/dropmenu";Basic Usage
Section titled “Basic Usage”Import the package once, then place <loomi-dropmenu-item> elements inside the menu.
The default trigger is a horizontal ellipsis.
<loomi-dropmenu> <loomi-dropmenu-item>Invite to Project</loomi-dropmenu-item> <loomi-dropmenu-item>Assign Task</loomi-dropmenu-item> <loomi-dropmenu-item>Send Message</loomi-dropmenu-item></loomi-dropmenu>Trigger Icon
Section titled “Trigger Icon”Swap the default ellipsis for any icon from @loomidev/icons. You can
write the icon name with or without the -icon suffix.
<loomi-dropmenu trigger="musical-note"> <loomi-dropmenu-item>Add to playlist</loomi-dropmenu-item> <loomi-dropmenu-item>Play again</loomi-dropmenu-item></loomi-dropmenu>
<loomi-dropmenu trigger="cog-6-tooth"> <loomi-dropmenu-item>Company settings</loomi-dropmenu-item> <loomi-dropmenu-item>User settings</loomi-dropmenu-item></loomi-dropmenu>Trigger on Hover
Section titled “Trigger on Hover”Menus open on click by default. Use trigger-on="mouseover" when the surrounding UI
already behaves like a hover menu.
<loomi-dropmenu trigger="musical-note" trigger-on="mouseover"> <loomi-dropmenu-item>Add to playlist</loomi-dropmenu-item> <loomi-dropmenu-item>Play again</loomi-dropmenu-item></loomi-dropmenu>Custom Trigger Markup
Section titled “Custom Trigger Markup”Use the trigger slot when the trigger should look like text, an avatar row, or a
custom control. The component wraps this slot in its own button, so keep the slotted
markup non-interactive.
<loomi-dropmenu> <span slot="trigger" style="font-weight:600">Options</span> <loomi-dropmenu-item>Add to playlist</loomi-dropmenu-item> <loomi-dropmenu-item>Play again</loomi-dropmenu-item></loomi-dropmenu>
<loomi-dropmenu> <div slot="trigger" style="display:flex;align-items:center;gap:0.5rem;box-shadow:0 1px 2px rgb(0 0 0 / 0.1);padding:0 1rem;border-radius:0.375rem"> <loomi-avatar size="small" image="/john.jpg"></loomi-avatar> <div> <div><strong>John C. Doe</strong></div> <div style="font-size:0.875rem">Tech, IT Support</div> </div> <loomi-icon name="chevron-down" style="width:1rem;height:1rem"></loomi-icon> </div> <loomi-dropmenu-item>Deactivate my account</loomi-dropmenu-item> <loomi-dropmenu-item>Delete Profile</loomi-dropmenu-item></loomi-dropmenu>Item Actions
Section titled “Item Actions”Each <loomi-dropmenu-item> can be plain text, a link, or markup that you handle with
a regular click listener.
<loomi-dropmenu trigger="light-bulb"> <loomi-dropmenu-item><a href="/library" target="_blank">Go to Library</a></loomi-dropmenu-item> <loomi-dropmenu-item id="show-modal-item">Show a Modal</loomi-dropmenu-item></loomi-dropmenu>
<script type="module"> import { showLoomiModal } from "@loomidev/modal"; document.getElementById("show-modal-item").addEventListener("click", () => showLoomiModal("dropmenu-demo"));</script>Headers, Icons and Dividers
Section titled “Headers, Icons and Dividers”Headers
Section titled “Headers”Use header for a non-clickable section label. It keeps the same spacing as the rest
of the menu but does not get pointer or hover behavior.
<loomi-dropmenu> <loomi-dropmenu-item header>Project</loomi-dropmenu-item> <loomi-dropmenu-item icon="paper-airplane">Invite</loomi-dropmenu-item> <loomi-dropmenu-item icon="trash">Delete</loomi-dropmenu-item></loomi-dropmenu><loomi-dropmenu-item icon="pencil-square">Edit Profile</loomi-dropmenu-item>By default an item’s icon sits on the left. Set icon-right on the menu to flip every
item, or set it on one item to flip just that row.
<loomi-dropmenu icon-right> <loomi-dropmenu-item icon="pencil-square">Edit Profile</loomi-dropmenu-item></loomi-dropmenu>Dividers
Section titled “Dividers”<loomi-dropmenu> <loomi-dropmenu-item>Edit</loomi-dropmenu-item> <loomi-dropmenu-item divider></loomi-dropmenu-item> <loomi-dropmenu-item>Delete</loomi-dropmenu-item></loomi-dropmenu>Use divided on the menu itself when every row should have a thin separator.
<loomi-dropmenu divided> <loomi-dropmenu-item>Add to playlist</loomi-dropmenu-item> <loomi-dropmenu-item>Play again</loomi-dropmenu-item></loomi-dropmenu>Checkboxes
Section titled “Checkboxes”Set checkbox on an item to turn it into a toggle row. It shows a checkmark when
checked, keeps the menu open on click (unlike a normal item), and fires a change
event you can use to sync application state.
<loomi-dropmenu> <loomi-dropmenu-item header>Appearance</loomi-dropmenu-item> <loomi-dropmenu-item checkbox checked>Status Bar</loomi-dropmenu-item> <loomi-dropmenu-item checkbox>Activity Bar</loomi-dropmenu-item> <loomi-dropmenu-item checkbox>Panel</loomi-dropmenu-item></loomi-dropmenu>
<script type="module"> document.querySelectorAll("loomi-dropmenu-item[checkbox]").forEach((item) => { item.addEventListener("change", () => console.log(item.textContent.trim(), item.checked)); });</script>Radio Groups
Section titled “Radio Groups”Set radio plus a shared group name to make a set of items mutually exclusive,
similar to a native radio group. Give each item a value; the previously checked
item in the same group is unchecked automatically.
<loomi-dropmenu> <loomi-dropmenu-item header>Panel Position</loomi-dropmenu-item> <loomi-dropmenu-item radio group="position" value="top">Top</loomi-dropmenu-item> <loomi-dropmenu-item radio group="position" value="bottom" checked>Bottom</loomi-dropmenu-item> <loomi-dropmenu-item radio group="position" value="right">Right</loomi-dropmenu-item></loomi-dropmenu>
<script type="module"> document.querySelectorAll("loomi-dropmenu-item[radio]").forEach((item) => { item.addEventListener("change", () => console.log("position:", item.value)); });</script>Disabled Items
Section titled “Disabled Items”Set disabled on an item to make it non-interactive: it’s skipped by arrow-key
navigation, dimmed, and clicks on it are blocked.
<loomi-dropmenu> <loomi-dropmenu-item>GitHub</loomi-dropmenu-item> <loomi-dropmenu-item>Support</loomi-dropmenu-item> <loomi-dropmenu-item disabled>API</loomi-dropmenu-item></loomi-dropmenu>Destructive Items
Section titled “Destructive Items”Set variant="destructive" for irreversible actions like deleting a resource. It
tints the label, icon, and hover state red.
<loomi-dropmenu> <loomi-dropmenu-item icon="pencil-square">Edit</loomi-dropmenu-item> <loomi-dropmenu-item divider></loomi-dropmenu-item> <loomi-dropmenu-item variant="destructive" icon="trash">Delete</loomi-dropmenu-item></loomi-dropmenu>Keyboard Shortcut Hints
Section titled “Keyboard Shortcut Hints”Use shortcut to show a keyboard shortcut or command hint on the right side of an
item, like ⌘S or ⌘K>P.
<loomi-dropmenu> <loomi-dropmenu-item icon="user" shortcut="⌘K>P">View profile</loomi-dropmenu-item> <loomi-dropmenu-item icon="cog-6-tooth" shortcut="⌘S">Settings</loomi-dropmenu-item> <loomi-dropmenu-item icon="moon">Dark mode</loomi-dropmenu-item></loomi-dropmenu>The shortcut text is a visual hint. Wire the actual keyboard command in your app, then trigger the same action you use for the item click.
Multi-level Menus
Section titled “Multi-level Menus”Add submenu items with slot="submenu" inside the parent item. The parent keeps the
menu open and shows a chevron automatically.
<loomi-dropmenu> <loomi-dropmenu-item icon="user" shortcut="⌘K>P">View profile</loomi-dropmenu-item> <loomi-dropmenu-item icon="cog-6-tooth" shortcut="⌘S">Settings</loomi-dropmenu-item> <loomi-dropmenu-item icon="question-mark-circle"> Support <loomi-dropmenu-item slot="submenu">Documentation</loomi-dropmenu-item> <loomi-dropmenu-item slot="submenu">Contact support</loomi-dropmenu-item> <loomi-dropmenu-item slot="submenu">System status</loomi-dropmenu-item> </loomi-dropmenu-item> <loomi-dropmenu-item icon="cube"> API <loomi-dropmenu-item slot="submenu">API keys</loomi-dropmenu-item> <loomi-dropmenu-item slot="submenu">Webhooks</loomi-dropmenu-item> </loomi-dropmenu-item></loomi-dropmenu>Menu Placement
Section titled “Menu Placement”The panel is placed against the viewport, not inside the component’s own box, and is promoted to the top layer with the popover API. Two consequences worth knowing:
- No ancestor can clip it. A menu opened from the last row of a table with
overflow: auto, from inside a card withoverflow: hidden, or from a sticky toolbar opens whole. This is what makes<loomi-dropmenu>usable for row-level action menus; nothing has to be teleported to<body>by hand. - It flips and shifts to stay on screen. With no room below the trigger the panel opens upward instead, and the arrow moves to the underside to keep pointing at the trigger. Alignment swaps the same way near a left or right edge.
placement is therefore a preference, not a guarantee — a panel that would leave the
viewport still moves.
<loomi-dropmenu placement="auto">…</loomi-dropmenu><loomi-dropmenu placement="left">…</loomi-dropmenu><loomi-dropmenu placement="right">…</loomi-dropmenu>left and right align the panel’s left or right edge with the trigger, opening
downward. To choose the side as well, use the same bottom-*/top-* names the other
floating panels take (<loomi-split-button>, <loomi-popover>) — start/end are the
left/right edges:
<loomi-dropmenu placement="top-end">…</loomi-dropmenu>placement | Alignment | Opens |
|---|---|---|
auto | left edge (left) | downward, flips up |
left | left edge | downward, flips up |
right | right edge | downward, flips up |
bottom-start | left edge | downward, flips up |
bottom-end | right edge | downward, flips up |
top-start | left edge | upward, flips down |
top-end | right edge | upward, flips down |
Styling the panel
Section titled “Styling the panel”The panel is exposed as the menu part, and the arrow’s minimum distance from the
panel’s corners is --loomi-dropmenu-arrow-inset:
loomi-dropmenu::part(menu) { --loomi-dropmenu-arrow-inset: 1rem; min-width: 14rem;}Because the panel lives in the top layer, --loomi-dropmenu-z-index only matters as a
fallback on browsers without popover support.
Submenus
Section titled “Submenus”A submenu is a floating panel in its own right, on the same terms as the menu: it opens
beside the row that owns it, flips to that row’s left when it would run off the right of
the screen, slides up when it is taller than the room beneath the row, and is in the top
layer — so scrollable menus don’t clip it either. A nested submenu follows whichever
side its parent settled on, rather than zig-zagging back across it.
The resolved side is published as data-side="left" | "right" on the submenu, and the
submenu is exposed as the submenu part.
Submenus open on hover and on keyboard focus, and close a moment after the pointer leaves both the row and the panel — the delay is what lets you cross the gap between them.
Scrollable Menus
Section titled “Scrollable Menus”For long lists, cap the menu’s height and let the menu body scroll.
<loomi-dropmenu scrollable height="150"> <loomi-dropmenu-item>Item 1</loomi-dropmenu-item> <loomi-dropmenu-item>Item 2</loomi-dropmenu-item> <!-- … --></loomi-dropmenu>Keeping the Menu Open After a Click
Section titled “Keeping the Menu Open After a Click”By default, clicking an item closes the menu. Set hide-after-click="false" when the
items contain controls such as toggles or checkboxes.
<loomi-dropmenu hide-after-click="false"> <loomi-dropmenu-item><loomi-checkbox>Email notifications</loomi-checkbox></loomi-dropmenu-item> <loomi-dropmenu-item><loomi-checkbox>SMS notifications</loomi-checkbox></loomi-dropmenu-item></loomi-dropmenu>Pairing with <loomi-bell>
Section titled “Pairing with <loomi-bell>”See <loomi-bell>’s README for a worked example of
<loomi-dropmenu> as a notifications panel.
Opening and closing motion
Section titled “Opening and closing motion”The panel drops in from the trigger and plays the reverse on close — a panel that flipped
above its trigger rises in and sinks back down instead, so the motion always reads as
coming out of and returning to the trigger. Submenus fade. prefers-reduced-motion: reduce shortens all of it to near-zero.
The menu counts as closed the moment it is dismissed — isOpen is false, listeners are
released, focus goes back to the trigger — and only the panel’s own visibility waits for
the animation. While it does, the panel keeps its open class, gains a closing one, and
stops taking pointer events so a click can’t land on a menu that is leaving.
Accessibility
Section titled “Accessibility”For the library-wide baseline, see Foundations — Accessibility.
Responsive behavior
Section titled “Responsive behavior”For the shared container and viewport rules, see Foundations — Responsive behavior.
Dark mode
Section titled “Dark mode”For theme activation, token overrides, and contrast guidance, see Foundations — Dark mode.
Attributes
Section titled “Attributes”<loomi-dropmenu>
Section titled “<loomi-dropmenu>”| Attribute | Default | Description |
|---|---|---|
trigger | (ellipsis) | Icon name for the trigger. The -icon suffix is optional. |
trigger-on | click | Open interaction: click or mouseover. |
placement | auto | Panel placement (see above). auto | left | right | bottom-start | bottom-end | top-start | top-end |
divided | false | Divider lines between items. (boolean) |
scrollable | false | Scroll items past height. (boolean) |
height | 200 | Max menu height (px) when scrollable. |
hide-after-click | true | Close the menu after an item click. (boolean) |
icon-right | false | Place every item’s icon after its label. (boolean) |
<loomi-dropmenu-item>
Section titled “<loomi-dropmenu-item>”| Attribute | Default | Description |
|---|---|---|
icon | (blank) | Leading icon name. |
shortcut | (blank) | Right-aligned keyboard shortcut hint. |
icon-right | false | Place the icon after the label. (boolean) |
header | false | Non-clickable section header. (boolean) |
divider | false | Render a divider line. (boolean) |
hover | true | Enable hover styling for a normal item. (boolean) |
disabled | false | Skip navigation/clicks and dim the row. (boolean) |
variant | default | default | destructive (tints the row red). |
checkbox | false | Render as a checkbox row; toggles checked on click. (boolean) |
radio | false | Render as a radio row; use with group and value. (boolean) |
group | (blank) | Shared name that makes radio items mutually exclusive. |
value | (blank) | Value carried by a radio item. |
checked | false | Current state of a checkbox/radio item. (boolean) |
| Slot | Description |
|---|---|
| (default) | Content placed inside the component. |
submenu | Nested menu items. |
trigger | Custom content used to open the component. |
Methods
Section titled “Methods”| Method | Description |
|---|---|
show() | Open the menu. |
hide() | Close the menu. |
isOpen | (getter) true while the menu is open. |
focus() | Focus the trigger — e.g. to hand focus back after a dialog it opened closes. |
blur() | Blur the trigger. |
Events
Section titled “Events”| Event | Description |
|---|---|
change | Fired when the value is committed or changed. |
Full Example
Section titled “Full Example”<loomi-dropmenu placement="right"> <loomi-dropmenu-item header>Profile</loomi-dropmenu-item> <loomi-dropmenu-item icon="user" shortcut="⌘K>P">View profile</loomi-dropmenu-item> <loomi-dropmenu-item icon="cog-6-tooth" shortcut="⌘S">Settings</loomi-dropmenu-item> <loomi-dropmenu-item icon="question-mark-circle"> Support <loomi-dropmenu-item slot="submenu">Documentation</loomi-dropmenu-item> <loomi-dropmenu-item slot="submenu">Contact support</loomi-dropmenu-item> </loomi-dropmenu-item> <loomi-dropmenu-item divider></loomi-dropmenu-item> <loomi-dropmenu-item icon="log-out-01">Sign out</loomi-dropmenu-item></loomi-dropmenu>Framework integration
Section titled “Framework integration”<loomi-dropmenu-item> and <loomi-dropmenu> are standard custom elements, so the browser can use them in plain HTML, Blade, React, Vue, Angular, Svelte, Astro, and most other frameworks. The important beginner rule is: install the package, import it once before the tag is rendered, then write the Loomi tag in your template.
Where to run commands
Section titled “Where to run commands”Run install commands from the app where you want to use this component. That means the folder that contains that app’s package.json. Do not run these install commands from packages/dropmenu unless you are editing LoomiUI itself.
cd /path/to/your-appnpm install @loomidev/dropmenu litIf you are contributing to LoomiUI itself, first move to the top-level components folder. That is where the main package.json for all packages lives, and pnpm --filter ... commands should be run from there:
cd /path/to/your-copy-of-loomiui/componentspnpm --filter @loomidev/dropmenu buildpnpm --filter @loomidev/dropmenu typecheckChoose your framework
Section titled “Choose your framework”Use the CDN version for prototypes, documentation pages, or a quick reproduction. The import map tells the browser where to find Lit, which Loomi components use internally.
<script type="importmap"> { "imports": { "lit": "https://esm.sh/lit@3.3.3", "lit/": "https://esm.sh/lit@3.3.3/" } }</script><script type="module" src="https://esm.sh/@loomidev/dropmenu"></script>
<loomi-dropmenu> <span slot="trigger" style="font-weight:600">Actions</span> <loomi-dropmenu-item icon="pencil-square">Edit</loomi-dropmenu-item> <loomi-dropmenu-item icon="trash">Delete</loomi-dropmenu-item></loomi-dropmenu>In Vite, Webpack, Parcel, Rollup, or a framework build pipeline, install the package and import it once in your main app JavaScript file. After that, you can use the Loomi tag anywhere in your app.
import "@loomidev/dropmenu";Run the install command from your Laravel project root, then import the component in resources/js/app.js. If your project uses Laravel Vite, npm run dev and npm run build should also be run from the Laravel project root.
cd /path/to/your-laravel-appnpm install @loomidev/dropmenu litnpm run devimport "@loomidev/dropmenu";<loomi-dropmenu> <span slot="trigger" style="font-weight:600">Actions</span> <loomi-dropmenu-item icon="pencil-square">Edit</loomi-dropmenu-item> <loomi-dropmenu-item icon="trash">Delete</loomi-dropmenu-item></loomi-dropmenu>React can render Loomi tags directly. If you are on React 18, or if you need to pass arrays, objects, or functions, use a ref and assign those values after the component mounts.
import "@loomidev/dropmenu";
export function LoomiExample() { return ( <loomi-dropmenu> <span slot="trigger" style="font-weight:600">Actions</span> <loomi-dropmenu-item icon="pencil-square">Edit</loomi-dropmenu-item> <loomi-dropmenu-item icon="trash">Delete</loomi-dropmenu-item> </loomi-dropmenu> );}If TypeScript does not recognize the Loomi tag in JSX, add it to your app’s JSX type declarations.
Import the package in the component that uses it, or once in your main Vue file. Vue templates can use Loomi tags directly. For arrays, objects, or functions, pass the value as a JavaScript property instead of as plain text.
<script setup>import "@loomidev/dropmenu";</script>
<template> <loomi-dropmenu> <span slot="trigger" style="font-weight:600">Actions</span> <loomi-dropmenu-item icon="pencil-square">Edit</loomi-dropmenu-item> <loomi-dropmenu-item icon="trash">Delete</loomi-dropmenu-item> </loomi-dropmenu></template>If Vue warns that the tag is an unknown component, configure compilerOptions.isCustomElement for tags that start with loomi- in your Vite or Vue config.
Import the package once and tell Angular to allow custom HTML tags with CUSTOM_ELEMENTS_SCHEMA. For NgModule apps, add the schema to the module instead of the standalone component.
import { CUSTOM_ELEMENTS_SCHEMA, Component } from "@angular/core";import "@loomidev/dropmenu";
@Component({ selector: "app-root", standalone: true, schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <loomi-dropmenu> <span slot="trigger" style="font-weight:600">Actions</span> <loomi-dropmenu-item icon="pencil-square">Edit</loomi-dropmenu-item> <loomi-dropmenu-item icon="trash">Delete</loomi-dropmenu-item> </loomi-dropmenu> `,})export class AppComponent {}Svelte can import the package inside a component script. Astro can import it in the frontmatter of the page or layout where the tag appears.
<script> import "@loomidev/dropmenu";</script>
<loomi-dropmenu> <span slot="trigger" style="font-weight:600">Actions</span> <loomi-dropmenu-item icon="pencil-square">Edit</loomi-dropmenu-item> <loomi-dropmenu-item icon="trash">Delete</loomi-dropmenu-item></loomi-dropmenu>---import "@loomidev/dropmenu";---
<loomi-dropmenu> <span slot="trigger" style="font-weight:600">Actions</span> <loomi-dropmenu-item icon="pencil-square">Edit</loomi-dropmenu-item> <loomi-dropmenu-item icon="trash">Delete</loomi-dropmenu-item></loomi-dropmenu>Server-side rendering notes
Section titled “Server-side rendering notes”Frameworks such as Next.js, Nuxt, SvelteKit, and Astro sometimes render HTML on the server before browser-only code runs. If your framework complains, move the Loomi import to client-side code. In Next.js, that usually means a component with "use client"; in Nuxt, it often means a .client.ts plugin.
Dependencies
Section titled “Dependencies”@loomidev/core@loomidev/icons