Meowland docs
Workbench

Component reference

Menu

A list of actions in a popover, with keyboard navigation and nested submenus.

Preview

Groups and keyboard operation

Arrow keys and Home/End move the focus, typing text jumps to an item; Escape returns to the trigger button. All actions only update the local receipt.

packages/meowtail/stories/menu.stories.tsxline 14tsx
render() {
  const feedback = signal("No action selected yet.");
  return (
    <Stack align="start">
      <Menu
        id="story-actions"
        trigger={(bindings) => (
          <Button variant="outline" {...bindings}>
            Actions
            <Icon name="chevron-down" />
          </Button>
        )}
        items={[
          {
            type: "group",
            id: "file",
            label: "File",
            items: [
              {
                id: "open",
                label: "Open",
                icon: <Icon name="file" />,
                shortcut: "↵",
                onSelect: () => {
                  feedback.value = "Selected Open.";
                },
              },
              {
                id: "copy",
                label: "Copy",
                icon: <Icon name="copy" />,
                onSelect: () => {
                  feedback.value = "Selected Copy.";
                },
              },
              { id: "export", label: "Export (unavailable)", disabled: true, onSelect: () => {} },
            ],
          },
          { type: "separator", id: "divider" },
          {
            id: "remove",
            label: "Remove",
            danger: true,
            onSelect: () => {
              feedback.value = "Selected Remove sample.";
            },
          },
        ]}
      />
      <Note role="status">{feedback}</Note>
    </Stack>
  );
}

Installation

bun add meowtail

Usage

usage.tsts
import { Menu } from "meowtail";

Examples

The other stories that show this component.

Nesting and touch

Hover or click to open a submenu; the right-arrow direction enters it, the left-arrow direction goes back, and Escape closes one level at a time.

packages/meowtail/stories/menu.stories.tsxline 77tsx
render() {
  const feedback = signal("No format selected yet.");
  return (
    <Stack align="start">
      <Menu
        id="nested-actions"
        trigger={(bindings) => (
          <Button variant="outline" {...bindings}>
            Format actions
          </Button>
        )}
        items={[
          {
            id: "export",
            label: "Export format",
            children: [
              {
                id: "markdown",
                label: "Markdown",
                onSelect: () => {
                  feedback.value = "Selected Markdown.";
                },
              },
              { id: "disabled", label: "PDF (unavailable)", disabled: true, onSelect: () => {} },
              {
                id: "more",
                label: "More formats",
                children: [
                  {
                    id: "text",
                    label: "Plain text",
                    onSelect: () => {
                      feedback.value = "Selected Plain text.";
                    },
                  },
                ],
              },
            ],
          },
          {
            id: "locked",
            label: "Unavailable submenu",
            disabled: true,
            children: [{ id: "locked-child", label: "Unreachable item", onSelect: () => {} }],
          },
          {
            id: "copy",
            label: "Copy",
            onSelect: () => {
              feedback.value = "Selected Copy.";
            },
          },
        ]}
      />
      <Note role="status">{feedback}</Note>
    </Stack>
  );
}

API reference

Props

PropTypeDefault
idRequired
string–
triggerRequired
(bindings: MenuTriggerBindings) => Child–
itemsRequired
readonly MenuEntry[]–
open
MaybeSignal<boolean>–
onOpenChange
(open: boolean) => void–
align
"start" | "end"–
anchorRectPosition against a point such as a right-click, instead of the trigger box.
() => DOMRect–
anchorElementElement whose layout defines a custom rect; observed by the same autoUpdate.
() => HTMLElement | null–
placement
"bottom" | "right""bottom"
closeAncestors
() => void–

Also takes className, children and the attributes and events of its root element.

DOM parts

rootitemiconlabelshortcutgroupheadingseparatorbranch

Registry

The component is registered as menu in the meowtail component registry.