# Dropdown Menu (/ui/components/react/dropdown-menu)



<Playground component="dropdown-menu" />

## Usage [#usage]

```tsx
import {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuGroupLabel,
  DropdownMenuItem,
  DropdownMenuSeparator,
} from '@appica/ui-react/dropdown-menu'
import { Button } from '@appica/ui-react/button'
```

```tsx
<DropdownMenu>
  <DropdownMenuTrigger render={<Button variant="outline">Open</Button>} />
  <DropdownMenuContent>
    <DropdownMenuGroup>
      <DropdownMenuGroupLabel>My Account</DropdownMenuGroupLabel>
      <DropdownMenuItem>Profile</DropdownMenuItem>
      <DropdownMenuItem>Billing</DropdownMenuItem>
    </DropdownMenuGroup>
    <DropdownMenuSeparator />
    <DropdownMenuGroup>
      <DropdownMenuItem>Team</DropdownMenuItem>
      <DropdownMenuItem>Subscription</DropdownMenuItem>
    </DropdownMenuGroup>
  </DropdownMenuContent>
</DropdownMenu>
```

Unlike a plain `<button>`, the trigger is wired to the menu through `render` — pass it your own [`Button`](/ui/components/react/button) (or any element) and the menu manages its `aria-expanded`, focus, and open/close. Every part lives in its own import under `@appica/ui-react/dropdown-menu`.

`DropdownMenu` is a compound component built on [Base UI's Menu](https://base-ui.com/react/components/menu). The root holds the open state and the shared `size`; the parts compose the trigger and the portalled popup:

* **`DropdownMenuTrigger`** — the button that toggles the menu. Use `render` to project it onto your own [`Button`](/ui/components/react/button) (or any element).
* **`DropdownMenuContent`** — the portalled, auto-positioned popup. Accepts the shared [floating props](/ui/components/react/popover) (`side`, `align`, `sideOffset`, …).
* **`DropdownMenuItem`** — one action. Fires `onClick` and closes the menu by default.
* **`DropdownMenuGroup`*&#x2A; / &#x2A;*`DropdownMenuGroupLabel`*&#x2A; / &#x2A;*`DropdownMenuSeparator`** — optional structure for sectioned lists.
* **`DropdownMenuCheckboxItem`*&#x2A; / &#x2A;*`DropdownMenuRadioGroup`*&#x2A; + &#x2A;*`DropdownMenuRadioItem`** — stateful options with a check indicator.
* **`DropdownMenuLinkItem`** — an item that renders an `<a>` for navigation.
* **`DropdownMenuSub`*&#x2A; / &#x2A;*`DropdownMenuSubTrigger`*&#x2A; / &#x2A;*`DropdownMenuSubContent`** — a nested submenu.

Put a leading icon on `data-icon="start"` so the item reserves the right padding; push a trailing badge, [`Kbd`](/ui/components/react/kbd) shortcut, or icon to the far edge with `ms-auto`.

Reach for a `DropdownMenu` for **actions** triggered from a button. To pick one value from a list of options, use a [`Select`](/ui/components/react/select); for the application command bar across the top of a window, use a [`Menubar`](/ui/components/react/menubar); to open the same menu on right-click, use a [`Context Menu`](/ui/components/react/context-menu).

## Examples [#examples]

### Account menu [#account-menu]

A complete profile menu: a header with an [`Avatar`](/ui/components/react/avatar), name, and a role [`Badge`](/ui/components/react/badge); grouped sections with count badges; a Settings submenu; an inline dark-mode [`Switch`](/ui/components/react/switch); and a sign-out action. The header is plain markup — only the rows that should be focusable are `DropdownMenuItem`s.

```tsx
'use client'

import * as React from 'react'
import {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuGroupLabel,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuSub,
  DropdownMenuSubTrigger,
  DropdownMenuSubContent,
} from '@appica/ui-react/dropdown-menu'
import { Button } from '@appica/ui-react/button'
import { Avatar, AvatarImage, AvatarFallback } from '@appica/ui-react/avatar'
import { Badge } from '@appica/ui-react/badge'
import { Switch } from '@appica/ui-react/switch'
import {
  UserCircle,
  ShoppingBag,
  Heart,
  Message,
  LayoutDashboard,
  Settings,
  BellRinging,
  MoonStars,
  Logout,
  IdBadge2,
  CreditCard,
  MapPin,
} from '@appica/icons-react'

export default function DropdownMenuAccount() {
  const [darkMode, setDarkMode] = React.useState(false)

  return (
    <DropdownMenu>
      <DropdownMenuTrigger
        render={
          <Button>
            <UserCircle data-icon="start" />
            My account
          </Button>
        }
      />
      <DropdownMenuContent className="min-w-64">
        <div className="flex gap-3 px-2.5 pt-2.5 pb-1.5">
          <Avatar size="sm">
            <AvatarImage src="/avatars/01.jpg" alt="Sarah Jenkins" />
            <AvatarFallback>SJ</AvatarFallback>
          </Avatar>
          <div className="min-w-0 flex-1">
            <p className="text-foreground-intense truncate text-sm font-semibold">Sarah Jenkins</p>
            <p className="text-foreground-muted truncate text-xs">sarah@appica.dev</p>
          </div>
          <Badge variant="secondary" size="sm">
            User
          </Badge>
        </div>

        <DropdownMenuGroup>
          <DropdownMenuGroupLabel>Personal</DropdownMenuGroupLabel>
          <DropdownMenuItem>
            <UserCircle data-icon="start" />
            Profile
          </DropdownMenuItem>
          <DropdownMenuItem>
            <ShoppingBag data-icon="start" />
            My Orders
            <Badge variant="outline" size="sm" className="ms-auto">
              2
            </Badge>
          </DropdownMenuItem>
          <DropdownMenuItem>
            <Heart data-icon="start" />
            Wishlist
          </DropdownMenuItem>
          <DropdownMenuItem>
            <Message data-icon="start" />
            Messages
            <Badge variant="outline" size="sm" className="ms-auto">
              5
            </Badge>
          </DropdownMenuItem>
        </DropdownMenuGroup>

        <DropdownMenuSeparator />

        <DropdownMenuGroup>
          <DropdownMenuGroupLabel>Management</DropdownMenuGroupLabel>
          <DropdownMenuItem>
            <LayoutDashboard data-icon="start" />
            Dashboard
          </DropdownMenuItem>
          <DropdownMenuSub>
            <DropdownMenuSubTrigger>
              <Settings data-icon="start" />
              Settings
            </DropdownMenuSubTrigger>
            <DropdownMenuSubContent>
              <DropdownMenuItem>
                <IdBadge2 data-icon="start" />
                Personal Info
              </DropdownMenuItem>
              <DropdownMenuItem>
                <CreditCard data-icon="start" />
                Payments
              </DropdownMenuItem>
              <DropdownMenuItem>
                <MapPin data-icon="start" />
                Addresses
              </DropdownMenuItem>
            </DropdownMenuSubContent>
          </DropdownMenuSub>
          <DropdownMenuItem>
            <BellRinging data-icon="start" />
            Notifications
          </DropdownMenuItem>
          <DropdownMenuItem closeOnClick={false} onClick={() => setDarkMode((on) => !on)}>
            <MoonStars data-icon="start" />
            Dark Mode
            <Switch checked={darkMode} readOnly tabIndex={-1} aria-hidden className="pointer-events-none ms-auto" />
          </DropdownMenuItem>
        </DropdownMenuGroup>

        <DropdownMenuSeparator />

        <DropdownMenuItem>
          <Logout data-icon="start" />
          Log Out
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
```

### Icons, shortcuts & animated chevron [#icons-shortcuts--animated-chevron]

Give items a leading icon with `data-icon="start"`, and align a trailing [`Kbd`](/ui/components/react/kbd) shortcut with `ms-auto`. The trigger carries an animated chevron: mark the trigger `group/trigger` and add `group-data-popup-open/trigger:rotate-180` to the icon so it flips while the menu is open. Paint a destructive action by overriding the item's text and highlight colors.

```tsx
import {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
} from '@appica/ui-react/dropdown-menu'
import { Button } from '@appica/ui-react/button'
import { Kbd } from '@appica/ui-react/kbd'
import { ChevronDown, User, Copy, Pencil, Trash } from '@appica/icons-react'

export default function DropdownMenuIcons() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger
        className="group/trigger"
        render={
          <Button variant="outline">
            Actions
            <ChevronDown
              data-icon="end"
              className="transition-transform duration-200 ease-out group-data-popup-open/trigger:rotate-180 motion-reduce:transition-none"
            />
          </Button>
        }
      />
      <DropdownMenuContent>
        <DropdownMenuItem>
          <User data-icon="start" />
          Profile
          <Kbd size="sm" className="ms-auto">
            ⇧⌘P
          </Kbd>
        </DropdownMenuItem>
        <DropdownMenuItem>
          <Copy data-icon="start" />
          Duplicate
          <Kbd size="sm" className="ms-auto">
            ⌘D
          </Kbd>
        </DropdownMenuItem>
        <DropdownMenuItem>
          <Pencil data-icon="start" />
          Rename
        </DropdownMenuItem>
        <DropdownMenuSeparator />
        <DropdownMenuItem className="text-error-emphasis! data-highlighted:before:bg-error-subtle!">
          <Trash data-icon="start" />
          Delete
          <Kbd size="sm" className="ms-auto">
            ⌫
          </Kbd>
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
```

### Groups & labels [#groups--labels]

Wrap related items in `DropdownMenuGroup` with a `DropdownMenuGroupLabel`, and split sections with `DropdownMenuSeparator`.

```tsx
import {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuGroupLabel,
  DropdownMenuItem,
  DropdownMenuSeparator,
} from '@appica/ui-react/dropdown-menu'
import { Button } from '@appica/ui-react/button'

export default function DropdownMenuGroups() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger render={<Button variant="outline">Open menu</Button>} />
      <DropdownMenuContent>
        <DropdownMenuGroup>
          <DropdownMenuGroupLabel>Account</DropdownMenuGroupLabel>
          <DropdownMenuItem>Profile</DropdownMenuItem>
          <DropdownMenuItem>Billing</DropdownMenuItem>
          <DropdownMenuItem>Settings</DropdownMenuItem>
        </DropdownMenuGroup>
        <DropdownMenuSeparator />
        <DropdownMenuGroup>
          <DropdownMenuGroupLabel>Team</DropdownMenuGroupLabel>
          <DropdownMenuItem>Invite members</DropdownMenuItem>
          <DropdownMenuItem>New workspace</DropdownMenuItem>
        </DropdownMenuGroup>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
```

### Scrollable [#scrollable]

For a long list, cap the height and wrap the items in a [`ScrollArea`](/ui/components/react/scroll-area) for the custom scrollbar. Pull the `ScrollArea` out to the popup edges with `-mx-2` (cancelling the popup's inner padding) and restore the item padding on the viewport (`viewportProps={{ className: 'px-2' }}`), so the scrollbar sits a hair inside the popup edge.

```tsx
import {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuGroupLabel,
  DropdownMenuItem,
} from '@appica/ui-react/dropdown-menu'
import { Button } from '@appica/ui-react/button'
import { ScrollArea } from '@appica/ui-react/scroll-area'
import { Avatar, AvatarFallback } from '@appica/ui-react/avatar'
import { ChevronDown } from '@appica/icons-react'

const WORKSPACES = [
  'Acme Corp',
  'Globex',
  'Initech',
  'Umbrella',
  'Stark Industries',
  'Wayne Enterprises',
  'Soylent',
  'Hooli',
  'Pied Piper',
  'Wonka Industries',
  'Cyberdyne',
  'Aperture Science',
]

export default function DropdownMenuScrollable() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger
        className="group/trigger"
        render={
          <Button variant="outline">
            Switch workspace
            <ChevronDown
              data-icon="end"
              className="transition-transform duration-200 ease-out group-data-popup-open/trigger:rotate-180 motion-reduce:transition-none"
            />
          </Button>
        }
      />
      <DropdownMenuContent className="min-w-60">
        <DropdownMenuGroup>
          <DropdownMenuGroupLabel>Workspaces</DropdownMenuGroupLabel>
          {/* Pull the ScrollArea out to the popup edges (cancelling the popup's
              px-2) and restore the item padding on the viewport, so the custom
              scrollbar sits 2px from the popup edge. */}
          <ScrollArea className="-mx-2 max-h-64" scrollbarVisibility="always" viewportProps={{ className: 'px-2' }}>
            <div className="flex flex-col gap-0.5">
              {WORKSPACES.map((name) => (
                <DropdownMenuItem key={name}>
                  <Avatar size="sm" data-icon="start">
                    <AvatarFallback>{name.slice(0, 1)}</AvatarFallback>
                  </Avatar>
                  {name}
                </DropdownMenuItem>
              ))}
            </div>
          </ScrollArea>
        </DropdownMenuGroup>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
```

### Checkbox items (controlled) [#checkbox-items-controlled]

`DropdownMenuCheckboxItem` toggles a boolean and shows a check when on. Drive it with `checked` + `onCheckedChange` (or leave it uncontrolled with `defaultChecked`); checkbox items keep the menu open so several can be toggled in one pass. A classic real-world use is **column visibility** — each item controls whether a [`Table`](/ui/components/react/table) column is shown.

```tsx
'use client'

import * as React from 'react'
import {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuGroupLabel,
  DropdownMenuCheckboxItem,
} from '@appica/ui-react/dropdown-menu'
import { Button } from '@appica/ui-react/button'
import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@appica/ui-react/table'
import { ScrollArea } from '@appica/ui-react/scroll-area'
import { LayoutColumns, ChevronDown } from '@appica/icons-react'

type ColumnKey = 'email' | 'role' | 'status'

const COLUMNS: { key: ColumnKey; label: string }[] = [
  { key: 'email', label: 'Email' },
  { key: 'role', label: 'Role' },
  { key: 'status', label: 'Status' },
]

const ROWS: { name: string; email: string; role: string; status: string }[] = [
  { name: 'Sarah Jenkins', email: 'sarah@appica.dev', role: 'Admin', status: 'Active' },
  { name: 'Liam Hudson', email: 'liam@appica.dev', role: 'Editor', status: 'Invited' },
  { name: 'Mateo Rossi', email: 'mateo@appica.dev', role: 'Viewer', status: 'Active' },
]

export default function DropdownMenuCheckboxes() {
  const [visible, setVisible] = React.useState<Record<ColumnKey, boolean>>({
    email: true,
    role: true,
    status: false,
  })

  const shown = COLUMNS.filter((column) => visible[column.key])

  return (
    <div className="flex w-full max-w-125 flex-col gap-3">
      <div className="flex justify-end">
        <DropdownMenu>
          <DropdownMenuTrigger
            className="group/trigger"
            render={
              <Button variant="outline">
                <LayoutColumns data-icon="start" />
                Columns
                <ChevronDown
                  data-icon="end"
                  className="transition-transform duration-200 ease-out group-data-popup-open/trigger:rotate-180 motion-reduce:transition-none"
                />
              </Button>
            }
          />
          <DropdownMenuContent align="end">
            <DropdownMenuGroup>
              <DropdownMenuGroupLabel>Toggle columns</DropdownMenuGroupLabel>
              {COLUMNS.map((column) => (
                <DropdownMenuCheckboxItem
                  key={column.key}
                  checked={visible[column.key]}
                  onCheckedChange={(checked) => setVisible((prev) => ({ ...prev, [column.key]: checked }))}
                >
                  {column.label}
                </DropdownMenuCheckboxItem>
              ))}
            </DropdownMenuGroup>
          </DropdownMenuContent>
        </DropdownMenu>
      </div>

      <ScrollArea
        orientation="horizontal"
        scrollbarVisibility="auto"
        className="[&_td]:whitespace-nowrap [&_th]:whitespace-nowrap"
      >
        <Table hoverableRows>
          <TableHeader>
            <TableRow>
              <TableHead>Name</TableHead>
              {shown.map((column) => (
                <TableHead key={column.key}>{column.label}</TableHead>
              ))}
            </TableRow>
          </TableHeader>
          <TableBody>
            {ROWS.map((row) => (
              <TableRow key={row.email}>
                <TableCell className="text-foreground-intense font-medium">{row.name}</TableCell>
                {shown.map((column) => (
                  <TableCell key={column.key}>{row[column.key]}</TableCell>
                ))}
              </TableRow>
            ))}
          </TableBody>
        </Table>
      </ScrollArea>
    </div>
  )
}
```

### Radio items [#radio-items]

Wrap mutually exclusive options in a `DropdownMenuRadioGroup` (controlled with `value` + `onValueChange`, or uncontrolled with `defaultValue`); each `DropdownMenuRadioItem` carries the `value` it selects. Like checkbox items, radio items keep the menu open after a choice.

```tsx
import {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuGroupLabel,
  DropdownMenuRadioGroup,
  DropdownMenuRadioItem,
} from '@appica/ui-react/dropdown-menu'
import { Button } from '@appica/ui-react/button'

export default function DropdownMenuRadio() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger render={<Button variant="outline">Sort by</Button>} />
      <DropdownMenuContent>
        <DropdownMenuRadioGroup defaultValue="recent">
          <DropdownMenuGroupLabel>Sort order</DropdownMenuGroupLabel>
          <DropdownMenuRadioItem value="recent">Most recent</DropdownMenuRadioItem>
          <DropdownMenuRadioItem value="popular">Most popular</DropdownMenuRadioItem>
          <DropdownMenuRadioItem value="oldest">Oldest first</DropdownMenuRadioItem>
        </DropdownMenuRadioGroup>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
```

### Submenus [#submenus]

Nest a `DropdownMenuSub` with its own `DropdownMenuSubTrigger` and `DropdownMenuSubContent`. The trigger shows a chevron and opens on hover or with the arrow keys; the submenu opens to the side and flips automatically near a screen edge.

```tsx
import {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuSub,
  DropdownMenuSubTrigger,
  DropdownMenuSubContent,
} from '@appica/ui-react/dropdown-menu'
import { Button } from '@appica/ui-react/button'
import { UserPlus, Mail, MessageCircle, Link } from '@appica/icons-react'

export default function DropdownMenuSubmenu() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger render={<Button variant="outline">Share</Button>} />
      <DropdownMenuContent>
        <DropdownMenuItem>Copy link</DropdownMenuItem>
        <DropdownMenuItem>Embed</DropdownMenuItem>
        <DropdownMenuSeparator />
        <DropdownMenuSub>
          <DropdownMenuSubTrigger>
            <UserPlus data-icon="start" />
            Invite people
          </DropdownMenuSubTrigger>
          <DropdownMenuSubContent>
            <DropdownMenuItem>
              <Mail data-icon="start" />
              Email
            </DropdownMenuItem>
            <DropdownMenuItem>
              <MessageCircle data-icon="start" />
              Message
            </DropdownMenuItem>
            <DropdownMenuItem>
              <Link data-icon="start" />
              Invite link
            </DropdownMenuItem>
          </DropdownMenuSubContent>
        </DropdownMenuSub>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
```

### Open on hover [#open-on-hover]

Set `openOnHover` on the `DropdownMenuTrigger` to open the menu when the pointer rests on it (it still opens on click and keyboard too). Tune the timing with `delay` / `closeDelay`. Use this sparingly — hover menus aren't reachable on touch and can feel twitchy.

```tsx
import {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
} from '@appica/ui-react/dropdown-menu'
import { Button } from '@appica/ui-react/button'
import { ChevronDown, User, Settings, Logout } from '@appica/icons-react'

export default function DropdownMenuHover() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger
        className="group/trigger"
        openOnHover
        delay={100}
        render={
          <Button variant="outline">
            Hover me
            <ChevronDown
              data-icon="end"
              className="transition-transform duration-200 ease-out group-data-popup-open/trigger:rotate-180 motion-reduce:transition-none"
            />
          </Button>
        }
      />
      <DropdownMenuContent>
        <DropdownMenuItem>
          <User data-icon="start" />
          Profile
        </DropdownMenuItem>
        <DropdownMenuItem>
          <Settings data-icon="start" />
          Settings
        </DropdownMenuItem>
        <DropdownMenuSeparator />
        <DropdownMenuItem>
          <Logout data-icon="start" />
          Log out
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
```

### Link items [#link-items]

`DropdownMenuLinkItem` renders a real `<a>`, so it navigates (and supports `target`, modifier-clicks, and right-click "open in new tab") while staying keyboard-reachable inside the menu. For **internal** routes, project it onto your router's link via `render` — here a Next.js [`Link`](https://nextjs.org/docs/app/api-reference/components/link) (`render={<Link href="…" />}`) for client-side navigation; for **external** links, pass a plain `href` with `target="_blank"`.

```tsx
import Link from 'next/link'
import {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuLinkItem,
  DropdownMenuSeparator,
} from '@appica/ui-react/dropdown-menu'
import { Button } from '@appica/ui-react/button'
import { LayoutDashboard, Settings, ArrowUpRight, Book, GitBranch } from '@appica/icons-react'

export default function DropdownMenuLinks() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger render={<Button variant="outline">Go to</Button>} />
      <DropdownMenuContent>
        {/* Internal navigation — render through a Next.js <Link> for client-side routing. */}
        <DropdownMenuLinkItem render={<Link href="/ui/components/react/dropdown-menu" />}>
          <LayoutDashboard data-icon="start" />
          Dashboard
        </DropdownMenuLinkItem>
        <DropdownMenuLinkItem render={<Link href="/ui/components/react/dropdown-menu" />}>
          <Settings data-icon="start" />
          Settings
        </DropdownMenuLinkItem>
        <DropdownMenuSeparator />
        {/* External links — a plain href with target opens in a new tab. */}
        <DropdownMenuLinkItem href="https://base-ui.com" target="_blank" rel="noreferrer">
          <Book data-icon="start" />
          Documentation
          <ArrowUpRight data-icon="end" className="ms-auto" />
        </DropdownMenuLinkItem>
        <DropdownMenuLinkItem href="https://github.com" target="_blank" rel="noreferrer">
          <GitBranch data-icon="start" />
          Source code
          <ArrowUpRight data-icon="end" className="ms-auto" />
        </DropdownMenuLinkItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
```

### Sizes [#sizes]

`size` on the root scales the popup radius, item padding, and icon size together — `sm`, `md` (default), or `lg`.

```tsx
import {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
} from '@appica/ui-react/dropdown-menu'
import { Button } from '@appica/ui-react/button'

const SIZES = ['sm', 'md', 'lg'] as const

export default function DropdownMenuSizes() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      {SIZES.map((size) => (
        <DropdownMenu key={size} size={size}>
          <DropdownMenuTrigger
            render={
              <Button variant="outline" size={size}>
                {size}
              </Button>
            }
          />
          <DropdownMenuContent>
            <DropdownMenuItem>Profile</DropdownMenuItem>
            <DropdownMenuItem>Settings</DropdownMenuItem>
            <DropdownMenuSeparator />
            <DropdownMenuItem>Log out</DropdownMenuItem>
          </DropdownMenuContent>
        </DropdownMenu>
      ))}
    </div>
  )
}
```

### Side & alignment [#side--alignment]

Position the popup with `side` (`top` / `right` / `bottom` / `left`) and `align` (`start` / `center` / `end`) on `DropdownMenuContent`. It still flips and shifts automatically to stay in view.

By default a menu only flips to the opposite **same-axis** side (e.g. `right` → `left`); it won't re-place onto the other axis, since menus cap their height and scroll instead. For a side-opening (`left` / `right`) menu, pass `collisionAvoidance={{ fallbackAxisSide: 'end' }}` so it can fall back to the top/bottom axis when neither side fits — that's why the `left` and `right` triggers below set it.

```tsx
import {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuItem,
} from '@appica/ui-react/dropdown-menu'
import { Button } from '@appica/ui-react/button'

const PLACEMENTS = [
  { side: 'top', align: 'start' },
  { side: 'right', align: 'center' },
  { side: 'bottom', align: 'end' },
  { side: 'left', align: 'center' },
] as const

export default function DropdownMenuPlacement() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-3">
      {PLACEMENTS.map(({ side, align }) => (
        <DropdownMenu key={`${side}-${align}`}>
          <DropdownMenuTrigger
            render={
              <Button variant="outline">
                {side} / {align}
              </Button>
            }
          />
          <DropdownMenuContent
            side={side}
            align={align}
            collisionAvoidance={side === 'left' || side === 'right' ? { fallbackAxisSide: 'end' } : undefined}
          >
            <DropdownMenuItem>Profile</DropdownMenuItem>
            <DropdownMenuItem>Settings</DropdownMenuItem>
            <DropdownMenuItem>Log out</DropdownMenuItem>
          </DropdownMenuContent>
        </DropdownMenu>
      ))}
    </div>
  )
}
```

## RTL [#rtl]

Every Appica UI component supports right-to-left layouts out of the box. Set the `dir` attribute on a container (commonly your `<html>` element) so CSS logical properties resolve correctly, and wrap your tree in `DirectionProvider` so direction-aware behavior — roving focus, popup placement, and submenu direction — follows the same direction.

```tsx
import { DirectionProvider } from '@appica/ui-react/providers/direction-provider'

export default function RootLayout({ children }) {
  return (
    <html lang="ar" dir="rtl">
      <body>
        <DirectionProvider dir="rtl">{children}</DirectionProvider>
      </body>
    </html>
  )
}
```

The popup anchors from the trigger's leading edge, submenus open toward the inline-start side, and the submenu chevron mirrors. For setup details and caveats, see the [RTL guide](/ui/docs/react/rtl).

<Callout type="note" title="Scoped RTL needs the direction on the positioner">
  The popup portals to `<body>`, so its `start`/`end` alignment mirrors from the **document** direction (read from CSS,
  not from `DirectionProvider`) — automatic when `<html dir="rtl">`. If you instead scope RTL to a subtree of an LTR page,
  set it on the positioner so alignment still mirrors: `<DropdownMenuContent positionerProps={{ dir: 'rtl' }}>`. See
  [Floating popups in a scoped RTL region](/ui/docs/react/rtl#floating-popups-in-a-scoped-rtl-region).
</Callout>

<RtlPreview component="dropdown-menu" />

## API reference [#api-reference]

`DropdownMenu` wraps [Base UI's Menu](https://base-ui.com/react/components/menu). Each part forwards every remaining prop to the matching Base UI primitive, so anything it accepts works here — the tables below list the Appica additions and the most common props. See the Base UI docs for the complete API.

### DropdownMenu [#dropdownmenu]

The root. Holds the open state and the shared `size`; renders no DOM of its own.

| Prop                   | <ColMinWidth width="200">Type</ColMinWidth> | Default | <ColMinWidth width="220">Description</ColMinWidth>          |
| ---------------------- | ------------------------------------------- | ------- | ----------------------------------------------------------- |
| `size`                 | <Code>'sm' \| 'md' \| 'lg'</Code>           | `'md'`  | Scales the popup radius, item padding, and icon size.       |
| `open`                 | `boolean`                                   | —       | Controlled open state. Pair with `onOpenChange`.            |
| `defaultOpen`          | `boolean`                                   | `false` | Uncontrolled initial open state.                            |
| `onOpenChange`         | <Code>(open, eventDetails) => void</Code>   | —       | Fires when the menu opens or closes.                        |
| `modal`                | `boolean`                                   | `true`  | Trap focus and block outside scroll/interaction while open. |
| `loopFocus`            | `boolean`                                   | `true`  | Wrap keyboard focus from the last item back to the first.   |
| `disabled`             | `boolean`                                   | `false` | Disable the trigger and prevent opening.                    |
| `onOpenChangeComplete` | `() => void`                                | —       | Fires after the open/close transition finishes.             |

### DropdownMenuTrigger [#dropdownmenutrigger]

The button that toggles the menu. Forwards Base UI's `Menu.Trigger`; use `render` to project it onto your own element.

| Prop          | <ColMinWidth width="220">Type</ColMinWidth>                 | Default | Description                                                  |
| ------------- | ----------------------------------------------------------- | ------- | ------------------------------------------------------------ |
| `render`      | <Code>ReactElement \| (props, state) => ReactElement</Code> | —       | Render the trigger as your own element (e.g. a `Button`).    |
| `openOnHover` | `boolean`                                                   | `false` | Open the menu when the trigger is hovered, not just clicked. |
| `delay`       | `number`                                                    | `100`   | Hover-open delay in ms (with `openOnHover`).                 |
| `closeDelay`  | `number`                                                    | `0`     | Hover-close delay in ms (with `openOnHover`).                |
| `className`   | `string`                                                    | —       | Extra classes on the trigger, merged via `tailwind-merge`.   |
| `disabled`    | `boolean`                                                   | `false` | Disable just this trigger.                                   |

### DropdownMenuContent [#dropdownmenucontent]

The portalled, auto-positioned popup wrapping the list. Defaults to `align="start"` and `sideOffset={6}`, and accepts the shared floating props.

| Prop               | <ColMinWidth width="160">Type</ColMinWidth>         | Default    | Description                                                     |
| ------------------ | --------------------------------------------------- | ---------- | --------------------------------------------------------------- |
| `side`             | <Code>'top' \| 'right' \| 'bottom' \| 'left'</Code> | `'bottom'` | Preferred side to open on.                                      |
| `align`            | <Code>'start' \| 'center' \| 'end'</Code>           | `'start'`  | Alignment along that side.                                      |
| `sideOffset`       | `number`                                            | `6`        | Gap between the trigger and the popup.                          |
| `alignOffset`      | `number`                                            | —          | Shift along the alignment axis.                                 |
| `collisionPadding` | `number`                                            | `5`        | Minimum gap kept from the viewport edge when flipping/shifting. |
| `container`        | <Code>HTMLElement \| ref \| null</Code>             | —          | Portal target — render the popup into a different node.         |
| `keepMounted`      | `boolean`                                           | `false`    | Keep the popup mounted in the DOM while closed.                 |
| `positionerProps`  | `object`                                            | —          | Escape hatch for the positioner element (`style`, `render`, …). |
| `portalProps`      | `object`                                            | —          | Escape hatch for the portal element (`style`, `render`, …).     |
| `className`        | `string`                                            | —          | Extra classes on the popup.                                     |

### DropdownMenuItem [#dropdownmenuitem]

One action in the menu.

| Prop           | <ColMinWidth width="180">Type</ColMinWidth> | Default | Description                                                    |
| -------------- | ------------------------------------------- | ------- | -------------------------------------------------------------- |
| `closeOnClick` | `boolean`                                   | `true`  | Close the menu after the item is selected.                     |
| `disabled`     | `boolean`                                   | `false` | Make the item unselectable; skipped by keyboard and typeahead. |
| `onClick`      | <Code>(event) => void</Code>                | —       | Fires when the item is selected (click or Enter/Space).        |
| `className`    | `string`                                    | —       | Extra classes, merged via `tailwind-merge`.                    |

### DropdownMenuCheckboxItem [#dropdownmenucheckboxitem]

A toggleable item with a check indicator. Keeps the menu open on click.

| Prop              | <ColMinWidth width="180">Type</ColMinWidth> | Default | Description                                            |
| ----------------- | ------------------------------------------- | ------- | ------------------------------------------------------ |
| `checked`         | `boolean`                                   | —       | Controlled checked state. Pair with `onCheckedChange`. |
| `defaultChecked`  | `boolean`                                   | `false` | Uncontrolled initial checked state.                    |
| `onCheckedChange` | <Code>(checked) => void</Code>              | —       | Fires when the checked state changes.                  |
| `closeOnClick`    | `boolean`                                   | `false` | Close the menu when toggled.                           |
| `disabled`        | `boolean`                                   | `false` | Make the item unselectable.                            |

### DropdownMenuRadioGroup [#dropdownmenuradiogroup]

Wraps a set of mutually exclusive `DropdownMenuRadioItem`s.

| Prop            | <ColMinWidth width="180">Type</ColMinWidth> | Default | Description                                           |
| --------------- | ------------------------------------------- | ------- | ----------------------------------------------------- |
| `value`         | `string`                                    | —       | Controlled selected value. Pair with `onValueChange`. |
| `defaultValue`  | `string`                                    | —       | Uncontrolled initial value.                           |
| `onValueChange` | <Code>(value) => void</Code>                | —       | Fires when the selection changes.                     |

### DropdownMenuRadioItem [#dropdownmenuradioitem]

One option in a `DropdownMenuRadioGroup`. Keeps the menu open on click.

| Prop           | Type      | Default | Description                   |
| -------------- | --------- | ------- | ----------------------------- |
| `value`        | `string`  | —       | The value this item selects.  |
| `closeOnClick` | `boolean` | `false` | Close the menu when chosen.   |
| `disabled`     | `boolean` | `false` | Make the option unselectable. |

### DropdownMenuLinkItem [#dropdownmenulinkitem]

An item that renders an `<a>` for navigation. Forwards every native anchor prop (`href`, `target`, `rel`, …).

| Prop        | Type     | Default | Description                                 |
| ----------- | -------- | ------- | ------------------------------------------- |
| `href`      | `string` | —       | Navigation target.                          |
| `className` | `string` | —       | Extra classes, merged via `tailwind-merge`. |

### DropdownMenuSub [#dropdownmenusub]

Wraps a nested submenu. Forwards Base UI's `Menu.SubmenuRoot`.

| Prop          | <ColMinWidth width="180">Type</ColMinWidth> | Default | Description                           |
| ------------- | ------------------------------------------- | ------- | ------------------------------------- |
| `open`        | `boolean`                                   | —       | Controlled open state of the submenu. |
| `defaultOpen` | `boolean`                                   | `false` | Uncontrolled initial open state.      |

### DropdownMenuSubTrigger [#dropdownmenusubtrigger]

The item that opens a submenu; renders a trailing chevron. Submenus open on hover by default.

| Prop          | Type      | Default | Description                                 |
| ------------- | --------- | ------- | ------------------------------------------- |
| `openOnHover` | `boolean` | `true`  | Open the submenu on hover (and on click).   |
| `delay`       | `number`  | `100`   | Hover-open delay in ms.                     |
| `closeDelay`  | `number`  | `0`     | Hover-close delay in ms.                    |
| `className`   | `string`  | —       | Extra classes, merged via `tailwind-merge`. |

### DropdownMenuSubContent [#dropdownmenusubcontent]

The submenu popup. Defaults to opening on the `inline-end` side; otherwise identical to `DropdownMenuContent`.

| Prop        | Type     | Default | Description                         |
| ----------- | -------- | ------- | ----------------------------------- |
| `className` | `string` | —       | Extra classes on the submenu popup. |

### DropdownMenuGroup [#dropdownmenugroup]

Wraps one labelled section of items so a `DropdownMenuGroupLabel` can name it.

| Prop        | Type        | Default | Description                                          |
| ----------- | ----------- | ------- | ---------------------------------------------------- |
| `children`  | `ReactNode` | —       | A `DropdownMenuGroupLabel` plus the section's items. |
| `className` | `string`    | —       | Extra classes, merged via `tailwind-merge`.          |

### DropdownMenuGroupLabel [#dropdownmenugrouplabel]

The visible heading for a `DropdownMenuGroup`. Size-aware; must be rendered inside a `DropdownMenuGroup` (or `DropdownMenuRadioGroup`).

| Prop        | Type        | Default | Description                                 |
| ----------- | ----------- | ------- | ------------------------------------------- |
| `children`  | `ReactNode` | —       | The label text.                             |
| `className` | `string`    | —       | Extra classes, merged via `tailwind-merge`. |

### DropdownMenuSeparator [#dropdownmenuseparator]

A thin divider between items or groups.

| Prop        | Type     | Default | Description                                 |
| ----------- | -------- | ------- | ------------------------------------------- |
| `className` | `string` | —       | Extra classes, merged via `tailwind-merge`. |

## Accessibility [#accessibility]

* The trigger is a `button` with `aria-haspopup="menu"` and `aria-expanded`; the popup has `role="menu"` and items are `menuitem`, `menuitemcheckbox`, or `menuitemradio`.
* **Enter / Space / ↓*&#x2A; open the menu, &#x2A;*↑/↓*&#x2A; move the highlight, &#x2A;*→*&#x2A; opens a submenu, &#x2A;*←** closes it, **Enter** selects, **Escape** closes, and typing jumps to the matching item (typeahead).
* Focus is trapped inside the open popup when `modal` (the default) and returns to the trigger on close.
* `disabled` items are skipped by keyboard navigation and typeahead.
* `DropdownMenuLinkItem` keeps native anchor semantics, so links are announced and openable in a new tab.
* Popup enter/exit transitions are skipped when the user prefers reduced motion.
