Easy Shadcn
Components

Dropdown Menu

Dropdown Menu flattens shadcn's compound menu into a trigger and one homogeneous array of actions. The primitive still owns open state, focus, keyboard interaction, dismissal, and accessible menu semantics.

Basic

Controlled and observable

Menu: closed; last action: None

Presentation

Disabled states

Position and classes

Installation

With the @easy-shadcn namespace configured:

pnpm dlx shadcn@latest add @easy-shadcn/dropdown-menu

Or install via the full URL (zero configuration):

pnpm dlx shadcn@latest add https://easy-shadcn.vercel.app/r/dropdown-menu.json

The underlying shadcn dropdown-menu primitive is installed automatically.

Basic use

The first use case needs only trigger and items:

import { Button } from "@/components/ui/button"
import { DropdownMenu } from "@/components/easy/dropdown-menu"

<DropdownMenu
  trigger={<Button variant="outline">Actions</Button>}
  items={[
    { value: "edit", content: "Edit" },
    { value: "duplicate", content: "Duplicate" },
  ]}
/>

Each value must be unique and stable. It is rendered identity, not selected state; this component is an action menu.

Props

PropTypeDefaultDescription
triggerReactElementrequiredOne element composed as the primitive trigger, with no wrapper button.
itemsDropdownMenuItem[]requiredHomogeneous flat actions. An empty array is valid.
openboolean-Current visibility in controlled mode.
defaultOpenbooleanfalseInitial visibility in uncontrolled mode.
onOpenChange(open, eventDetails) => void-Receives the requested state and exact Base UI event details.
disabledbooleanfalseDisables the composed trigger and prevents opening.
align"start" | "center" | "end""start"Content alignment relative to the trigger.
side"top" | "right" | "bottom" | "left" | "inline-start" | "inline-end""bottom"Preferred content side.
sideOffsetnumber4Distance in pixels between trigger and content.
contentClassNameClassValue-Class override for the popup content.
itemClassNameClassValue-Class override merged into every action.
shortcutClassNameClassValue-Class override merged into every shortcut hint.
FieldTypeDefaultDescription
valuestringrequiredUnique, stable rendered identity.
contentReactNoderequiredVisible content and source of the accessible name.
disabledbooleanfalseKeeps the action visible but prevents activation.
iconReactNode-Decorative content rendered before content.
shortcutReactNode-Decorative shortcut hint rendered after content.
variant"default" | "destructive""default"Primitive item emphasis.
insetbooleanfalseIndents content to align with icon-bearing actions.
onClickMenuPrimitive.Item.Props["onClick"]-Exact Base UI item click event; normal activation closes the menu.
itemClassNameClassValue-Per-item class merged after the global item class.

State and action events

Use defaultOpen for primitive-managed state. In controlled mode, pair open with onOpenChange; the callback preserves Base UI's details object and cancellation methods without translation.

An enabled item's onClick receives the primitive event unchanged. Unless its Base UI handler is prevented, pointer, Enter, and Space activation use the primitive's normal close behavior. Disabled triggers do not open, and disabled actions remain visible without invoking onClick.

Keyboard and accessibility

  • Arrow keys, Home, End, and visible-text typeahead use the primitive's roving-focus behavior.
  • Enter and Space activate the focused action. Escape dismisses and restores focus to the trigger.
  • The trigger, menu, and menu-item roles and relationships come from the primitive.
  • An action's accessible name comes from content. icon and shortcut are deliberately decorative so they do not duplicate or alter that name.

Position and classes

Content opens below the trigger, start-aligned with a four-pixel offset. Override the ordinary placement with align, side, and sideOffset. contentClassName, the global and per-item itemClassName, and shortcutClassName accept ClassValue; per-item classes are merged last while primitive defaults remain intact.

When to use the Primitive instead

Use components/ui/dropdown-menu directly for groups, labels, separators, checkbox or radio items, submenus, custom typeahead labels, heterogeneous layouts, arbitrary compound-component markup, custom portals or positioners, alignment offsets, final-focus control, modal/orientation/loop policies, hover delays, payloads, or imperative actions.

The Compose component deliberately excludes children, render, prop bags, slots objects, insertion callbacks, alternate action APIs, and custom close-on-click behavior. These exclusions keep this thin wrapper limited to one predictable flat action list.

On this page