# Dropdown Menu

A menu of actions built on Base UI Menu with groups, labels, separators, shortcuts, checkboxes, radio items, and nested submenus.

- Category: Overlays & Navigation
- License: MIT
- Page: https://www.saasuji.com/components/dropdown-menu
- Install: npx shadcn@latest add https://www.saasuji.com/r/dropdown-menu.json

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| open (DropdownMenu) | `boolean` | — | Controlled open state of the menu. |
| defaultOpen (DropdownMenu) | `boolean` | `false` | Open state on first render for an uncontrolled menu. |
| onOpenChange (DropdownMenu) | `(open: boolean, eventDetails: MenuRoot.ChangeEventDetails) => void` | — | Called when the menu is opened or closed. |
| onOpenChangeComplete (DropdownMenu) | `(open: boolean) => void` | — | Called after the open or close animation finishes. |
| modal (DropdownMenu) | `boolean` | `true` | Locks page scroll and blocks pointer interaction outside the menu while open. |
| loopFocus (DropdownMenu) | `boolean` | `true` | Loops keyboard focus back to the first item at the end of the list. |
| highlightItemOnHover (DropdownMenu) | `boolean` | `true` | Highlights items when the pointer moves over them. |
| orientation (DropdownMenu) | `'horizontal' | 'vertical'` | `'vertical'` | Visual orientation that controls which arrow keys move focus. |
| disabled (DropdownMenu, DropdownMenuTrigger, DropdownMenuItem) | `boolean` | `false` | Ignores interaction with the menu root, its trigger, or a single item. |
| render (DropdownMenuTrigger, DropdownMenuSubTrigger, DropdownMenuItem) | `React.ReactElement | ((props, state) => React.ReactElement)` | — | Renders a custom element in place of the default trigger, submenu trigger, or item. |
| side (DropdownMenuContent) | `'top' | 'bottom' | 'left' | 'right'` | `'bottom'` | Preferred side of the trigger the menu is positioned against. |
| sideOffset (DropdownMenuContent) | `number` | `4` | Distance in pixels between the trigger and the menu. |
| align (DropdownMenuContent) | `'start' | 'center' | 'end'` | `'start'` | Alignment of the menu along the trigger edge. |
| alignOffset (DropdownMenuContent) | `number` | `0` | Pixel offset applied along the alignment axis. |
| inset (DropdownMenuItem, DropdownMenuSubTrigger, DropdownMenuLabel) | `boolean` | `false` | Indents the part to line up with items that have leading indicators. |
| variant (DropdownMenuItem) | `"default" | "destructive"` | `"default"` | Text treatment for the item; destructive colors the label and highlighted state. |
| closeOnClick (DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem) | `boolean` | `true` | Closes the menu after the item is activated. |
| label (DropdownMenuItem) | `string` | — | Overrides the text used for keyboard typeahead navigation. |
| checked (DropdownMenuCheckboxItem) | `boolean | 'indeterminate'` | — | Checked state of the checkbox item. |
| value (DropdownMenuRadioGroup) | `string` | — | Controlled selected value of the radio group. |
| defaultValue (DropdownMenuRadioGroup) | `string` | — | Initially selected value for an uncontrolled radio group. |
| onValueChange (DropdownMenuRadioGroup) | `(value: string) => void` | — | Called when the selected radio item changes. |
| className (all parts) | `string` | — | Additional classes merged onto the part that receives it. |
| children (all parts) | `React.ReactNode` | — | Trigger label and menu content: labels, groups, items, shortcuts, separators, and nested submenus. |
| ...props (DropdownMenu) | `DropdownMenuPrimitive.Root.Props` | — | All remaining Base UI Menu root props are forwarded (handle, actionsRef, …). |
| ...props (DropdownMenuTrigger, DropdownMenuSubTrigger, DropdownMenuItem) | `DropdownMenuPrimitive.Trigger.Props | DropdownMenuPrimitive.SubmenuTrigger.Props | DropdownMenuPrimitive.Item.Props` | — | All remaining Base UI trigger and item props are forwarded (payload, nativeButton, onClick, …). |
| ...props (DropdownMenuContent, DropdownMenuSubContent, DropdownMenuPortal, DropdownMenuGroup) | `DropdownMenuPrimitive.Popup.Props | DropdownMenuPrimitive.Portal.Props | DropdownMenuPrimitive.Group.Props` | — | All remaining Base UI popup, portal, and group props are forwarded. |
| ...props (DropdownMenuCheckboxItem, DropdownMenuRadioItem, DropdownMenuRadioGroup, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut) | `DropdownMenuPrimitive.CheckboxItem.Props | DropdownMenuPrimitive.RadioItem.Props | DropdownMenuPrimitive.RadioGroup.Props | DropdownMenuPrimitive.GroupLabel.Props | DropdownMenuPrimitive.Separator.Props | React.HTMLAttributes<HTMLSpanElement>` | — | All remaining Base UI and native props are forwarded to the part that receives them. |

## Source

```tsx
"use client"

import * as React from "react"
import { Menu as DropdownMenuPrimitive } from "@base-ui/react/menu"
import Check from "lucide-react/dist/esm/icons/check";
import ChevronRight from "lucide-react/dist/esm/icons/chevron-right";
import Circle from "lucide-react/dist/esm/icons/circle";

import { cn } from "@/lib/utils"

function DropdownMenu({
  ...props
}: DropdownMenuPrimitive.Root.Props) {
  return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
}

function DropdownMenuTrigger({
  ...props
}: DropdownMenuPrimitive.Trigger.Props) {
  return (
    <DropdownMenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />
  )
}

function DropdownMenuGroup({
  ...props
}: DropdownMenuPrimitive.Group.Props) {
  return (
    <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
  )
}

function DropdownMenuPortal({
  ...props
}: DropdownMenuPrimitive.Portal.Props) {
  return (
    <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
  )
}

function DropdownMenuSub({
  ...props
}: DropdownMenuPrimitive.SubmenuRoot.Props) {
  return (
    <DropdownMenuPrimitive.SubmenuRoot data-slot="dropdown-menu-sub" {...props} />
  )
}

function DropdownMenuRadioGroup({
  ...props
}: DropdownMenuPrimitive.RadioGroup.Props) {
  return (
    <DropdownMenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />
  )
}

function DropdownMenuSubTrigger({
  className,
  inset,
  children,
  ...props
}: DropdownMenuPrimitive.SubmenuTrigger.Props & {
  inset?: boolean
}) {
  return (
    <DropdownMenuPrimitive.SubmenuTrigger
      data-slot="dropdown-menu-sub-trigger"
      data-inset={inset}
      className={cn(
        "flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
        inset && "pl-8",
        className
      )}
      {...props}
    >
      {children}
      <ChevronRight className="ml-auto" />
    </DropdownMenuPrimitive.SubmenuTrigger>
  )
}

function DropdownMenuSubContent({
  className,
  ...props
}: DropdownMenuPrimitive.Popup.Props) {
  return (
    <DropdownMenuPrimitive.Popup
      data-slot="dropdown-menu-sub-content"
      className={cn(
        "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg transition-all duration-150 data-ending-style:opacity-0 data-ending-style:scale-95 data-starting-style:opacity-0 data-starting-style:scale-95",
        className
      )}
      {...props}
    />
  )
}

function DropdownMenuContent({
  className,
  side = "bottom",
  sideOffset = 4,
  align = "start",
  alignOffset = 0,
  ...props
}: DropdownMenuPrimitive.Popup.Props & {
  side?: DropdownMenuPrimitive.Positioner.Props["side"]
  sideOffset?: DropdownMenuPrimitive.Positioner.Props["sideOffset"]
  align?: DropdownMenuPrimitive.Positioner.Props["align"]
  alignOffset?: DropdownMenuPrimitive.Positioner.Props["alignOffset"]
}) {
  return (
    <DropdownMenuPrimitive.Portal>
      <DropdownMenuPrimitive.Positioner
        data-slot="dropdown-menu-content-wrapper"
        className="z-50"
        side={side}
        sideOffset={sideOffset}
        align={align}
        alignOffset={alignOffset}
      >
        <DropdownMenuPrimitive.Popup
          data-slot="dropdown-menu-content"
          className={cn(
            "min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md transition-all duration-150 data-ending-style:opacity-0 data-ending-style:scale-95 data-starting-style:opacity-0 data-starting-style:scale-95",
            className
          )}
          {...props}
        />
      </DropdownMenuPrimitive.Positioner>
    </DropdownMenuPrimitive.Portal>
  )
}

function DropdownMenuItem({
  className,
  inset,
  variant = "default",
  ...props
}: DropdownMenuPrimitive.Item.Props & {
  inset?: boolean
  variant?: "default" | "destructive"
}) {
  return (
    <DropdownMenuPrimitive.Item
      data-slot="dropdown-menu-item"
      data-inset={inset}
      data-variant={variant}
      className={cn(
        "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[variant=destructive]:text-destructive data-[variant=destructive]:data-[highlighted]:bg-destructive/10 data-[variant=destructive]:data-[highlighted]:text-destructive [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
        inset && "pl-8",
        className
      )}
      {...props}
    />
  )
}

function DropdownMenuCheckboxItem({
  className,
  children,
  checked,
  ...props
}: DropdownMenuPrimitive.CheckboxItem.Props) {
  return (
    <DropdownMenuPrimitive.CheckboxItem
      data-slot="dropdown-menu-checkbox-item"
      className={cn(
        "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
        className
      )}
      checked={checked}
      {...props}
    >
      <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
        <DropdownMenuPrimitive.CheckboxItemIndicator>
          <Check className="h-4 w-4" />
        </DropdownMenuPrimitive.CheckboxItemIndicator>
      </span>
      {children}
    </DropdownMenuPrimitive.CheckboxItem>
  )
}

function DropdownMenuRadioItem({
  className,
  children,
  ...props
}: DropdownMenuPrimitive.RadioItem.Props) {
  return (
    <DropdownMenuPrimitive.RadioItem
      data-slot="dropdown-menu-radio-item"
      className={cn(
        "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
        className
      )}
      {...props}
    >
      <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
        <DropdownMenuPrimitive.RadioItemIndicator>
          <Circle className="h-2 w-2 fill-current" />
        </DropdownMenuPrimitive.RadioItemIndicator>
      </span>
      {children}
    </DropdownMenuPrimitive.RadioItem>
  )
}

function DropdownMenuLabel({
  className,
  inset,
  ...props
}: DropdownMenuPrimitive.GroupLabel.Props & {
  inset?: boolean
}) {
  return (
    <DropdownMenuPrimitive.GroupLabel
      data-slot="dropdown-menu-label"
      data-inset={inset}
      className={cn(
        "px-2 py-1.5 text-sm font-semibold",
        inset && "pl-8",
        className
      )}
      {...props}
    />
  )
}

function DropdownMenuSeparator({
  className,
  ...props
}: DropdownMenuPrimitive.Separator.Props) {
  return (
    <DropdownMenuPrimitive.Separator
      data-slot="dropdown-menu-separator"
      className={cn("-mx-1 my-1 h-px bg-muted", className)}
      {...props}
    />
  )
}

function DropdownMenuShortcut({
  className,
  ...props
}: React.HTMLAttributes<HTMLSpanElement>) {
  return (
    <span
      data-slot="dropdown-menu-shortcut"
      className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
      {...props}
    />
  )
}

export {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuCheckboxItem,
  DropdownMenuRadioItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuShortcut,
  DropdownMenuGroup,
  DropdownMenuPortal,
  DropdownMenuSub,
  DropdownMenuSubContent,
  DropdownMenuSubTrigger,
  DropdownMenuRadioGroup,
}

```
