# Dropdown menu

> A menu of actions and options behind a button, with groups, submenus, checkbox and radio items, and shortcuts.

Docs: https://hextaui.com/docs/dropdown-menu
Markdown: https://hextaui.com/docs/dropdown-menu.md

```tsx title="components/examples/dropdown-menu/demo.tsx"
import {
  IconBrandGithub,
  IconCreditCard,
  IconExternalLink,
  IconKeyboard,
  IconLogout,
  IconMail,
  IconMessage,
  IconPlus,
  IconSettings,
  IconUser,
  IconUserPlus,
} from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuShortcut,
  DropdownMenuSub,
  DropdownMenuSubContent,
  DropdownMenuSubTrigger,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

export function DropdownMenuDemo() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger render={<Button variant="outline" />}>
        Open
      </DropdownMenuTrigger>
      <DropdownMenuContent className="w-56">
        <DropdownMenuGroup>
          <DropdownMenuLabel>My account</DropdownMenuLabel>
          <DropdownMenuItem>
            <IconUser />
            Profile
            <DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
          </DropdownMenuItem>
          <DropdownMenuItem>
            <IconCreditCard />
            Billing
            <DropdownMenuShortcut>⌘B</DropdownMenuShortcut>
          </DropdownMenuItem>
          <DropdownMenuItem>
            <IconSettings />
            Settings
            <DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
          </DropdownMenuItem>
          <DropdownMenuItem>
            <IconKeyboard />
            Keyboard shortcuts
            <DropdownMenuShortcut>⌘K</DropdownMenuShortcut>
          </DropdownMenuItem>
        </DropdownMenuGroup>
        <DropdownMenuSeparator />
        <DropdownMenuGroup>
          <DropdownMenuSub>
            <DropdownMenuSubTrigger>
              <IconUserPlus />
              Invite users
            </DropdownMenuSubTrigger>
            <DropdownMenuSubContent>
              <DropdownMenuItem>
                <IconMail />
                Email
              </DropdownMenuItem>
              <DropdownMenuItem>
                <IconMessage />
                Message
              </DropdownMenuItem>
              <DropdownMenuSeparator />
              <DropdownMenuItem>
                <IconPlus />
                More…
              </DropdownMenuItem>
            </DropdownMenuSubContent>
          </DropdownMenuSub>
          <DropdownMenuItem
            render={<a href="https://github.com" target="_blank" />}
          >
            <IconBrandGithub />
            GitHub
            <IconExternalLink className="ms-auto" />
          </DropdownMenuItem>
          <DropdownMenuItem disabled>API</DropdownMenuItem>
        </DropdownMenuGroup>
        <DropdownMenuSeparator />
        <DropdownMenuItem variant="destructive">
          <IconLogout />
          Log out
          <DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
```

## Installation

### CLI

```bash
npx shadcn@latest add https://hextaui.com/r/dropdown-menu.json
```

This adds the component, the HextaUI theme tokens and any HextaUI components it depends on.

### Manual

Add the HextaUI theme tokens (https://hextaui.com/docs/installation#theme) to your global CSS if you haven't yet, then install the dependencies.

```bash
pnpm add @base-ui/react @tabler/icons-react class-variance-authority cn
```

Copy and paste the following code into your project.

```tsx title="components/ui/dropdown-menu.tsx"
"use client"

import * as React from "react"
import {
  DirectionProvider,
  useDirection,
  type TextDirection,
} from "@base-ui/react/direction-provider"
import { Menu as MenuPrimitive } from "@base-ui/react/menu"
import { mergeProps } from "@base-ui/react/merge-props"
import { useRender } from "@base-ui/react/use-render"
import {
  IconCheck,
  IconChevronRight,
  IconCircleFilled,
} from "@tabler/icons-react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"

import { SafeAreaOverlay } from "@/components/ui/safe-area"

type ClassName<State> =
  string | ((state: State) => string | undefined) | undefined

function mergeClassName<State>(base: string, className: ClassName<State>) {
  return typeof className === "function"
    ? (state: State) => cn(base, className(state))
    : cn(base, className)
}

type DropdownMenuContextValue = {
  direction: TextDirection
  chosen: string | null
  choose: (id: string) => void
}

const DropdownMenuContext =
  React.createContext<DropdownMenuContextValue | null>(null)

const DropdownMenuGroupContext = React.createContext(false)

function useDropdownMenuContext(part: string) {
  const context = React.useContext(DropdownMenuContext)
  if (!context) {
    throw new Error(`${part} must be used within <DropdownMenu>.`)
  }
  return context
}

function readDirection(element: Element | null | undefined) {
  if (!element || !element.isConnected) {
    return undefined
  }
  return getComputedStyle(element).direction === "rtl" ? "rtl" : "ltr"
}

function getSubmenuElements() {
  const triggers = document.querySelectorAll(
    "[data-slot=dropdown-menu-sub-trigger][data-popup-open]"
  )
  const reference = triggers[triggers.length - 1]
  const id = reference?.getAttribute("aria-controls")
  const floating = id ? document.getElementById(id) : null
  return reference && floating ? { reference, floating } : null
}

type DropdownMenuProps<Payload> = MenuPrimitive.Root.Props<Payload> & {
  showSafeArea?: boolean
}

function DropdownMenu<Payload>({
  onOpenChange,
  onOpenChangeComplete,
  showSafeArea = false,
  children,
  ...props
}: DropdownMenuProps<Payload>) {
  const [open, setOpen] = React.useState(false)
  const inherited = useDirection()
  const [triggerDirection, setTriggerDirection] =
    React.useState<TextDirection>()
  const [chosen, setChosen] = React.useState<string | null>(null)
  const direction = triggerDirection === "rtl" ? "rtl" : inherited

  const context = React.useMemo(
    () => ({ direction, chosen, choose: setChosen }),
    [direction, chosen]
  )

  return (
    <DropdownMenuContext.Provider value={context}>
      <DirectionProvider direction={direction}>
        <MenuPrimitive.Root
          onOpenChange={(open, eventDetails) => {
            onOpenChange?.(open, eventDetails)
            if (!eventDetails.isCanceled) {
              setOpen(open)
            }
            if (open && !eventDetails.isCanceled) {
              setChosen(null)
              if (eventDetails.trigger) {
                setTriggerDirection(readDirection(eventDetails.trigger))
              }
            }
          }}
          onOpenChangeComplete={(open) => {
            onOpenChangeComplete?.(open)
            if (!open) {
              setChosen(null)
            }
          }}
          {...props}
        >
          {children}
        </MenuPrimitive.Root>
        {showSafeArea && (props.open ?? open) ? (
          <SafeAreaOverlay getElements={getSubmenuElements} />
        ) : null}
      </DirectionProvider>
    </DropdownMenuContext.Provider>
  )
}

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

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

type DropdownMenuContentProps = MenuPrimitive.Popup.Props &
  Pick<
    MenuPrimitive.Positioner.Props,
    | "side"
    | "align"
    | "sideOffset"
    | "alignOffset"
    | "anchor"
    | "collisionPadding"
    | "collisionAvoidance"
    | "sticky"
  > & {
    portalProps?: Omit<MenuPrimitive.Portal.Props, "children">
  }

const dropdownMenuContentClassName =
  "relative max-h-(--available-height) max-w-[min(var(--available-width),20rem)] min-w-40 origin-(--transform-origin) overflow-x-hidden overflow-y-auto overscroll-none rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-(length:--hairline) forced-colors:border ring-foreground/10 transition-[opacity,scale] duration-150 ease-out-quint outline-none focus-visible:outline-hidden [--dropdown-menu-item-radius:max(calc(var(--radius-sm)*0.5),calc(var(--radius-lg)-0.25rem))] data-ending-style:opacity-0 data-starting-style:opacity-0 data-ending-style:data-instant:transition-none motion-safe:data-starting-style:scale-96 motion-safe:data-[chosen]:data-ending-style:delay-120 motion-reduce:transition-opacity"

const SIDE_COLLISION_AVOIDANCE = { fallbackAxisSide: "end" } as const

function DropdownMenuPopup({
  slot,
  className,
  side,
  align,
  sideOffset,
  alignOffset,
  anchor,
  collisionPadding = 8,
  collisionAvoidance,
  sticky,
  portalProps,
  ...props
}: DropdownMenuContentProps & { slot: string }) {
  const { direction, chosen } = useDropdownMenuContext(
    slot === "dropdown-menu-content"
      ? "DropdownMenuContent"
      : "DropdownMenuSubContent"
  )

  return (
    <MenuPrimitive.Portal {...portalProps}>
      <MenuPrimitive.Positioner
        data-slot={slot.replace("content", "positioner")}
        dir={direction === "rtl" ? "rtl" : undefined}
        side={side}
        align={align}
        sideOffset={sideOffset}
        alignOffset={alignOffset}
        anchor={anchor}
        collisionPadding={collisionPadding}
        collisionAvoidance={
          collisionAvoidance ??
          (side === "bottom" || side === "top"
            ? undefined
            : SIDE_COLLISION_AVOIDANCE)
        }
        sticky={sticky}
        className="isolate z-50 outline-none focus-visible:outline-hidden"
      >
        <MenuPrimitive.Popup
          data-slot={slot}
          data-chosen={chosen === null ? undefined : ""}
          className={mergeClassName(
            cn(
              dropdownMenuContentClassName,
              slot === "dropdown-menu-content" &&
                "min-w-[max(var(--anchor-width),10rem)]"
            ),
            className
          )}
          {...props}
        />
      </MenuPrimitive.Positioner>
    </MenuPrimitive.Portal>
  )
}

function DropdownMenuContent({
  side = "bottom",
  align = "start",
  sideOffset = 4,
  alignOffset = 0,
  ...props
}: DropdownMenuContentProps) {
  return (
    <DropdownMenuPopup
      slot="dropdown-menu-content"
      side={side}
      align={align}
      sideOffset={sideOffset}
      alignOffset={alignOffset}
      {...props}
    />
  )
}

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

type DropdownMenuLabelProps = MenuPrimitive.GroupLabel.Props & {
  inset?: boolean
}

const dropdownMenuLabelClassName =
  "px-2 pt-2 pb-1 text-xs font-medium text-muted-foreground wrap-anywhere data-inset:ps-8"

function DropdownMenuStandaloneLabel({
  className,
  render,
  ...props
}: MenuPrimitive.GroupLabel.Props) {
  const resolved = mergeClassName(dropdownMenuLabelClassName, className)

  return useRender({
    defaultTagName: "div",
    render,
    props: mergeProps<"div">(
      {
        className: typeof resolved === "function" ? resolved({}) : resolved,
      },
      props as React.ComponentProps<"div">
    ),
  })
}

function DropdownMenuLabel({ inset, ...props }: DropdownMenuLabelProps) {
  const inGroup = React.useContext(DropdownMenuGroupContext)
  const Label = inGroup ? MenuPrimitive.GroupLabel : DropdownMenuStandaloneLabel

  return (
    <Label
      data-slot="dropdown-menu-label"
      data-inset={inset ? "" : undefined}
      {...props}
      className={
        inGroup
          ? mergeClassName(dropdownMenuLabelClassName, props.className)
          : props.className
      }
    />
  )
}

const dropdownMenuItemVariants = cva(
  "relative flex min-h-8 w-full min-w-0 cursor-default items-center gap-2 rounded-(--dropdown-menu-item-radius) px-2 py-1.5 text-start text-sm wrap-anywhere outline-none select-none focus-visible:outline-hidden data-highlighted:bg-accent data-highlighted:text-accent-foreground data-inset:ps-8 motion-safe:data-[chosen]:animate-menu-blink forced-colors:data-highlighted:outline-2 forced-colors:data-highlighted:-outline-offset-2 forced-colors:data-highlighted:outline-solid pointer-coarse:min-h-11 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-highlighted:[&_svg:not([class*='text-'])]:text-accent-foreground",
  {
    variants: {
      variant: {
        default: "",
        destructive:
          "text-destructive [--menu-blink:color-mix(in_oklab,var(--color-destructive)_10%,transparent)] data-highlighted:bg-destructive/10 data-highlighted:text-destructive dark:[--menu-blink:color-mix(in_oklab,var(--color-destructive)_20%,transparent)] dark:data-highlighted:bg-destructive/20 forced-colors:data-highlighted:outline-2 forced-colors:data-highlighted:-outline-offset-2 forced-colors:data-highlighted:outline-solid [&_svg:not([class*='text-'])]:text-destructive data-highlighted:[&_svg:not([class*='text-'])]:text-destructive",
      },
    },
    defaultVariants: {
      variant: "default",
    },
  }
)

type ChoosableEvent = React.MouseEvent<HTMLElement> & {
  baseUIHandlerPrevented?: boolean
}

function useChooseOnClick<E extends ChoosableEvent>(
  closeOnClick: boolean,
  onClick: ((event: E) => void) | undefined
) {
  const context = React.useContext(DropdownMenuContext)
  const id = React.useId()

  return {
    chosen: context?.chosen === id ? "" : undefined,
    onClick: (event: E) => {
      onClick?.(event)
      if (
        context &&
        closeOnClick &&
        event.detail !== 0 &&
        !event.defaultPrevented &&
        !event.baseUIHandlerPrevented
      ) {
        context.choose(id)
      }
    },
  }
}

type DropdownMenuItemProps = MenuPrimitive.Item.Props &
  VariantProps<typeof dropdownMenuItemVariants> & {
    inset?: boolean
  }

function DropdownMenuItem({
  className,
  variant = "default",
  inset,
  closeOnClick = true,
  onClick,
  ...props
}: DropdownMenuItemProps) {
  const choice = useChooseOnClick(closeOnClick, onClick)

  return (
    <MenuPrimitive.Item
      data-slot="dropdown-menu-item"
      data-variant={variant}
      data-inset={inset ? "" : undefined}
      data-chosen={choice.chosen}
      closeOnClick={closeOnClick}
      onClick={choice.onClick}
      className={mergeClassName(
        dropdownMenuItemVariants({ variant }),
        className
      )}
      {...props}
    />
  )
}

const dropdownMenuIndicatorClassName =
  "flex items-center justify-center opacity-0 transition-[opacity,scale] duration-150 ease-out-quint data-checked:opacity-100 motion-safe:scale-50 motion-safe:data-checked:scale-100 motion-reduce:transition-none"

type DropdownMenuCheckboxItemProps = MenuPrimitive.CheckboxItem.Props & {
  inset?: boolean
}

function DropdownMenuCheckboxItem({
  className,
  children,
  inset,
  closeOnClick = false,
  onClick,
  ...props
}: DropdownMenuCheckboxItemProps) {
  const choice = useChooseOnClick(closeOnClick, onClick)

  return (
    <MenuPrimitive.CheckboxItem
      data-slot="dropdown-menu-checkbox-item"
      data-inset={inset ? "" : undefined}
      data-chosen={choice.chosen}
      closeOnClick={closeOnClick}
      onClick={choice.onClick}
      className={mergeClassName(
        cn(dropdownMenuItemVariants(), "ps-8"),
        className
      )}
      {...props}
    >
      <span
        aria-hidden
        className="pointer-events-none absolute start-2 flex size-4 items-center justify-center"
      >
        <MenuPrimitive.CheckboxItemIndicator
          keepMounted
          data-slot="dropdown-menu-checkbox-item-indicator"
          className={dropdownMenuIndicatorClassName}
        >
          <IconCheck className="text-current" />
        </MenuPrimitive.CheckboxItemIndicator>
      </span>
      {children}
    </MenuPrimitive.CheckboxItem>
  )
}

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

type DropdownMenuRadioItemProps = MenuPrimitive.RadioItem.Props & {
  inset?: boolean
}

function DropdownMenuRadioItem({
  className,
  children,
  inset,
  closeOnClick = false,
  onClick,
  ...props
}: DropdownMenuRadioItemProps) {
  const choice = useChooseOnClick(closeOnClick, onClick)

  return (
    <MenuPrimitive.RadioItem
      data-slot="dropdown-menu-radio-item"
      data-inset={inset ? "" : undefined}
      data-chosen={choice.chosen}
      closeOnClick={closeOnClick}
      onClick={choice.onClick}
      className={mergeClassName(
        cn(dropdownMenuItemVariants(), "ps-8"),
        className
      )}
      {...props}
    >
      <span
        aria-hidden
        className="pointer-events-none absolute start-2 flex size-4 items-center justify-center"
      >
        <MenuPrimitive.RadioItemIndicator
          keepMounted
          data-slot="dropdown-menu-radio-item-indicator"
          className={dropdownMenuIndicatorClassName}
        >
          <IconCircleFilled className="size-2 text-current" />
        </MenuPrimitive.RadioItemIndicator>
      </span>
      {children}
    </MenuPrimitive.RadioItem>
  )
}

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

function DropdownMenuShortcut({
  className,
  dir = "ltr",
  children,
  ...props
}: React.ComponentProps<"kbd">) {
  return (
    <kbd
      data-slot="dropdown-menu-shortcut"
      className={cn(
        "ms-auto shrink-0 ps-3 font-sans text-xs tracking-widest text-muted-foreground in-data-highlighted:text-accent-foreground/70 in-data-[variant=destructive]:text-destructive/70",
        className
      )}
      {...props}
    >
      <span dir={dir} className="[unicode-bidi:isolate]">
        {children}
      </span>
    </kbd>
  )
}

function DropdownMenuSub(props: MenuPrimitive.SubmenuRoot.Props) {
  return <MenuPrimitive.SubmenuRoot {...props} />
}

type DropdownMenuSubTriggerProps = MenuPrimitive.SubmenuTrigger.Props & {
  inset?: boolean
}

function DropdownMenuSubTrigger({
  className,
  children,
  inset,
  ...props
}: DropdownMenuSubTriggerProps) {
  return (
    <MenuPrimitive.SubmenuTrigger
      data-slot="dropdown-menu-sub-trigger"
      data-inset={inset ? "" : undefined}
      className={mergeClassName(
        cn(
          dropdownMenuItemVariants(),
          "data-popup-open:bg-accent data-popup-open:text-accent-foreground forced-colors:data-popup-open:outline-2 forced-colors:data-popup-open:-outline-offset-2 forced-colors:data-popup-open:outline-solid"
        ),
        className
      )}
      {...props}
    >
      {children}
      <IconChevronRight
        aria-hidden
        data-slot="dropdown-menu-sub-trigger-icon"
        className="ms-auto rtl:-scale-x-100"
      />
    </MenuPrimitive.SubmenuTrigger>
  )
}

function DropdownMenuSubContent({
  sideOffset = 0,
  alignOffset = -4,
  ...props
}: DropdownMenuContentProps) {
  return (
    <DropdownMenuPopup
      slot="dropdown-menu-sub-content"
      sideOffset={sideOffset}
      alignOffset={alignOffset}
      {...props}
    />
  )
}

const createDropdownMenuHandle = MenuPrimitive.createHandle

export {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuPortal,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuLabel,
  DropdownMenuItem,
  DropdownMenuCheckboxItem,
  DropdownMenuRadioGroup,
  DropdownMenuRadioItem,
  DropdownMenuSeparator,
  DropdownMenuShortcut,
  DropdownMenuSub,
  DropdownMenuSubTrigger,
  DropdownMenuSubContent,
  createDropdownMenuHandle,
  dropdownMenuItemVariants,
}
export type {
  DropdownMenuProps,
  DropdownMenuContentProps,
  DropdownMenuLabelProps,
  DropdownMenuItemProps,
  DropdownMenuCheckboxItemProps,
  DropdownMenuRadioItemProps,
  DropdownMenuSubTriggerProps,
}
```

Update the import paths to match your project setup.

## Usage

```tsx
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
```

```tsx
<DropdownMenu>
  <DropdownMenuTrigger render={<Button variant="outline" />}>
    Open
  </DropdownMenuTrigger>
  <DropdownMenuContent>
    <DropdownMenuItem>Profile</DropdownMenuItem>
    <DropdownMenuItem>Billing</DropdownMenuItem>
    <DropdownMenuSeparator />
    <DropdownMenuItem variant="destructive">Log out</DropdownMenuItem>
  </DropdownMenuContent>
</DropdownMenu>
```

## Composition

```text
DropdownMenu
├── DropdownMenuTrigger
└── DropdownMenuContent
    ├── DropdownMenuGroup
    │   ├── DropdownMenuLabel
    │   └── DropdownMenuItem
    │       └── DropdownMenuShortcut
    ├── DropdownMenuCheckboxItem
    ├── DropdownMenuRadioGroup
    │   └── DropdownMenuRadioItem
    ├── DropdownMenuSeparator
    └── DropdownMenuSub
        ├── DropdownMenuSubTrigger
        └── DropdownMenuSubContent
```

## Examples

### Safe area

Moving diagonally from a submenu trigger toward its submenu crosses sibling triggers. The safe area keeps the submenu open while the pointer heads into it. Turn on `showSafeArea` to see it live: the dot turns green inside the area and red where the submenu would switch.

```tsx title="components/examples/dropdown-menu/safe-area.tsx"
"use client"

import * as React from "react"
import { IconDots } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuSub,
  DropdownMenuSubContent,
  DropdownMenuSubTrigger,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

const folders = [
  "Inbox",
  "Design",
  "Engineering",
  "Marketing",
  "Finance",
  "Legal",
  "Archive",
  "Shared with me",
]

const submenus = [
  { label: "Move to", items: folders },
  { label: "Copy to", items: folders },
  { label: "Share", items: ["Copy link", "Email", "Slack"] },
  { label: "Export as", items: ["PDF", "PNG", "SVG"] },
]

export function DropdownMenuSafeArea() {
  const [showSafeArea, setShowSafeArea] = React.useState(true)

  return (
    <div className="flex flex-col items-center gap-6">
      <label className="flex items-center gap-3 text-sm">
        <Checkbox
          checked={showSafeArea}
          onCheckedChange={(checked) => setShowSafeArea(checked === true)}
        />
        Show safe area
      </label>
      <DropdownMenu showSafeArea={showSafeArea}>
        <DropdownMenuTrigger render={<Button variant="outline" />}>
          <IconDots data-icon="inline-start" />
          Q3 report.pdf
        </DropdownMenuTrigger>
        <DropdownMenuContent className="w-44">
          {submenus.map((submenu) => (
            <DropdownMenuSub key={submenu.label}>
              <DropdownMenuSubTrigger>{submenu.label}</DropdownMenuSubTrigger>
              <DropdownMenuSubContent className="w-44">
                {submenu.items.map((item) => (
                  <DropdownMenuItem key={item}>{item}</DropdownMenuItem>
                ))}
              </DropdownMenuSubContent>
            </DropdownMenuSub>
          ))}
          <DropdownMenuSeparator />
          <DropdownMenuItem>Rename</DropdownMenuItem>
          <DropdownMenuItem variant="destructive">Delete</DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
    </div>
  )
}
```

### Checkboxes

`<DropdownMenuCheckboxItem />` toggles a setting and keeps the menu open, so several can be changed in a row. The check draws in and out instead of popping.

```tsx title="components/examples/dropdown-menu/checkboxes.tsx"
"use client"

import * as React from "react"
import { IconChevronDown } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuCheckboxItem,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuLabel,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

export function DropdownMenuCheckboxes() {
  const [statusBar, setStatusBar] = React.useState(true)
  const [activityBar, setActivityBar] = React.useState(false)

  return (
    <DropdownMenu>
      <DropdownMenuTrigger render={<Button variant="outline" />}>
        View
        <IconChevronDown data-icon="inline-end" />
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        <DropdownMenuGroup>
          <DropdownMenuLabel>Appearance</DropdownMenuLabel>
          <DropdownMenuCheckboxItem
            checked={statusBar}
            onCheckedChange={setStatusBar}
          >
            Status bar
          </DropdownMenuCheckboxItem>
          <DropdownMenuCheckboxItem
            checked={activityBar}
            onCheckedChange={setActivityBar}
          >
            Activity bar
          </DropdownMenuCheckboxItem>
          <DropdownMenuCheckboxItem disabled>Panel</DropdownMenuCheckboxItem>
        </DropdownMenuGroup>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
```

### Radio group

Wrap `<DropdownMenuRadioItem />` in a `<DropdownMenuRadioGroup />` to pick one option. A label inside the group names it for screen readers.

```tsx title="components/examples/dropdown-menu/radio-group.tsx"
"use client"

import * as React from "react"
import { IconChevronDown } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuLabel,
  DropdownMenuRadioGroup,
  DropdownMenuRadioItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

export function DropdownMenuRadioGroupDemo() {
  const [position, setPosition] = React.useState("bottom")

  return (
    <DropdownMenu>
      <DropdownMenuTrigger render={<Button variant="outline" />}>
        Panel position
        <IconChevronDown data-icon="inline-end" />
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        <DropdownMenuRadioGroup value={position} onValueChange={setPosition}>
          <DropdownMenuLabel>Panel position</DropdownMenuLabel>
          <DropdownMenuRadioItem value="top">Top</DropdownMenuRadioItem>
          <DropdownMenuRadioItem value="bottom">Bottom</DropdownMenuRadioItem>
          <DropdownMenuRadioItem value="right">Right</DropdownMenuRadioItem>
        </DropdownMenuRadioGroup>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
```

### Select-like

The menu is never narrower than its trigger. Set `closeOnClick` on radio items when picking one should also close the menu.

```tsx title="components/examples/dropdown-menu/select.tsx"
"use client"

import * as React from "react"
import { IconChevronDown, IconPlus } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuRadioGroup,
  DropdownMenuRadioItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

const workspaces = [
  "Acme Inc.",
  "Monsters Inc.",
  "A workspace with a very long name that wraps",
]

export function DropdownMenuSelect() {
  const [workspace, setWorkspace] = React.useState(workspaces[0])

  return (
    <div className="w-64 max-w-full">
      <DropdownMenu>
        <DropdownMenuTrigger
          render={
            <Button variant="outline" className="w-full justify-between" />
          }
        >
          <span className="truncate">{workspace}</span>
          <IconChevronDown data-icon="inline-end" />
        </DropdownMenuTrigger>
        <DropdownMenuContent>
          <DropdownMenuRadioGroup
            value={workspace}
            onValueChange={setWorkspace}
          >
            {workspaces.map((name) => (
              <DropdownMenuRadioItem key={name} value={name} closeOnClick>
                {name}
              </DropdownMenuRadioItem>
            ))}
          </DropdownMenuRadioGroup>
          <DropdownMenuSeparator />
          <DropdownMenuItem>
            <IconPlus />
            New workspace
          </DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
    </div>
  )
}
```

### Row actions

One menu serves every row. Create a handle with `createDropdownMenuHandle`, pass it to each trigger with a `payload`, and read the payload in the menu. Delete hands off to an alert dialog.

```tsx title="components/examples/dropdown-menu/row-actions.tsx"
"use client"

import * as React from "react"
import { IconDots, IconPencil, IconTrash } from "@tabler/icons-react"

import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"
import {
  createDropdownMenuHandle,
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

const files = ["Quarterly report.pdf", "Brand assets", "Roadmap 2027.md"]

const rowMenu = createDropdownMenuHandle<{ name: string }>()

export function DropdownMenuRowActions() {
  const [pending, setPending] = React.useState<string | null>(null)
  const [removed, setRemoved] = React.useState<string[]>([])

  return (
    <div className="flex w-full max-w-sm flex-col gap-3">
      <ul className="flex flex-col gap-0.5 rounded-xl border p-1">
        {files
          .filter((name) => !removed.includes(name))
          .map((name) => (
            <li
              key={name}
              className="flex items-center gap-3 rounded-lg py-1 ps-3 pe-1 text-sm"
            >
              <span className="min-w-0 flex-1 truncate">{name}</span>
              <DropdownMenuTrigger
                handle={rowMenu}
                payload={{ name }}
                aria-label={`Actions for ${name}`}
                render={<Button variant="ghost" size="icon-sm" />}
              >
                <IconDots />
              </DropdownMenuTrigger>
            </li>
          ))}
      </ul>
      <div>
        <Button variant="outline" size="sm" onClick={() => setRemoved([])}>
          Restore files
        </Button>
      </div>
      <DropdownMenu handle={rowMenu}>
        {({ payload }) => (
          <DropdownMenuContent align="end">
            <DropdownMenuItem>
              <IconPencil />
              Rename
            </DropdownMenuItem>
            <DropdownMenuSeparator />
            <DropdownMenuItem
              variant="destructive"
              onClick={() => setPending(payload?.name ?? null)}
            >
              <IconTrash />
              Delete…
            </DropdownMenuItem>
          </DropdownMenuContent>
        )}
      </DropdownMenu>
      <AlertDialog
        open={pending !== null}
        onOpenChange={(open) => {
          if (!open) {
            setPending(null)
          }
        }}
      >
        <AlertDialogContent size="sm">
          <AlertDialogHeader>
            <AlertDialogTitle>Delete “{pending}”?</AlertDialogTitle>
            <AlertDialogDescription>
              It moves to the trash for 30 days.
            </AlertDialogDescription>
          </AlertDialogHeader>
          <AlertDialogFooter>
            <AlertDialogCancel>Cancel</AlertDialogCancel>
            <AlertDialogAction
              variant="destructive"
              onClick={() => {
                if (pending) {
                  setRemoved((value) => [...value, pending])
                }
                setPending(null)
              }}
            >
              Delete
            </AlertDialogAction>
          </AlertDialogFooter>
        </AlertDialogContent>
      </AlertDialog>
    </div>
  )
}
```

### Controlled

Pass `open` and `onOpenChange`. The second argument says why the menu changed, such as a trigger press, an item press or Escape. Items with `closeOnClick={false}` keep it open.

```tsx title="components/examples/dropdown-menu/controlled.tsx"
"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

export function DropdownMenuControlled() {
  const [open, setOpen] = React.useState(false)
  const [reason, setReason] = React.useState("none")

  return (
    <div className="flex flex-col items-center gap-3">
      <DropdownMenu
        open={open}
        onOpenChange={(next, details) => {
          setOpen(next)
          setReason(details.reason)
        }}
      >
        <DropdownMenuTrigger render={<Button variant="outline" />}>
          Controlled
        </DropdownMenuTrigger>
        <DropdownMenuContent>
          <DropdownMenuItem>Closes the menu</DropdownMenuItem>
          <DropdownMenuItem closeOnClick={false}>Stays open</DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
      <p className="text-sm text-muted-foreground">
        Open: {String(open)} · last reason: {reason}
      </p>
    </div>
  )
}
```

### Open on hover

`openOnHover` on the trigger opens the menu after `delay` and closes it after `closeDelay`. Clicking and the keyboard still work, so touch and keyboard users aren’t locked out.

```tsx title="components/examples/dropdown-menu/hover.tsx"
import { IconChevronDown } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

export function DropdownMenuHover() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger
        openOnHover
        delay={120}
        closeDelay={120}
        render={<Button variant="ghost" />}
      >
        Products
        <IconChevronDown data-icon="inline-end" />
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        <DropdownMenuItem>Analytics</DropdownMenuItem>
        <DropdownMenuItem>Engagement</DropdownMenuItem>
        <DropdownMenuItem>Security</DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
```

### Disabled

A disabled trigger never opens. Disabled items stay visible and are skipped by the arrow keys.

```tsx title="components/examples/dropdown-menu/disabled.tsx"
import { Button } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

export function DropdownMenuDisabled() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-2">
      <DropdownMenu>
        <DropdownMenuTrigger disabled render={<Button variant="outline" />}>
          Disabled trigger
        </DropdownMenuTrigger>
        <DropdownMenuContent>
          <DropdownMenuItem>Never shown</DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
      <DropdownMenu>
        <DropdownMenuTrigger render={<Button variant="outline" />}>
          Disabled items
        </DropdownMenuTrigger>
        <DropdownMenuContent>
          <DropdownMenuItem>Rename</DropdownMenuItem>
          <DropdownMenuItem disabled>Move (no permission)</DropdownMenuItem>
          <DropdownMenuItem disabled>Share (no permission)</DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
    </div>
  )
}
```

### Long content

Labels wrap instead of stretching the menu past 20rem, unbroken strings break anywhere, and shortcuts stay on the first line.

```tsx title="components/examples/dropdown-menu/long-content.tsx"
import { Button } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuShortcut,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

export function DropdownMenuLongContent() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger render={<Button variant="outline" />}>
        Long labels
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        <DropdownMenuLabel>
          A label that goes on for much longer than any menu label should
        </DropdownMenuLabel>
        <DropdownMenuItem>
          Move to “Supercalifragilisticexpialidocious archive 2026”
          <DropdownMenuShortcut>⌥⌘M</DropdownMenuShortcut>
        </DropdownMenuItem>
        <DropdownMenuItem>
          https://example.com/a/really/long/url/without/any/spaces/at/all
        </DropdownMenuItem>
        <DropdownMenuItem>👩‍👩‍👧‍👦 日本語のテキスト</DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
```

### Scrolling

When there isn’t room for every item, the menu scrolls inside the viewport and the highlighted item scrolls into view.

```tsx title="components/examples/dropdown-menu/scrolling.tsx"
import { Button } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

export function DropdownMenuScrolling() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger render={<Button variant="outline" />}>
        40 documents
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        {Array.from({ length: 40 }, (_, index) => (
          <DropdownMenuItem key={index}>Document {index + 1}</DropdownMenuItem>
        ))}
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
```

### Inside a sheet

The menu layers above the sheet. Escape closes the menu first, then the sheet.

```tsx title="components/examples/dropdown-menu/in-sheet.tsx"
import { Button } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSub,
  DropdownMenuSubContent,
  DropdownMenuSubTrigger,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import {
  Sheet,
  SheetBody,
  SheetContent,
  SheetDescription,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from "@/components/ui/sheet"

export function DropdownMenuInSheet() {
  return (
    <Sheet>
      <SheetTrigger render={<Button variant="outline" />}>
        Open sheet
      </SheetTrigger>
      <SheetContent>
        <SheetHeader>
          <SheetTitle>Sheet</SheetTitle>
          <SheetDescription>
            The menu layers above the sheet, and Escape closes only the menu.
          </SheetDescription>
        </SheetHeader>
        <SheetBody>
          <DropdownMenu>
            <DropdownMenuTrigger render={<Button variant="outline" />}>
              Open menu
            </DropdownMenuTrigger>
            <DropdownMenuContent>
              <DropdownMenuItem>Copy</DropdownMenuItem>
              <DropdownMenuSub>
                <DropdownMenuSubTrigger>Share</DropdownMenuSubTrigger>
                <DropdownMenuSubContent>
                  <DropdownMenuItem>Email</DropdownMenuItem>
                  <DropdownMenuItem>Link</DropdownMenuItem>
                </DropdownMenuSubContent>
              </DropdownMenuSub>
            </DropdownMenuContent>
          </DropdownMenu>
        </SheetBody>
      </SheetContent>
    </Sheet>
  )
}
```

### Right to left

The menu, submenu chevron and submenu side follow the trigger’s direction. Shortcuts keep their left-to-right order.

```tsx title="components/examples/dropdown-menu/rtl.tsx"
import {
  IconChevronDown,
  IconLogout,
  IconUser,
  IconUserPlus,
} from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuCheckboxItem,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuShortcut,
  DropdownMenuSub,
  DropdownMenuSubContent,
  DropdownMenuSubTrigger,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

export function DropdownMenuRtl() {
  return (
    <div dir="rtl">
      <DropdownMenu>
        <DropdownMenuTrigger render={<Button variant="outline" />}>
          الحساب
          <IconChevronDown data-icon="inline-end" />
        </DropdownMenuTrigger>
        <DropdownMenuContent>
          <DropdownMenuItem>
            <IconUser />
            الملف الشخصي
            <DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
          </DropdownMenuItem>
          <DropdownMenuCheckboxItem defaultChecked>
            إظهار الشريط
          </DropdownMenuCheckboxItem>
          <DropdownMenuSub>
            <DropdownMenuSubTrigger>
              <IconUserPlus />
              دعوة
            </DropdownMenuSubTrigger>
            <DropdownMenuSubContent>
              <DropdownMenuItem>بريد</DropdownMenuItem>
              <DropdownMenuItem>رسالة</DropdownMenuItem>
            </DropdownMenuSubContent>
          </DropdownMenuSub>
          <DropdownMenuSeparator />
          <DropdownMenuItem variant="destructive">
            <IconLogout />
            تسجيل الخروج
          </DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
    </div>
  )
}
```

## Keyboard

| Key | Action |
| --- | --- |
| `Enter` `Space` `↓` | On the trigger, opens the menu and highlights the first item. |
| `↓` | Highlights the next item, wrapping at the end. |
| `↑` | Highlights the previous item, wrapping at the start. |
| `Home` | Highlights the first item. |
| `End` | Highlights the last item. |
| `Enter` `Space` | Runs the highlighted item. Checkbox and radio items toggle and keep the menu open. |
| `→` | Opens the highlighted submenu and moves into it. ← in right-to-left layouts. |
| `←` | Closes the current submenu and returns to its trigger. → in right-to-left layouts. |
| `Esc` | Closes the current menu and returns focus to its trigger. In a submenu, only that submenu closes. |
| `A–Z` | Highlights the next item starting with that letter. |

## Accessibility

- The trigger is announced as a menu button, and focus returns to it when the menu closes.
- Give icon-only triggers an `aria-label`, like the row actions example does.
- `<DropdownMenuShortcut />` is a visual label only. Bind the keys yourself.
- With reduced motion on, the menu fades without scaling and the item blink is skipped.

## API reference

Built on the Base UI menu. Every part accepts the props of the primitive it wraps.

### DropdownMenu

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `open` | `boolean` | – |  |
| `defaultOpen` | `boolean` | `false` |  |
| `showSafeArea` | `boolean` | `false` | Draws the submenu safe area while the menu is open. For debugging and demos. |
| `onOpenChange` | `(open: boolean, details) => void` | – | details.reason says what caused the change. |
| `modal` | `boolean` | `true` | Locks page scroll and blocks outside clicks while open. |
| `loopFocus` | `boolean` | `true` | Wrap the arrow keys at either end. |
| `handle` | `DropdownMenuHandle<Payload>` | – | Connects detached triggers to this menu. |
| `children` | `ReactNode \| ({ payload }) => ReactNode` | – | Use the function form to read the opening trigger’s payload. |
| `disabled` | `boolean` | `false` |  |

### DropdownMenuTrigger

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `openOnHover` | `boolean` | `false` |  |
| `delay` | `number` | `100` | Milliseconds before a hover opens the menu. |
| `closeDelay` | `number` | `0` | Milliseconds before a hover-opened menu closes. |
| `handle` | `DropdownMenuHandle<Payload>` | – |  |
| `payload` | `Payload` | – | Passed to the menu when this trigger opens it. |
| `disabled` | `boolean` | `false` |  |
| `render` | `ReactElement \| (props, state) => ReactElement` | `<button>` |  |

| Attribute | Description |
| --- | --- |
| `data-slot="dropdown-menu-trigger"` | Target the trigger in CSS. |
| `data-popup-open` | Present while its menu is open. |
| `data-pressed` | Present while the trigger is pressed. |
| `data-disabled` | Present when the trigger is disabled. |

### DropdownMenuContent

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `side` | `"top" \| "bottom" \| "left" \| "right" \| "inline-start" \| "inline-end"` | `"bottom"` |  |
| `align` | `"start" \| "center" \| "end"` | `"start"` |  |
| `sideOffset` | `number` | `4` |  |
| `alignOffset` | `number` | `0` |  |
| `collisionPadding` | `number \| Rect` | `8` | Space kept between the menu and the edge of the viewport. |
| `anchor` | `Element \| RefObject \| VirtualElement` | – | Position against another element instead of the trigger. |
| `sticky` | `boolean` | `false` |  |
| `render` | `ReactElement \| (props, state) => ReactElement` | `<div>` |  |

| Attribute | Description |
| --- | --- |
| `data-slot="dropdown-menu-content"` | Target the menu in CSS. |
| `data-open` | Present while the menu is open. |
| `data-starting-style` | Present while the menu animates in. |
| `data-ending-style` | Present while the menu animates out. |
| `data-side` | The side the menu settled on after collision handling. |
| `data-align` | The alignment it settled on. |
| `data-chosen` | Present after an item is clicked, so the menu waits for the item’s confirmation blink before closing. |
| `--anchor-width` | The trigger’s width. The menu is at least this wide. |
| `--available-height` | Room left in the viewport. Long menus scroll within it. |
| `--transform-origin` | Where the scale animation grows from, next to the trigger. |

### DropdownMenuItem

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `"default" \| "destructive"` | `"default"` |  |
| `inset` | `boolean` | `false` | Indents the item to line up with checkbox items. |
| `closeOnClick` | `boolean` | `true` |  |
| `onClick` | `(event) => void` | – |  |
| `label` | `string` | – | Text used for type-ahead when children aren’t plain text. |
| `disabled` | `boolean` | `false` |  |
| `render` | `ReactElement \| (props, state) => ReactElement` | `<div>` | Render a link with render={<a href="…" />}. |

| Attribute | Description |
| --- | --- |
| `data-slot="dropdown-menu-item"` | Target items in CSS. |
| `data-variant` | The current variant. |
| `data-highlighted` | Present while the item is highlighted by pointer or keyboard. |
| `data-disabled` | Present when the item is disabled. |
| `data-chosen` | Present on the item that was just clicked, while it blinks. |
| `data-inset` | Present when inset is set. |

### DropdownMenuCheckboxItem

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `checked` | `boolean` | – |  |
| `defaultChecked` | `boolean` | `false` |  |
| `onCheckedChange` | `(checked: boolean, details) => void` | – |  |
| `closeOnClick` | `boolean` | `false` |  |
| `inset` | `boolean` | `false` |  |
| `disabled` | `boolean` | `false` |  |

| Attribute | Description |
| --- | --- |
| `data-slot="dropdown-menu-checkbox-item"` | Target checkbox items in CSS. |
| `data-checked` | Present when checked. |
| `data-unchecked` | Present when not checked. |
| `data-highlighted` | Present while the item is highlighted by pointer or keyboard. |
| `data-disabled` | Present when the item is disabled. |
| `data-chosen` | Present on the item that was just clicked, while it blinks. |
| `data-inset` | Present when inset is set. |

### DropdownMenuRadioGroup

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `Value` | – |  |
| `defaultValue` | `Value` | – |  |
| `onValueChange` | `(value: Value, details) => void` | – |  |
| `disabled` | `boolean` | `false` |  |

### DropdownMenuRadioItem

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `Value` | – |  |
| `closeOnClick` | `boolean` | `false` |  |
| `inset` | `boolean` | `false` |  |
| `disabled` | `boolean` | `false` |  |

| Attribute | Description |
| --- | --- |
| `data-slot="dropdown-menu-radio-item"` | Target radio items in CSS. |
| `data-checked` | Present on the selected item. |
| `data-unchecked` | Present on the other items. |
| `data-highlighted` | Present while the item is highlighted by pointer or keyboard. |
| `data-disabled` | Present when the item is disabled. |
| `data-chosen` | Present on the item that was just clicked, while it blinks. |
| `data-inset` | Present when inset is set. |

### DropdownMenuGroup

Groups related items. A `<DropdownMenuLabel />` inside it becomes the group’s accessible name.

### DropdownMenuLabel

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `inset` | `boolean` | `false` |  |
| `render` | `ReactElement \| (props, state) => ReactElement` | `<div>` |  |

### DropdownMenuSeparator

A divider between groups, announced as a separator.

### DropdownMenuShortcut

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `dir` | `"ltr" \| "rtl"` | `"ltr"` | Shortcuts keep their key order in right-to-left menus. |

### DropdownMenuSub

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `open` | `boolean` | – |  |
| `defaultOpen` | `boolean` | `false` |  |
| `onOpenChange` | `(open: boolean, details) => void` | – |  |
| `closeParentOnEsc` | `boolean` | `false` | Close the whole menu, not just this submenu, on Escape. |

### DropdownMenuSubTrigger

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `inset` | `boolean` | `false` |  |
| `disabled` | `boolean` | `false` |  |
| `delay` | `number` | `100` | Milliseconds before hovering opens the submenu. |
| `closeDelay` | `number` | `0` |  |

| Attribute | Description |
| --- | --- |
| `data-slot="dropdown-menu-sub-trigger"` | Target submenu triggers in CSS. |
| `data-popup-open` | Present while its submenu is open. |
| `data-highlighted` | Present while highlighted. |

### DropdownMenuSubContent

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `sideOffset` | `number` | `0` |  |
| `alignOffset` | `number` | `-4` |  |
| `render` | `ReactElement \| (props, state) => ReactElement` | `<div>` |  |

| Attribute | Description |
| --- | --- |
| `data-slot="dropdown-menu-sub-content"` | Target submenus in CSS. |
| `data-open` | Present while the menu is open. |
| `data-starting-style` | Present while the menu animates in. |
| `data-ending-style` | Present while the menu animates out. |
| `data-side` | The side the menu settled on after collision handling. |
| `data-align` | The alignment it settled on. |
| `data-chosen` | Present after an item is clicked, so the menu waits for the item’s confirmation blink before closing. |
| `--available-height` | Room left in the viewport. Long menus scroll within it. |
| `--transform-origin` | Where the scale animation grows from, next to the trigger. |

## Notes for AI assistants

- Install a component with the shadcn CLI: `npx shadcn@latest add https://hextaui.com/r/<name>.json`. It adds the source, the HextaUI theme tokens and any HextaUI components it depends on. `https://hextaui.com/r/all.json` installs every component.
- The code is then owned by the project, like shadcn/ui. There is no HextaUI npm package. HextaUI is MIT licensed and free for personal and commercial use.
- Behavior and accessibility come from Base UI (`@base-ui/react`). Compose with the `render` prop, not `asChild`.
- Styling uses Tailwind CSS v4 with theme tokens. Merge classes with `cn` from the `cn` package.
- Icons come from `@tabler/icons-react`.
- Import components from `@/components/ui/<name>`, hooks from `@/hooks/<name>` and utilities from `@/lib/<name>`.

Every HextaUI doc: https://hextaui.com/llms.txt
