---
title: Dropdown Menu
description: Compact action menu opened from a trigger.
group: Components
order: 35
---

Open a compact list of actions from a trigger. Click outside or press Escape to close it.

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

export function Example() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger className={buttonVariants({ variant: "outline" })}>
        Open
      </DropdownMenuTrigger>
      <DropdownMenuContent className="w-56">
        <DropdownMenuGroup>
          <DropdownMenuLabel>My Account</DropdownMenuLabel>
          <DropdownMenuItem>Profile</DropdownMenuItem>
          <DropdownMenuItem>Billing</DropdownMenuItem>
        </DropdownMenuGroup>
        <DropdownMenuSeparator />
        <DropdownMenuGroup>
          <DropdownMenuItem>Team</DropdownMenuItem>
          <DropdownMenuItem>Subscription</DropdownMenuItem>
        </DropdownMenuGroup>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
```

## Installation

```bash
npx @arctis-sh/@arctis-sh/ui@latest add dropdown-menu
```

## Usage

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

```tsx
<DropdownMenu>
  <DropdownMenuTrigger className={buttonVariants({ variant: "outline" })}>
    Open
  </DropdownMenuTrigger>
  <DropdownMenuContent>
    <DropdownMenuItem>Profile</DropdownMenuItem>
    <DropdownMenuItem>Billing</DropdownMenuItem>
    <DropdownMenuItem>Team</DropdownMenuItem>
    <DropdownMenuItem>Subscription</DropdownMenuItem>
  </DropdownMenuContent>
</DropdownMenu>
```

## Composition

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

## Basic

Grouped actions with a separator. Choosing an item closes the menu.

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

export function Example() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger className={buttonVariants({ variant: "outline" })}>
        Open
      </DropdownMenuTrigger>
      <DropdownMenuContent className="w-56">
        <DropdownMenuGroup>
          <DropdownMenuLabel>My Account</DropdownMenuLabel>
          <DropdownMenuItem>Profile</DropdownMenuItem>
          <DropdownMenuItem>Billing</DropdownMenuItem>
        </DropdownMenuGroup>
        <DropdownMenuSeparator />
        <DropdownMenuGroup>
          <DropdownMenuItem>Team</DropdownMenuItem>
          <DropdownMenuItem>Subscription</DropdownMenuItem>
        </DropdownMenuGroup>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
```

## Submenu

Use `DropdownMenuSub` for nested actions. Hover `DropdownMenuSubTrigger` to open it.

```tsx
import { buttonVariants } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuSub,
  DropdownMenuSubContent,
  DropdownMenuSubTrigger,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

export function Example() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger className={buttonVariants({ variant: "outline" })}>
        Open
      </DropdownMenuTrigger>
      <DropdownMenuContent className="w-56">
        <DropdownMenuItem>New Tab</DropdownMenuItem>
        <DropdownMenuItem>New Window</DropdownMenuItem>
        <DropdownMenuItem disabled>New Private Window</DropdownMenuItem>
        <DropdownMenuSeparator />
        <DropdownMenuSub>
          <DropdownMenuSubTrigger>Invite users</DropdownMenuSubTrigger>
          <DropdownMenuSubContent>
            <DropdownMenuItem>Email</DropdownMenuItem>
            <DropdownMenuItem>Message</DropdownMenuItem>
            <DropdownMenuSeparator />
            <DropdownMenuItem>More…</DropdownMenuItem>
          </DropdownMenuSubContent>
        </DropdownMenuSub>
        <DropdownMenuItem>New Team</DropdownMenuItem>
        <DropdownMenuItem>GitHub</DropdownMenuItem>
        <DropdownMenuItem>Support</DropdownMenuItem>
        <DropdownMenuItem disabled>API</DropdownMenuItem>
        <DropdownMenuSeparator />
        <DropdownMenuItem>Log out</DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
```

## Shortcuts

Place `DropdownMenuShortcut` inside an item to show a keyboard hint.

```tsx
import { buttonVariants } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuShortcut,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

export function Example() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger className={buttonVariants({ variant: "outline" })}>
        Open
      </DropdownMenuTrigger>
      <DropdownMenuContent className="w-56">
        <DropdownMenuItem>
          Profile
          <DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
        </DropdownMenuItem>
        <DropdownMenuItem>
          Billing
          <DropdownMenuShortcut>⌘B</DropdownMenuShortcut>
        </DropdownMenuItem>
        <DropdownMenuItem>
          Settings
          <DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
        </DropdownMenuItem>
        <DropdownMenuItem>
          Keyboard shortcuts
          <DropdownMenuShortcut>⌘K</DropdownMenuShortcut>
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
```

## Icons

Place an SVG icon before the label. Icons use a muted color by default.

```tsx
import type { SVGProps } from "react"
import { buttonVariants } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

function UserIcon(props: SVGProps<SVGSVGElement>) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>
      <path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" />
      <circle cx="12" cy="7" r="4" />
    </svg>
  )
}

function CreditCardIcon(props: SVGProps<SVGSVGElement>) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>
      <rect width="20" height="14" x="2" y="5" rx="2" />
      <path d="M2 10h20" />
    </svg>
  )
}

function SettingsIcon(props: SVGProps<SVGSVGElement>) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>
      <path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" />
      <circle cx="12" cy="12" r="3" />
    </svg>
  )
}

function LogOutIcon(props: SVGProps<SVGSVGElement>) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>
      <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
      <polyline points="16 17 21 12 16 7" />
      <line x1="21" x2="9" y1="12" y2="12" />
    </svg>
  )
}

export function Example() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger className={buttonVariants({ variant: "outline" })}>
        Open
      </DropdownMenuTrigger>
      <DropdownMenuContent className="w-56">
        <DropdownMenuItem>
          <UserIcon />
          Profile
        </DropdownMenuItem>
        <DropdownMenuItem>
          <CreditCardIcon />
          Billing
        </DropdownMenuItem>
        <DropdownMenuItem>
          <SettingsIcon />
          Settings
        </DropdownMenuItem>
        <DropdownMenuSeparator />
        <DropdownMenuItem>
          <LogOutIcon />
          Log out
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
```

## Checkboxes

Use `DropdownMenuCheckboxItem` for independent toggles. The menu stays open so you can change several at once.

```tsx
import * as React from "react"
import { buttonVariants } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuCheckboxItem,
  DropdownMenuContent,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

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

  return (
    <DropdownMenu>
      <DropdownMenuTrigger className={buttonVariants({ variant: "outline" })}>
        Open
      </DropdownMenuTrigger>
      <DropdownMenuContent className="w-56">
        <DropdownMenuLabel>Appearance</DropdownMenuLabel>
        <DropdownMenuSeparator />
        <DropdownMenuCheckboxItem
          checked={statusBar}
          onCheckedChange={setStatusBar}
        >
          Status Bar
        </DropdownMenuCheckboxItem>
        <DropdownMenuCheckboxItem
          checked={activityBar}
          onCheckedChange={setActivityBar}
        >
          Activity Bar
        </DropdownMenuCheckboxItem>
        <DropdownMenuCheckboxItem checked={panel} onCheckedChange={setPanel}>
          Panel
        </DropdownMenuCheckboxItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
```

## Checkboxes with Icons

Checkbox items with leading icons for settings-style toggles.

```tsx
import type { SVGProps } from "react"
import * as React from "react"
import { buttonVariants } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuCheckboxItem,
  DropdownMenuContent,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

function BellIcon(props: SVGProps<SVGSVGElement>) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>
      <path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" />
      <path d="M10.3 21a1.94 1.94 0 0 0 3.4 0" />
    </svg>
  )
}

function MailIcon(props: SVGProps<SVGSVGElement>) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>
      <rect width="20" height="16" x="2" y="4" rx="2" />
      <path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" />
    </svg>
  )
}

function MessageIcon(props: SVGProps<SVGSVGElement>) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>
      <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
    </svg>
  )
}

export function Example() {
  const [push, setPush] = React.useState(true)
  const [email, setEmail] = React.useState(false)
  const [messages, setMessages] = React.useState(false)

  return (
    <DropdownMenu>
      <DropdownMenuTrigger className={buttonVariants({ variant: "outline" })}>
        Notifications
      </DropdownMenuTrigger>
      <DropdownMenuContent className="w-56">
        <DropdownMenuLabel>Notifications</DropdownMenuLabel>
        <DropdownMenuSeparator />
        <DropdownMenuCheckboxItem checked={push} onCheckedChange={setPush}>
          <BellIcon />
          Push Notifications
        </DropdownMenuCheckboxItem>
        <DropdownMenuCheckboxItem checked={email} onCheckedChange={setEmail}>
          <MailIcon />
          Email Notifications
        </DropdownMenuCheckboxItem>
        <DropdownMenuCheckboxItem
          checked={messages}
          onCheckedChange={setMessages}
        >
          <MessageIcon />
          Message Notifications
        </DropdownMenuCheckboxItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
```

## Radio

`DropdownMenuRadioItem` inside `DropdownMenuRadioGroup`. One value at a time; the menu stays open while you switch.

```tsx
import * as React from "react"
import { buttonVariants } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuLabel,
  DropdownMenuRadioGroup,
  DropdownMenuRadioItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

export function Example() {
  const [plan, setPlan] = React.useState("personal")

  return (
    <DropdownMenu>
      <DropdownMenuTrigger className={buttonVariants({ variant: "outline" })}>
        Open
      </DropdownMenuTrigger>
      <DropdownMenuContent className="w-56">
        <DropdownMenuLabel>Billing Plan</DropdownMenuLabel>
        <DropdownMenuSeparator />
        <DropdownMenuRadioGroup value={plan} onValueChange={setPlan}>
          <DropdownMenuRadioItem value="personal">Personal</DropdownMenuRadioItem>
          <DropdownMenuRadioItem value="team">Team</DropdownMenuRadioItem>
          <DropdownMenuRadioItem value="enterprise">
            Enterprise
          </DropdownMenuRadioItem>
        </DropdownMenuRadioGroup>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
```

## Radio with Icons

Add icons to radio items when they help distinguish the choices, such as payment methods.

```tsx
import type { SVGProps } from "react"
import * as React from "react"
import { buttonVariants } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuLabel,
  DropdownMenuRadioGroup,
  DropdownMenuRadioItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

function CreditCardIcon(props: SVGProps<SVGSVGElement>) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>
      <rect width="20" height="14" x="2" y="5" rx="2" />
      <path d="M2 10h20" />
    </svg>
  )
}

function WalletIcon(props: SVGProps<SVGSVGElement>) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>
      <path d="M19 7V4a1 1 0 0 0-1-1H5a2 2 0 0 0 0 4h15a1 1 0 0 1 1 1v4h-3a2 2 0 0 0 0 4h3a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1" />
      <path d="M3 5v14a2 2 0 0 0 2 2h15a1 1 0 0 0 1-1v-4" />
    </svg>
  )
}

function SmartphoneIcon(props: SVGProps<SVGSVGElement>) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>
      <rect width="14" height="20" x="5" y="2" rx="2" ry="2" />
      <path d="M12 18h.01" />
    </svg>
  )
}

export function Example() {
  const [method, setMethod] = React.useState("card")

  return (
    <DropdownMenu>
      <DropdownMenuTrigger className={buttonVariants({ variant: "outline" })}>
        Payment Method
      </DropdownMenuTrigger>
      <DropdownMenuContent className="w-56">
        <DropdownMenuLabel>Payment Method</DropdownMenuLabel>
        <DropdownMenuSeparator />
        <DropdownMenuRadioGroup value={method} onValueChange={setMethod}>
          <DropdownMenuRadioItem value="card">
            <CreditCardIcon />
            Card
          </DropdownMenuRadioItem>
          <DropdownMenuRadioItem value="paypal">
            <WalletIcon />
            PayPal
          </DropdownMenuRadioItem>
          <DropdownMenuRadioItem value="apple">
            <SmartphoneIcon />
            Apple Pay
          </DropdownMenuRadioItem>
        </DropdownMenuRadioGroup>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
```

## Destructive

Set `variant="destructive"` on `DropdownMenuItem` for an irreversible action. The label and icon use the destructive color.

```tsx
import type { SVGProps } from "react"
import { buttonVariants } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

function PencilIcon(props: SVGProps<SVGSVGElement>) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>
      <path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" />
      <path d="m15 5 4 4" />
    </svg>
  )
}

function ShareIcon(props: SVGProps<SVGSVGElement>) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>
      <path d="M12 2v13" /><path d="m16 6-4-4-4 4" /><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8" />
    </svg>
  )
}

function ArchiveIcon(props: SVGProps<SVGSVGElement>) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>
      <rect width="20" height="5" x="2" y="3" rx="1" /><path d="M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8" /><path d="M10 12h4" />
    </svg>
  )
}

function TrashIcon(props: SVGProps<SVGSVGElement>) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>
      <path d="M3 6h18" /><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" /><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
    </svg>
  )
}

export function Example() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger className={buttonVariants({ variant: "outline" })}>
        Open
      </DropdownMenuTrigger>
      <DropdownMenuContent className="w-56">
        <DropdownMenuItem>
          <PencilIcon />
          Edit
        </DropdownMenuItem>
        <DropdownMenuItem>
          <ShareIcon />
          Share
        </DropdownMenuItem>
        <DropdownMenuItem>
          <ArchiveIcon />
          Archive
        </DropdownMenuItem>
        <DropdownMenuSeparator />
        <DropdownMenuItem variant="destructive">
          <TrashIcon />
          Delete
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
```

## Avatar

The trigger can contain anything, including an avatar for account switchers or profile menus.

```tsx
import {
  Avatar,
  AvatarFallback,
  AvatarImage,
} from "@/components/ui/avatar"
import { buttonVariants } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { cn } from "@/lib/utils"

export function Example() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger
        className={cn(
          buttonVariants({ variant: "outline" }),
          "h-auto gap-2 px-2 py-1.5",
        )}
      >
        <Avatar>
          <AvatarImage
            src="/assets/brand/demos/avatars/avatar-1.png"
            alt="@arctic"
          />
          <AvatarFallback>AR</AvatarFallback>
        </Avatar>
        <span className="text-sm">@arctic</span>
      </DropdownMenuTrigger>
      <DropdownMenuContent className="w-56" align="center">
        <DropdownMenuLabel>Switch account</DropdownMenuLabel>
        <DropdownMenuSeparator />
        <DropdownMenuGroup>
          <DropdownMenuItem>
            <Avatar size="sm">
              <AvatarImage
                src="/assets/brand/demos/avatars/avatar-1.png"
                alt="@arctic"
              />
              <AvatarFallback>AR</AvatarFallback>
            </Avatar>
            @arctic
          </DropdownMenuItem>
          <DropdownMenuItem>
            <Avatar size="sm">
              <AvatarImage
                src="/assets/brand/demos/avatars/avatar-2.png"
                alt="@nova"
              />
              <AvatarFallback>NV</AvatarFallback>
            </Avatar>
            @nova
          </DropdownMenuItem>
          <DropdownMenuItem>
            <Avatar size="sm">
              <AvatarImage
                src="/assets/brand/demos/avatars/avatar-3.png"
                alt="@orbit"
              />
              <AvatarFallback>OR</AvatarFallback>
            </Avatar>
            @orbit
          </DropdownMenuItem>
        </DropdownMenuGroup>
        <DropdownMenuSeparator />
        <DropdownMenuItem>Log out</DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
```

## Complex

Combine groups, icons, shortcuts, and submenus in one menu.

```tsx
import type { SVGProps } from "react"
import { buttonVariants } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuShortcut,
  DropdownMenuSub,
  DropdownMenuSubContent,
  DropdownMenuSubTrigger,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

function UserIcon(props: SVGProps<SVGSVGElement>) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>
      <path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" />
      <circle cx="12" cy="7" r="4" />
    </svg>
  )
}

function UsersIcon(props: SVGProps<SVGSVGElement>) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>
      <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
      <circle cx="9" cy="7" r="4" />
      <path d="M22 21v-2a4 4 0 0 0-3-3.87" />
      <path d="M16 3.13a4 4 0 0 1 0 7.75" />
    </svg>
  )
}

function PlusIcon(props: SVGProps<SVGSVGElement>) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>
      <path d="M5 12h14" />
      <path d="M12 5v14" />
    </svg>
  )
}

function CloudIcon(props: SVGProps<SVGSVGElement>) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>
      <path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z" />
    </svg>
  )
}

function LogOutIcon(props: SVGProps<SVGSVGElement>) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>
      <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
      <polyline points="16 17 21 12 16 7" />
      <line x1="21" x2="9" y1="12" y2="12" />
    </svg>
  )
}

export function Example() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger className={buttonVariants({ variant: "outline" })}>
        Open
      </DropdownMenuTrigger>
      <DropdownMenuContent className="w-56">
        <DropdownMenuLabel>My Account</DropdownMenuLabel>
        <DropdownMenuGroup>
          <DropdownMenuItem>
            <UserIcon />
            Profile
            <DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
          </DropdownMenuItem>
          <DropdownMenuItem>
            <UsersIcon />
            Team
          </DropdownMenuItem>
          <DropdownMenuItem disabled>
            <CloudIcon />
            Billing
            <DropdownMenuShortcut>⌘B</DropdownMenuShortcut>
          </DropdownMenuItem>
        </DropdownMenuGroup>
        <DropdownMenuSeparator />
        <DropdownMenuGroup>
          <DropdownMenuItem>
            <PlusIcon />
            New Team
            <DropdownMenuShortcut>⌘T</DropdownMenuShortcut>
          </DropdownMenuItem>
          <DropdownMenuSub>
            <DropdownMenuSubTrigger>Invite users</DropdownMenuSubTrigger>
            <DropdownMenuSubContent>
              <DropdownMenuItem>Email</DropdownMenuItem>
              <DropdownMenuItem>Message</DropdownMenuItem>
              <DropdownMenuSeparator />
              <DropdownMenuItem>More…</DropdownMenuItem>
            </DropdownMenuSubContent>
          </DropdownMenuSub>
        </DropdownMenuGroup>
        <DropdownMenuSeparator />
        <DropdownMenuItem>
          <LogOutIcon />
          Log out
          <DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
```

## API Reference

### DropdownMenu

Root provider for the trigger and content.

| Prop | Type | Default |
| --- | --- | --- |
| children | ReactNode | — |

```tsx
<DropdownMenu>
  <DropdownMenuTrigger>Open</DropdownMenuTrigger>
  <DropdownMenuContent>
    <DropdownMenuItem>Open</DropdownMenuItem>
  </DropdownMenuContent>
</DropdownMenu>
```

### DropdownMenuTrigger

Button that toggles the menu. Style it with `buttonVariants` or your own classes.

| Prop | Type | Default |
| --- | --- | --- |
| className | string | — |

```tsx
<DropdownMenuTrigger className={buttonVariants({ variant: "outline" })}>
  Open
</DropdownMenuTrigger>
```

### DropdownMenuContent

Menu panel positioned below the trigger and kept within the viewport.

| Prop | Type | Default |
| --- | --- | --- |
| align | "start" \| "center" \| "end" | "center" |
| sideOffset | number | 6 |
| className | string | — |

```tsx
<DropdownMenuContent align="end" sideOffset={8}>
  <DropdownMenuItem>Open</DropdownMenuItem>
</DropdownMenuContent>
```

### DropdownMenuItem

Choosing an item closes the menu unless it is a checkbox or radio item.

| Prop | Type | Default |
| --- | --- | --- |
| variant | "default" \| "destructive" | "default" |
| inset | boolean | false |
| href | string | — |
| disabled | boolean | false |
| className | string | — |

```tsx
<DropdownMenuItem href="/settings">Settings</DropdownMenuItem>
```

### DropdownMenuCheckboxItem

Toggle that does not close the menu on click.

| Prop | Type | Default |
| --- | --- | --- |
| checked | boolean | false |
| onCheckedChange | (checked: boolean) => void | — |
| disabled | boolean | false |
| className | string | — |

```tsx
<DropdownMenuCheckboxItem checked>
  Show bookmarks
</DropdownMenuCheckboxItem>
```

### DropdownMenuRadioGroup

Exclusive selection group for `DropdownMenuRadioItem`.

| Prop | Type | Default |
| --- | --- | --- |
| value | string | "" |
| onValueChange | (value: string) => void | — |
| className | string | — |

```tsx
<DropdownMenuRadioGroup value="personal">
  <DropdownMenuRadioItem value="personal">Personal</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
```

### DropdownMenuRadioItem

Must be used inside `DropdownMenuRadioGroup`. Does not close the menu on click.

| Prop | Type | Default |
| --- | --- | --- |
| value | string | — |
| disabled | boolean | false |
| className | string | — |

```tsx
<DropdownMenuRadioItem value="team">Team</DropdownMenuRadioItem>
```

### DropdownMenuLabel

| Prop | Type | Default |
| --- | --- | --- |
| inset | boolean | false |
| className | string | — |

```tsx
<DropdownMenuLabel>Account</DropdownMenuLabel>
```

### DropdownMenuShortcut

| Prop | Type | Default |
| --- | --- | --- |
| className | string | — |

```tsx
<DropdownMenuItem>
  Save
  <DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
</DropdownMenuItem>
```

### DropdownMenuSeparator

| Prop | Type | Default |
| --- | --- | --- |
| className | string | — |

```tsx
<DropdownMenuSeparator />
```

### DropdownMenuGroup

| Prop | Type | Default |
| --- | --- | --- |
| className | string | — |

```tsx
<DropdownMenuGroup>
  <DropdownMenuItem>Open</DropdownMenuItem>
</DropdownMenuGroup>
```

### DropdownMenuSub

| Prop | Type | Default |
| --- | --- | --- |
| children | ReactNode | — |

```tsx
<DropdownMenuSub>
  <DropdownMenuSubTrigger>More</DropdownMenuSubTrigger>
  <DropdownMenuSubContent>
    <DropdownMenuItem>Save</DropdownMenuItem>
  </DropdownMenuSubContent>
</DropdownMenuSub>
```

### DropdownMenuSubTrigger

| Prop | Type | Default |
| --- | --- | --- |
| inset | boolean | false |
| className | string | — |

```tsx
<DropdownMenuSubTrigger>More</DropdownMenuSubTrigger>
```

### DropdownMenuSubContent

| Prop | Type | Default |
| --- | --- | --- |
| className | string | — |

```tsx
<DropdownMenuSubContent>
  <DropdownMenuItem>Save</DropdownMenuItem>
</DropdownMenuSubContent>
```

## Source

```tsx
"use client";

import {
  createContext,
  useContext,
  useEffect,
  useLayoutEffect,
  useMemo,
  useRef,
  useState,
  type ButtonHTMLAttributes,
  type CSSProperties,
  type HTMLAttributes,
  type MouseEvent,
  type ReactNode,
  type RefObject,
} from "react";
import { createPortal } from "react-dom";
import {
  buttonGroupFaceClass,
  buttonGroupJoinClass,
  buttonGroupOutlineJoinClass,
  buttonGroupOverlapClass,
  useButtonGroup,
} from "@/components/ui/button-group";
import { cn } from "@/lib/utils";
import { useBodyScrollLock } from "@/lib/use-body-scroll-lock";
import { useOverlayEntered } from "@/lib/use-overlay-entered";
import { useSubmenuPosition } from "@/lib/use-submenu-position";

type DropdownMenuContextValue = {
  open: boolean;
  setOpen: (open: boolean) => void;
  rootRef: RefObject<HTMLDivElement | null>;
  contentRef: RefObject<HTMLDivElement | null>;
};

const DropdownMenuContext = createContext<DropdownMenuContextValue | null>(
  null,
);

function useDropdownMenu() {
  const context = useContext(DropdownMenuContext);
  if (!context) {
    throw new Error("DropdownMenu parts must be used within <DropdownMenu>");
  }
  return context;
}

type DropdownMenuProps = {
  children: ReactNode;
};

export function DropdownMenu({ children }: DropdownMenuProps) {
  const [open, setOpen] = useState(false);
  const rootRef = useRef<HTMLDivElement>(null);
  const contentRef = useRef<HTMLDivElement>(null);
  const group = useButtonGroup();

  useBodyScrollLock(open);

  useEffect(() => {
    if (!open) return;

    function onPointerDown(event: PointerEvent) {
      const target = event.target as Node;
      if (
        rootRef.current?.contains(target) ||
        contentRef.current?.contains(target)
      ) {
        return;
      }
      setOpen(false);
    }

    function onKeyDown(event: KeyboardEvent) {
      if (event.key === "Escape") setOpen(false);
    }

    document.addEventListener("pointerdown", onPointerDown, true);
    document.addEventListener("keydown", onKeyDown);
    return () => {
      document.removeEventListener("pointerdown", onPointerDown, true);
      document.removeEventListener("keydown", onKeyDown);
    };
  }, [open]);

  const value = useMemo(
    () => ({ open, setOpen, rootRef, contentRef }),
    [open],
  );

  return (
    <DropdownMenuContext.Provider value={value}>
      <div
        ref={rootRef}
        data-slot="dropdown-menu"
        className={cn(
          "relative inline-flex w-fit",
          buttonGroupOverlapClass(group),
        )}
      >
        {children}
      </div>
    </DropdownMenuContext.Provider>
  );
}

type DropdownMenuTriggerProps = ButtonHTMLAttributes<HTMLButtonElement> & {
  children: ReactNode;
};

export function DropdownMenuTrigger({
  className,
  children,
  onClick,
  ...props
}: DropdownMenuTriggerProps) {
  const { open, setOpen } = useDropdownMenu();
  const group = useButtonGroup();

  return (
    <button
      type="button"
      data-slot="dropdown-menu-trigger"
      aria-expanded={open}
      aria-haspopup="menu"
      className={cn(
        "[&_[data-slot=avatar]]:ring-0",
        className,
        buttonGroupFaceClass(group),
        typeof className === "string" &&
          className.includes("border") &&
          cn(
            buttonGroupJoinClass(group),
            buttonGroupOutlineJoinClass(group),
          ),
      )}
      onClick={(event) => {
        onClick?.(event);
        if (!event.defaultPrevented) setOpen(!open);
      }}
      {...props}
    >
      {children}
    </button>
  );
}

type DropdownMenuContentProps = HTMLAttributes<HTMLDivElement> & {
  children: ReactNode;
  align?: "start" | "center" | "end";
  sideOffset?: number;
};

export function DropdownMenuContent({
  className,
  children,
  align = "center",
  sideOffset = 6,
  style,
  ...props
}: DropdownMenuContentProps) {
  const { open, rootRef, contentRef } = useDropdownMenu();
  const entered = useOverlayEntered(open);
  const [coords, setCoords] = useState<CSSProperties>({
    top: 0,
    left: 0,
  });
  const [mounted, setMounted] = useState(false);

  useEffect(() => {
    setMounted(true);
  }, []);

  useLayoutEffect(() => {
    if (!open || !mounted || !rootRef.current) return;

    function update() {
      const trigger = rootRef.current?.getBoundingClientRect();
      const content = contentRef.current?.getBoundingClientRect();
      if (!trigger) return;

      const pad = 8;
      const width = content?.width ?? 0;
      const height = content?.height ?? 0;

      let top = trigger.bottom + sideOffset;
      if (top + height > window.innerHeight - pad) {
        top = Math.max(pad, trigger.top - sideOffset - height);
      }

      const next: CSSProperties = { top };

      if (align === "end") {
        const right = Math.max(pad, window.innerWidth - trigger.right);
        next.right = right;
        next.left = "auto";
        if (width > 0 && right + width > window.innerWidth - pad) {
          next.right = Math.max(pad, window.innerWidth - width - pad);
        }
      } else if (align === "center") {
        let left = trigger.left + trigger.width / 2 - width / 2;
        left = Math.min(
          Math.max(pad, left),
          window.innerWidth - width - pad,
        );
        next.left = left;
        next.right = "auto";
      } else {
        let left = trigger.left;
        if (left + width > window.innerWidth - pad) {
          left = Math.max(pad, window.innerWidth - width - pad);
        }
        next.left = left;
        next.right = "auto";
      }

      setCoords(next);
    }

    update();
    const raf = requestAnimationFrame(update);
    window.addEventListener("scroll", update, true);
    window.addEventListener("resize", update);
    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener("scroll", update, true);
      window.removeEventListener("resize", update);
    };
  }, [open, mounted, align, sideOffset, rootRef, contentRef]);

  if (!mounted || !open) return null;

  return createPortal(
    <div
      ref={contentRef}
      data-slot="dropdown-menu-content"
      role="menu"
      style={{ ...style, ...coords }}
      className={cn(
        "arctis-overlay fixed z-[70] w-max min-w-40 origin-top rounded-md border border-foreground/10 bg-surface p-1 text-foreground [&_[data-slot=avatar]]:ring-0",
        align === "start" && "origin-top-left",
        align === "center" && "arctis-overlay-center origin-top",
        align === "end" && "origin-top-right",
        entered
          ? "arctis-overlay-open pointer-events-auto"
          : "pointer-events-none",
        className,
      )}
      {...props}
    >
      {children}
    </div>,
    document.body,
  );
}

const itemClass =
  "relative flex w-full cursor-pointer items-center gap-1.5 rounded-sm px-2 py-1.5 text-left text-[13px] font-normal tracking-wide outline-none select-none transition-colors duration-200 ease-out hover:bg-surface-hover hover:text-foreground disabled:pointer-events-none disabled:opacity-40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5 [&_svg:not([class*='text-'])]:text-muted-foreground";

type DropdownMenuItemProps = ButtonHTMLAttributes<HTMLButtonElement> & {
  children: ReactNode;
  href?: string;
  inset?: boolean;
  variant?: "default" | "destructive";
};

export function DropdownMenuItem({
  className,
  children,
  href,
  inset,
  variant = "default",
  disabled,
  onClick,
  ...props
}: DropdownMenuItemProps) {
  const { setOpen } = useDropdownMenu();
  const classes = cn(
    itemClass,
    inset && "pl-8",
    variant === "destructive" &&
      "text-destructive hover:bg-destructive/10 hover:text-destructive [&_svg:not([class*='text-'])]:text-destructive",
    className,
  );

  if (href) {
    return (
      <a
        href={disabled ? undefined : href}
        role="menuitem"
        data-slot="dropdown-menu-item"
        data-variant={variant}
        data-disabled={disabled ? "" : undefined}
        aria-disabled={disabled || undefined}
        className={cn(classes, disabled && "pointer-events-none opacity-40")}
        onClick={(event) => {
          onClick?.(event as unknown as MouseEvent<HTMLButtonElement>);
          if (!event.defaultPrevented && !disabled) setOpen(false);
        }}
      >
        {children}
      </a>
    );
  }

  return (
    <button
      type="button"
      role="menuitem"
      data-slot="dropdown-menu-item"
      data-variant={variant}
      data-disabled={disabled ? "" : undefined}
      disabled={disabled}
      className={classes}
      onClick={(event) => {
        onClick?.(event);
        if (!event.defaultPrevented && !disabled) setOpen(false);
      }}
      {...props}
    >
      {children}
    </button>
  );
}

type DropdownMenuCheckboxItemProps = ButtonHTMLAttributes<HTMLButtonElement> & {
  children: ReactNode;
  checked?: boolean;
  onCheckedChange?: (checked: boolean) => void;
};

export function DropdownMenuCheckboxItem({
  className,
  children,
  checked = false,
  disabled,
  onCheckedChange,
  onClick,
  ...props
}: DropdownMenuCheckboxItemProps) {
  return (
    <button
      type="button"
      role="menuitemcheckbox"
      aria-checked={checked}
      data-slot="dropdown-menu-checkbox-item"
      data-disabled={disabled ? "" : undefined}
      disabled={disabled}
      className={cn(itemClass, "pl-8", className)}
      onClick={(event) => {
        onClick?.(event);
        if (event.defaultPrevented || disabled) return;
        onCheckedChange?.(!checked);
      }}
      {...props}
    >
      <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
        {checked ? <CheckIcon className="size-3.5" /> : null}
      </span>
      {children}
    </button>
  );
}

type DropdownMenuRadioGroupContextValue = {
  value: string;
  onValueChange?: (value: string) => void;
};

const DropdownMenuRadioGroupContext =
  createContext<DropdownMenuRadioGroupContextValue | null>(null);

type DropdownMenuRadioGroupProps = HTMLAttributes<HTMLDivElement> & {
  value?: string;
  onValueChange?: (value: string) => void;
  children: ReactNode;
};

export function DropdownMenuRadioGroup({
  className,
  value = "",
  onValueChange,
  children,
  ...props
}: DropdownMenuRadioGroupProps) {
  const context = useMemo(
    () => ({ value, onValueChange }),
    [value, onValueChange],
  );

  return (
    <DropdownMenuRadioGroupContext.Provider value={context}>
      <div
        role="radiogroup"
        data-slot="dropdown-menu-radio-group"
        className={cn(className)}
        {...props}
      >
        {children}
      </div>
    </DropdownMenuRadioGroupContext.Provider>
  );
}

type DropdownMenuRadioItemProps = ButtonHTMLAttributes<HTMLButtonElement> & {
  children: ReactNode;
  value: string;
};

export function DropdownMenuRadioItem({
  className,
  children,
  value,
  disabled,
  onClick,
  ...props
}: DropdownMenuRadioItemProps) {
  const group = useContext(DropdownMenuRadioGroupContext);
  const checked = group?.value === value;

  return (
    <button
      type="button"
      role="menuitemradio"
      aria-checked={checked}
      data-slot="dropdown-menu-radio-item"
      data-disabled={disabled ? "" : undefined}
      disabled={disabled}
      className={cn(itemClass, "pl-8", className)}
      onClick={(event) => {
        onClick?.(event);
        if (event.defaultPrevented || disabled) return;
        group?.onValueChange?.(value);
      }}
      {...props}
    >
      <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
        {checked ? <CircleIcon className="size-2 fill-current" /> : null}
      </span>
      {children}
    </button>
  );
}

type DropdownMenuLabelProps = HTMLAttributes<HTMLDivElement> & {
  children: ReactNode;
  inset?: boolean;
};

export function DropdownMenuLabel({
  className,
  children,
  inset,
  ...props
}: DropdownMenuLabelProps) {
  return (
    <div
      data-slot="dropdown-menu-label"
      className={cn(
        "px-2 py-1.5 text-xs tracking-wide text-muted-foreground",
        inset && "pl-8",
        className,
      )}
      {...props}
    >
      {children}
    </div>
  );
}

type DropdownMenuSeparatorProps = HTMLAttributes<HTMLDivElement>;

export function DropdownMenuSeparator({
  className,
  ...props
}: DropdownMenuSeparatorProps) {
  return (
    <div
      role="separator"
      data-slot="dropdown-menu-separator"
      className={cn("-mx-1 my-1 h-px bg-foreground/10", className)}
      {...props}
    />
  );
}

type DropdownMenuShortcutProps = HTMLAttributes<HTMLSpanElement> & {
  children: ReactNode;
};

export function DropdownMenuShortcut({
  className,
  children,
  ...props
}: DropdownMenuShortcutProps) {
  return (
    <span
      data-slot="dropdown-menu-shortcut"
      className={cn(
        "ml-auto text-xs tracking-widest text-muted-foreground",
        className,
      )}
      {...props}
    >
      {children}
    </span>
  );
}

type DropdownMenuGroupProps = HTMLAttributes<HTMLDivElement> & {
  children: ReactNode;
};

export function DropdownMenuGroup({
  className,
  children,
  ...props
}: DropdownMenuGroupProps) {
  return (
    <div
      role="group"
      data-slot="dropdown-menu-group"
      className={cn(className)}
      {...props}
    >
      {children}
    </div>
  );
}

type DropdownMenuSubContextValue = {
  open: boolean;
  setOpen: (open: boolean) => void;
};

const DropdownMenuSubContext =
  createContext<DropdownMenuSubContextValue | null>(null);

type DropdownMenuSubProps = {
  children: ReactNode;
};

export function DropdownMenuSub({ children }: DropdownMenuSubProps) {
  const [open, setOpen] = useState(false);
  const value = useMemo(() => ({ open, setOpen }), [open]);

  return (
    <DropdownMenuSubContext.Provider value={value}>
      <div
        data-slot="dropdown-menu-sub"
        className="relative"
        onMouseLeave={() => setOpen(false)}
      >
        {children}
      </div>
    </DropdownMenuSubContext.Provider>
  );
}

function useDropdownMenuSub() {
  const context = useContext(DropdownMenuSubContext);
  if (!context) {
    throw new Error(
      "DropdownMenuSub parts must be used within <DropdownMenuSub>",
    );
  }
  return context;
}

type DropdownMenuSubTriggerProps = ButtonHTMLAttributes<HTMLButtonElement> & {
  children: ReactNode;
  inset?: boolean;
};

export function DropdownMenuSubTrigger({
  className,
  children,
  inset,
  ...props
}: DropdownMenuSubTriggerProps) {
  const { setOpen } = useDropdownMenuSub();

  return (
    <button
      type="button"
      role="menuitem"
      data-slot="dropdown-menu-sub-trigger"
      aria-haspopup="menu"
      className={cn(itemClass, "pr-1.5", inset && "pl-8", className)}
      onMouseEnter={() => setOpen(true)}
      onFocus={() => setOpen(true)}
      {...props}
    >
      {children}
      <ChevronRightIcon className="ml-auto size-3.5 text-muted-foreground" />
    </button>
  );
}

type DropdownMenuSubContentProps = HTMLAttributes<HTMLDivElement> & {
  children: ReactNode;
};

export function DropdownMenuSubContent({
  className,
  children,
  style,
  ...props
}: DropdownMenuSubContentProps) {
  const { open } = useDropdownMenuSub();
  const entered = useOverlayEntered(open);
  const contentRef = useRef<HTMLDivElement>(null);
  const placement = useSubmenuPosition(open, contentRef);

  return (
    <div
      ref={contentRef}
      data-slot="dropdown-menu-sub-content"
      role="menu"
      style={{ ...style, ...placement.style }}
      className={cn(
        "arctis-overlay absolute z-50 w-max min-w-32 rounded-md border border-foreground/10 bg-surface p-1 text-foreground",
        placement.side === "left" ? "origin-top-right" : "origin-top-left",
        entered
          ? "arctis-overlay-open pointer-events-auto"
          : "pointer-events-none",
        className,
      )}
      {...props}
    >
      {children}
    </div>
  );
}

function CheckIcon({ className }: { className?: string }) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="2"
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
      className={className}
    >
      <path d="M5 13l4 4L19 7" />
    </svg>
  );
}

function CircleIcon({ className }: { className?: string }) {
  return (
    <svg viewBox="0 0 24 24" aria-hidden="true" className={className}>
      <circle cx="12" cy="12" r="10" />
    </svg>
  );
}

function ChevronRightIcon({ className }: { className?: string }) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="2"
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
      className={className}
    >
      <path d="m9 18 6-6-6-6" />
    </svg>
  );
}
```
