# Dropdown Menu

- Page: <https://kznlabs.github.io/labsUI/components/dropdown-menu>

## Dropdown Menu (`dropdown-menu`, component)

Action menu anchored to a Button: items with icons and shortcuts, checkbox and radio items, labels, separators, destructive variant. Arrow keys, typeahead, focus follows the pointer, Esc returns focus.

- Install: `npx @kznlabs/labsui add dropdown-menu`
- Docs: <https://kznlabs.github.io/labsUI/components/dropdown-menu>
- Brings: button, button-variants, list, platform, use-anchor, use-controllable-state, use-dismiss, utils
- npm: clsx@^2.1.1, lucide-react@1.63.0, motion@14.7.1, tailwind-merge@^3.7.0

### API

- `<DropdownMenu>`: Action menu anchored to a button. Controlled (`open` + `onOpenChange`) or uncontrolled.
  - `children: ReactNode`: Trigger and content.
  - `defaultOpen?: boolean` = `false`: Open at first render (uncontrolled).
  - `onOpenChange?: (open: boolean) => void`: Called on the trigger, Esc, outside click or a selection.
  - `open?: boolean`: Controlled state; pair with `onOpenChange`.
- `<DropdownMenuTrigger>` (+ Button props): A Button. Keyboard: Enter, Space or Down opens on the first item, Up on the last.
  - `children?: ReactNode`: Label; its width is reserved so loading and success never resize the button.
  - `confirmLabel?: ReactNode`: Two-step action: first click swaps the label to this, second click runs `onClick`.
  - `confirmTimeout?: number`: Ms before an armed confirm step resets. Also resets on blur or Esc.
  - `loading?: boolean`: Disables the button and shows a spinner before the label.
  - `loadingLabel?: ReactNode`: Label shown while loading, with the spinner. Defaults to `children`.
  - `size?: "default" | "sm" | "lg" | "icon"`: Height and padding; "icon" is square, so give it an `aria-label`.
  - `status?: ButtonStatus`: In-place feedback: `loading` spins, `success` swaps the label to `successLabel`.
  - `successLabel?: ReactNode`: Text shown on `status="success"`, with a check icon. Defaults to "Done".
  - `swap?: SwapDirection`: Where the old label slides out to on a swap. Default "left".
  - `variant?: "default" | "secondary" | "outline" | "ghost" | "destructive"`: Fill and emphasis: one primary action per view, `destructive` for irreversible ones.
- `<DropdownMenuContent>`
  - `children: ReactNode`: Items, labels, groups and separators.
  - `align?: "start" | "end"` = `"start"`: Edge of the trigger the menu lines up with.
- `<DropdownMenuItem>`
  - `children: ReactNode`: Label, optionally with a leading icon.
  - `disabled?: boolean`: Shown but not selectable.
  - `onSelect?: () => void`: Called on click or Enter; the menu then closes.
  - `shortcut?: string`: Right-aligned hint, e.g. "⌘S".
  - `variant?: "default" | "destructive"` = `"default"`: "destructive": red, for irreversible actions.
- `<DropdownMenuCheckboxItem>`: Toggles without closing the menu.
  - `checked: boolean`: Shows the check mark.
  - `children: ReactNode`: Label, optionally with a leading icon.
  - `onCheckedChange: (checked: boolean) => void`: Called with the new state.
  - `disabled?: boolean`: Shown but not selectable.
  - `shortcut?: string`: Right-aligned hint, e.g. "⌘S".
- `<DropdownMenuRadioGroup>`
  - `children: ReactNode`: `DropdownMenuRadioItem`s.
  - `onValueChange: (value: string) => void`: Called with the picked item's value.
  - `value: string`: Value of the checked item.
- `<DropdownMenuRadioItem>`: One choice of a RadioGroup; picking it keeps the menu open.
  - `children: ReactNode`: Label, optionally with a leading icon.
  - `value: string`: Checked when it equals the group's `value`.
  - `disabled?: boolean`: Shown but not selectable.
- `<DropdownMenuLabel>` (+ <div> props)
- `<DropdownMenuSeparator>`

### Usage

```tsx
<DropdownMenu><DropdownMenuTrigger variant="outline">Open</DropdownMenuTrigger><DropdownMenuContent align="end"><DropdownMenuItem onSelect={edit} shortcut="⌘E">Edit</DropdownMenuItem></DropdownMenuContent></DropdownMenu>
```

### Example: clip-actions.tsx

```tsx
"use client";

import { Copy, MoreHorizontal, Pencil, Trash2 } from "lucide-react";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";

export function ClipActions({ onDelete }: { onDelete: () => void }) {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger variant="ghost" size="icon" aria-label="Clip actions">
        <MoreHorizontal />
      </DropdownMenuTrigger>
      <DropdownMenuContent align="end">
        <DropdownMenuItem><Pencil />Rename</DropdownMenuItem>
        <DropdownMenuItem shortcut="⌘C"><Copy />Copy link</DropdownMenuItem>
        <DropdownMenuSeparator />
        <DropdownMenuItem variant="destructive" onSelect={onDelete}><Trash2 />Delete</DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}
```
