Search

Search the site

All components

Action Menu

Shadcn dropdown preset for item actions: icons, shortcuts, destructive items, labels, and separators.

Default
Default trigger plus a compact list row pattern.

Default more trigger

Open the menu…
NameActions
  • Quarterly report
  • Design tokens
  • API schema
1"use client";
2
3import { useState } from "react";
4import {
5 Copy,
6 ExternalLink,
7 MoreHorizontal,
8 Pencil,
9 Share2,
10 Trash2,
11} from "lucide-react";
12import { Button } from "@/components/ui/button";
13import { ActionMenu, type ActionMenuEntry } from "@/registry/ui";
14
15const rowActions: ActionMenuEntry[] = [
16 {
17 id: "edit",
18 label: "Edit",
19 icon: <Pencil className="size-4" />,
20 onSelect: () => {},
21 },
22 {
23 id: "duplicate",
24 label: "Duplicate",
25 icon: <Copy className="size-4" />,
26 onSelect: () => {},
27 },
28 {
29 id: "share",
30 label: "Share",
31 icon: <Share2 className="size-4" />,
32 shortcut: "⌘S",
33 onSelect: () => {},
34 },
35 { type: "separator" },
36 {
37 id: "open",
38 label: "Open in new tab",
39 icon: <ExternalLink className="size-4" />,
40 onSelect: () =>
41 window.open("https://example.com", "_blank", "noopener,noreferrer"),
42 },
43 { type: "separator" },
44 {
45 id: "delete",
46 label: "Delete",
47 icon: <Trash2 className="size-4" />,
48 variant: "destructive",
49 onSelect: () => {},
50 },
51];
52
53export function ActionMenuDefaultExample() {
54 const [last, setLast] = useState<string>("Open the menu…");
55
56 const actions: ActionMenuEntry[] = [
57 { type: "label", label: "Document" },
58 {
59 id: "edit",
60 label: "Edit",
61 icon: <Pencil className="size-4" />,
62 onSelect: () => setLast("Edit"),
63 },
64 {
65 id: "duplicate",
66 label: "Duplicate",
67 icon: <Copy className="size-4" />,
68 onSelect: () => setLast("Duplicate"),
69 },
70 { type: "separator" },
71 {
72 id: "delete",
73 label: "Delete",
74 icon: <Trash2 className="size-4" />,
75 variant: "destructive",
76 onSelect: () => setLast("Delete"),
77 },
78 ];
79
80 return (
81 <div className="flex flex-col gap-6">
82 <div className="flex flex-wrap items-center gap-4">
83 <p className="text-muted-foreground text-sm">Default more trigger</p>
84 <ActionMenu
85 actions={actions}
86 triggerLabel="Document actions"
87 onOpenChange={(o) => {
88 if (o) setLast("Menu opened");
89 }}
90 />
91 <span className="text-sm tabular-nums text-muted-foreground">
92 {last}
93 </span>
94 </div>
95
96 <div className="bg-card max-w-md rounded-lg border">
97 <div className="text-muted-foreground flex items-center justify-between border-b px-3 py-2 text-xs font-medium uppercase tracking-wide">
98 <span>Name</span>
99 <span className="w-10 text-center">Actions</span>
100 </div>
101 <ul className="divide-y">
102 {["Quarterly report", "Design tokens", "API schema"].map((name) => (
103 <li
104 key={name}
105 className="flex items-center justify-between gap-2 px-3 py-2.5 text-sm"
106 >
107 <span className="min-w-0 truncate font-medium">{name}</span>
108 <ActionMenu
109 actions={rowActions}
110 triggerLabel={`Actions for ${name}`}
111 trigger={
112 <Button
113 type="button"
114 variant="ghost"
115 size="icon"
116 className="size-8 shrink-0"
117 aria-label={`Actions for ${name}`}
118 >
119 <MoreHorizontal className="size-4" />
120 </Button>
121 }
122 />
123 </li>
124 ))}
125 </ul>
126 </div>
127 </div>
128 );
129}

Installation & source

Install via the shadcn CLI or copy the registry files manually.

bash
npx shadcn@latest add @tt-ui/action-menu

Props

NameTypeDefaultDescription
actionsActionMenuEntry[]RequiredAction rows, `{ type: "separator" }`, or `{ type: "label", label }`; duplicate separators are collapsed
triggerLabelstring"Actions"Accessible label for the default more (⋯) trigger
triggerReactElementundefinedOptional custom trigger (single element, `asChild`)
align"start" | "center" | "end""end"Menu alignment relative to trigger
side"top" | "right" | "bottom" | "left""bottom"Preferred side for the menu
open / onOpenChangeboolean / (open: boolean) => voidundefinedControlled open state