npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@loomidev/dropmenu

v0.3.0

Published

<loomi-dropmenu> — a dropdown action menu.

Readme

@loomidev/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 lit
import "@loomidev/dropmenu";

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

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

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

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

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

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>

Icons

<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

<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

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

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

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

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

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

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

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 with overflow: 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

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

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

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

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>

See <loomi-bell>'s README for a worked example of <loomi-dropmenu> as a notifications panel.

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

For the library-wide baseline, see Foundations — Accessibility.

Responsive behavior

For the shared container and viewport rules, see Foundations — Responsive behavior.

Dark mode

For theme activation, token overrides, and contrast guidance, see Foundations — Dark mode.

Attributes

<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>

| 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) |

Slots

| Slot | Description | | ----------- | ------------------------------------------ | | (default) | Content placed inside the component. | | submenu | Nested menu items. | | trigger | Custom content used to open the component. |

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

| Event | Description | | -------- | --------------------------------------------- | | change | Fired when the value is committed or changed. |

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

<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

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-app
npm install @loomidev/dropmenu lit

If 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/components
pnpm --filter @loomidev/dropmenu build
pnpm --filter @loomidev/dropmenu typecheck

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/[email protected]", "lit/": "https://esm.sh/[email protected]/" } }
</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-app
npm install @loomidev/dropmenu lit
npm run dev
// resources/js/app.js
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>

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.

// app.component.ts
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

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

  • @loomidev/core
  • @loomidev/icons