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
default-example.tsx
1"use client";23import { 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";1415const 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];5253export function ActionMenuDefaultExample() {54 const [last, setLast] = useState<string>("Open the menu…");5556 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 ];7980 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 <ActionMenu85 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>9596 <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 <li104 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 <ActionMenu109 actions={rowActions}110 triggerLabel={`Actions for ${name}`}111 trigger={112 <Button113 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
| Name | Type | Default | Description |
|---|---|---|---|
| actions | ActionMenuEntry[] | Required | Action rows, `{ type: "separator" }`, or `{ type: "label", label }`; duplicate separators are collapsed |
| triggerLabel | string | "Actions" | Accessible label for the default more (⋯) trigger |
| trigger | ReactElement | undefined | Optional 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 / onOpenChange | boolean / (open: boolean) => void | undefined | Controlled open state |