Composite
DropMenu
DropMenu builds on popover primitives with menu semantics for profile menus, action lists, and command palettes. Pair with an avatar or icon trigger for app-shell headers.
Installation
npx shadcn add https://rck.vibeboxph.com//r/drop-menu.jsonLive preview & controls
Acme Labs
Click the trigger to open the menu
Controls
"use client";
import { Avatar } from "@/registry/rck/ui/avatar";
import { Button } from "@/registry/rck/ui/button";
import {
DropMenu,
DropMenuContent,
DropMenuItem,
DropMenuLabel,
DropMenuSeparator,
DropMenuTrigger,
} from "@/registry/rck/ui/drop-menu";
import { Icon } from "@/registry/rck/ui/icon";
export default function DropMenuDemo() {
return (
<div className="flex w-full max-w-md items-center justify-between gap-4 rounded-xl border border-border bg-surface/80 px-4 py-3 shadow-sm">
<div className="min-w-0">
<p className="truncate text-sm font-semibold">Acme Labs</p>
<p className="truncate text-xs text-muted-foreground">Click the trigger to open the menu</p>
</div>
<DropMenu>
<DropMenuTrigger asChild>
<button
type="button"
className="inline-flex items-center gap-2.5 rounded-full border border-border bg-background py-1 pr-2 pl-1 shadow-sm outline-none transition-colors hover:bg-surface-2 focus-visible:ring-2 focus-visible:ring-ring"
aria-label="Open account menu"
>
<Avatar name="Ada Lovelace" className="size-8" />
<span className="hidden min-w-0 flex-col items-start text-left sm:flex">
<span className="max-w-[8rem] truncate text-sm font-semibold leading-tight">Ada Lovelace</span>
<span className="text-[11px] leading-tight text-muted-foreground">Admin</span>
</span>
<Icon name="chevron-down" size="xs" className="text-muted-foreground" />
</button>
</DropMenuTrigger>
<DropMenuContent className="w-56">
<div className="px-2 py-2">
<p className="truncate text-sm font-semibold">Ada Lovelace</p>
<p className="truncate text-[11px] text-muted-foreground">ada@acmelabs.io</p>
</div>
<DropMenuSeparator />
<DropMenuItem icon="plus">Create project</DropMenuItem>
<DropMenuItem icon="settings">Settings</DropMenuItem>
<DropMenuItem icon="external-link">Documentation</DropMenuItem>
<DropMenuSeparator />
<DropMenuItem icon="close" destructive>
Sign out
</DropMenuItem>
</DropMenuContent>
</DropMenu>
</div>
);
}Anatomy
DropMenuThe root, holds open state.DropMenuTriggerThe button or custom element that opens the menu.DropMenuContentThe floating menu panel.DropMenuItemA selectable action row; closes the menu on activate.DropMenuCheckboxItemA menu row with a trailing check indicator for toggle state.DropMenuGroupGroups related menu items with `role="group"`.DropMenuLabel / DropMenuSeparatorNon-interactive section label and divider.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| open / onOpenChange | boolean / (open: boolean) => void | — | Controlled open state. |
| defaultOpen | boolean | — | Initial open state when uncontrolled. |
| modal | boolean | — | When true, limits interaction outside the menu like a dialog. |
| asChild (DropMenuTrigger) | boolean | — | Merge props onto the child element instead of rendering a button. |
| align / side (DropMenuContent) | start | center | end / top | right | bottom | left | — | Placement relative to the trigger. |
| sideOffset (DropMenuContent) | number | 6 | Gap in pixels between trigger and menu. |
| inset (DropMenuItem / DropMenuLabel) | boolean | — | Extra left padding for nested menu rows. |
| icon (DropMenuItem) | IconName | — | Optional leading icon from the curated registry. |
| destructive (DropMenuItem) | boolean | — | Applies destructive styling for irreversible actions. |
| onSelect (DropMenuItem) | () => void | — | Callback fired when the item is activated. |
| checked (DropMenuCheckboxItem) | boolean | — | Shows a trailing check when true. |
Accessibility
- Trigger exposes `aria-haspopup="menu"`; content renders as `role="menu"` with `menuitem` rows.
- Items are native buttons for keyboard activation; the menu closes after a selection.
- Use `DropMenuLabel` for section headings and keep destructive actions at the bottom separated by a `DropMenuSeparator`.
Source
registry/rck/ui/drop-menu.tsx
"use client";
import * as React from "react";
import * as PopoverPrimitive from "@/registry/rck/primitives/popover";
import { cn } from "@/lib/utils";
import { Icon, type IconName } from "@/registry/rck/ui/icon";
export const DropMenu = PopoverPrimitive.Root;
export const DropMenuTrigger = React.forwardRef<
HTMLButtonElement,
React.ButtonHTMLAttributes<HTMLButtonElement> & { asChild?: boolean }
>(({ asChild, ...props }, ref) => (
<PopoverPrimitive.Trigger ref={ref} asChild={asChild} {...props} aria-haspopup="menu" />
));
DropMenuTrigger.displayName = "DropMenuTrigger";
export const DropMenuContent = React.forwardRef<
HTMLDivElement,
Omit<React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>, "placement"> & {
side?: "top" | "right" | "bottom" | "left";
}
>(({ className, align = "end", side = "bottom", sideOffset = 6, ...props }, ref) => (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
ref={ref}
role="menu"
aria-orientation="vertical"
align={align}
placement={side}
sideOffset={sideOffset}
className={cn(
"border-border bg-popover text-popover-foreground z-50 min-w-[12rem] overflow-hidden rounded-lg border p-1 shadow-md outline-none",
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0",
"data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
"data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2",
"data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className,
)}
{...props}
/>
</PopoverPrimitive.Portal>
));
DropMenuContent.displayName = "DropMenuContent";
export function DropMenuLabel({
className,
inset = false,
...props
}: React.HTMLAttributes<HTMLDivElement> & { inset?: boolean }) {
return (
<div
role="presentation"
data-slot="drop-menu-label"
className={cn(
"text-muted-foreground px-2 py-1.5 text-[11px] font-semibold",
inset && "pl-8",
className,
)}
{...props}
/>
);
}
export function DropMenuSeparator({
className,
...props
}: React.HTMLAttributes<HTMLHRElement>) {
return (
<hr
role="separator"
aria-orientation="horizontal"
data-slot="drop-menu-separator"
className={cn("bg-border -mx-1 my-1 h-px border-0", className)}
{...props}
/>
);
}
export function DropMenuGroup({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
role="group"
data-slot="drop-menu-group"
className={cn("flex flex-col gap-0.5", className)}
{...props}
/>
);
}
export interface DropMenuItemProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
inset?: boolean;
destructive?: boolean;
icon?: IconName;
/** Fires before the menu closes. */
onSelect?: () => void;
}
export const DropMenuItem = React.forwardRef<HTMLButtonElement, DropMenuItemProps>(
(
{
className,
inset = false,
destructive = false,
icon,
children,
disabled,
onSelect,
onClick,
...props
},
ref,
) => {
return (
<PopoverPrimitive.Close asChild>
<button
ref={ref}
type="button"
role="menuitem"
disabled={disabled}
className={cn(
"flex w-full cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-left text-[11px] font-medium transition-colors outline-none",
"focus-visible:bg-surface-2 focus-visible:text-foreground",
"disabled:pointer-events-none disabled:opacity-50",
destructive
? "text-destructive hover:bg-destructive/10 focus-visible:bg-destructive/10"
: "text-foreground hover:bg-surface-2",
inset && "pl-8",
className,
)}
onClick={(event) => {
onClick?.(event);
if (!event.defaultPrevented) onSelect?.();
}}
{...props}
>
{icon ? <Icon name={icon} size="xs" className="text-muted-foreground" /> : null}
<span className="flex-1 truncate">{children}</span>
</button>
</PopoverPrimitive.Close>
);
},
);
DropMenuItem.displayName = "DropMenuItem";
export interface DropMenuCheckboxItemProps extends DropMenuItemProps {
checked?: boolean;
}
export const DropMenuCheckboxItem = React.forwardRef<
HTMLButtonElement,
DropMenuCheckboxItemProps
>(({ className, checked, children, ...props }, ref) => {
return (
<DropMenuItem ref={ref} className={cn("relative pr-8", className)} {...props}>
<span className="absolute right-2 flex size-3.5 items-center justify-center">
{checked ? <Icon name="check" size="xs" className="text-primary" /> : null}
</span>
{children}
</DropMenuItem>
);
});
DropMenuCheckboxItem.displayName = "DropMenuCheckboxItem";
Related examples
Action menuBasic trigger with icons and grouped items.
Profile menuAvatar trigger with account header and sign-out.
ControlledExternal open state and a checkbox-style item.