Foundations
Action Menu
Dropdown and right-click menus — items with icons and shortcuts, submenus, checkable items and a destructive group.
2 variantsMIT
Preview
Installation
Copies the component into components/ui and installs its dependencies and theme tokens.
pnpm dlx shadcn@latest add https://tuffui.com/r/action-menu.jsonUsage
Pass your own data and wire the callbacks to your agent runtime.
import { Menu, ContextMenu, type MenuGroup } from "@/components/ui/action-menu";
const groups: MenuGroup[] = [
{
items: [
{ id: "copy", label: "Copy", icon: Copy, shortcut: "⌘C", onSelect: copy },
{ id: "regen", type: "submenu", label: "Regenerate with", items: [
{ id: "model", type: "radio", value: model, onValueChange: setModel,
options: [{ value: "opus", label: "opus-5.5" }, { value: "sonnet", label: "sonnet-5.5" }] },
] },
],
},
{ label: "View", items: [
{ id: "reasoning", type: "checkbox", label: "Show reasoning", checked, onCheckedChange: setChecked },
] },
{ items: [{ id: "delete", label: "Delete", icon: Trash, destructive: true, onSelect: remove }] },
];
<Menu groups={groups} trigger={<IconButton icon={DotsThree} label="Actions" />} />
<ContextMenu groups={groups}>
<div tabIndex={0}>Right-click me</div>
</ContextMenu>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| groups* | MenuGroup[] | — | Item groups, separated by spacing and an optional small label. |
| trigger* | ReactElement | — | Menu only. A button-like element that opens it. |
| children* | ReactElement | — | ContextMenu only. The area that opens it on right-click, long-press or the context-menu key. |
| side / align | "top" | "right" | "bottom" | "left" / "start" | "center" | "end" | "bottom" / "start" | Menu only. Placement against the trigger; flips when there's no room. |
| open / onOpenChange | boolean / (open: boolean) => void | — | Optional control of the open state. |
type MenuGroup = { id?: string; label?: string; items: MenuItem[] };
type MenuItem =
| { type?: "item"; id: string; label: ReactNode; icon?: Icon; shortcut?: string;
description?: ReactNode; destructive?: boolean; disabled?: boolean; onSelect?: () => void }
| { type: "checkbox"; id: string; label: ReactNode; icon?: Icon;
checked: boolean; onCheckedChange: (checked: boolean) => void }
| { type: "radio"; id: string; value: string; onValueChange: (value: string) => void;
options: { value: string; label: ReactNode; icon?: Icon; disabled?: boolean }[] }
| { type: "submenu"; id: string; label: ReactNode; icon?: Icon; items: MenuItem[] };