# DropdownMenu

> A list of actions on one thing, opened from a button, that can also hold a setting-style choice with a check mark on the current value.

Source: https://raft-design.botiverse.build/components/dropdown-menu. Generated from the current release; where it differs from what you remember, this page is right.

A dropdown menu is a list of actions on one thing (a message, a channel, a task, an agent), opened from a button such as ⋯. It can also hold a setting-style choice, such as sort order or status, with a check mark on the current value. Right-click on desktop and press and hold on a phone open the same menu as a [ContextMenu](https://raft-design.botiverse.build/components/context-menu.md).

```tsx
import {
  ArrowDownAZ,
  ArrowUpDown,
  Clock,
  EllipsisVertical,
  GitBranch,
  GripVertical,
  Plus,
} from "lucide-react";
import { createContext, useCallback, useContext, useState } from "react";
import type { ReactNode } from "react";
import {
  Button,
  ContextMenu,
  ContextMenuContent,
  ContextMenuFooter,
  ContextMenuItem,
  ContextMenuItemLabel,
  ContextMenuRadioGroup,
  ContextMenuRadioItem,
  ContextMenuSeparator,
  ContextMenuTrigger,
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuFooter,
  DropdownMenuItem,
  DropdownMenuItemLabel,
  DropdownMenuRadioGroup,
  DropdownMenuRadioItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
  SidebarControl,
  SidebarSectionHeader,
} from "@botiverse/raft-ui";

const SORT_MENU_CLASS = "flex w-[160px] min-w-[160px] flex-col";
const CHANNELS_MENU_CLASS = "flex w-[190px] min-w-[190px] flex-col";

/**
 * Lets the menus rendered inside a section header mirror their open state onto
 * the header element as `data-popup-open`. The attention badge hides off that
 * attribute (`group-data-popup-open/section-header`), because the previous
 * ancestor `:has()` version made Chromium recalculate the whole page on
 * unrelated mutations (#386). The provider lives in SidebarSectionHeaderMenu.
 */
const SectionPopupOpenContext = createContext<((open: boolean) => void) | null>(null);

const SIDEBAR_SORT_OPTIONS = [
  { value: "manual", label: "Manual", icon: <GripVertical /> },
  { value: "recent", label: "Recent", icon: <Clock /> },
  { value: "az", label: "A-Z", icon: <ArrowDownAZ /> },
] as const;

export type SidebarSortValue = (typeof SIDEBAR_SORT_OPTIONS)[number]["value"];

type SidebarSortItemsProps = {
  value?: SidebarSortValue;
  defaultValue?: SidebarSortValue;
  onValueChange?: (value: SidebarSortValue) => void;
};

function isSidebarSortValue(value: string): value is SidebarSortValue {
  return SIDEBAR_SORT_OPTIONS.some((option) => option.value === value);
}

function handleSidebarSortValueChange(
  onValueChange: ((value: SidebarSortValue) => void) | undefined,
  value: string,
) {
  if (onValueChange && isSidebarSortValue(value)) onValueChange(value);
}

function ChannelsMenuFooterText({ count, label }: { count: number; label: string }) {
  return (
    <>
      {count} {label}
    </>
  );
}

export function SidebarDropdownSortItems({
  value,
  defaultValue = "manual",
  onValueChange,
}: SidebarSortItemsProps = {}) {
  const handleValueChange = useCallback(
    (nextValue: string) => handleSidebarSortValueChange(onValueChange, nextValue),
    [onValueChange],
  );

  return (
    <DropdownMenuRadioGroup
      defaultValue={value === undefined ? defaultValue : undefined}
      value={value}
      onValueChange={handleValueChange}
    >
      {SIDEBAR_SORT_OPTIONS.map((option) => (
        <DropdownMenuRadioItem key={option.value} value={option.value}>
          {option.icon}
          <DropdownMenuItemLabel>{option.label}</DropdownMenuItemLabel>
        </DropdownMenuRadioItem>
      ))}
    </DropdownMenuRadioGroup>
  );
}

export function SidebarContextSortItems({
  value,
  defaultValue = "manual",
  onValueChange,
}: SidebarSortItemsProps = {}) {
  const handleValueChange = useCallback(
    (nextValue: string) => handleSidebarSortValueChange(onValueChange, nextValue),
    [onValueChange],
  );

  return (
    <ContextMenuRadioGroup
      defaultValue={value === undefined ? defaultValue : undefined}
      value={value}
      onValueChange={handleValueChange}
    >
      {SIDEBAR_SORT_OPTIONS.map((option) => (
        <ContextMenuRadioItem key={option.value} value={option.value}>
          {option.icon}
          <ContextMenuItemLabel>{option.label}</ContextMenuItemLabel>
        </ContextMenuRadioItem>
      ))}
    </ContextMenuRadioGroup>
  );
}

export function SidebarSectionSortDropdownContent(props: SidebarSortItemsProps = {}) {
  return (
    <DropdownMenuContent align="end" className={SORT_MENU_CLASS}>
      <SidebarDropdownSortItems {...props} />
    </DropdownMenuContent>
  );
}

export function SidebarSectionSortContextContent(props: SidebarSortItemsProps = {}) {
  return (
    <ContextMenuContent className={SORT_MENU_CLASS}>
      <SidebarContextSortItems {...props} />
    </ContextMenuContent>
  );
}

export function SidebarSectionChannelsDropdownContent({
  count,
  countLabel = "channels",
  createLabel = "Create Channel",
  createIcon = "channel",
  createDisabled = false,
  onCreate,
  sortValue,
  onSortValueChange,
}: {
  count: number;
  countLabel?: string;
  createLabel?: string;
  createIcon?: "channel" | "joint";
  createDisabled?: boolean;
  onCreate?: () => void;
  sortValue?: SidebarSortValue;
  onSortValueChange?: (value: SidebarSortValue) => void;
}) {
  const CreateIcon = createIcon === "joint" ? GitBranch : Plus;
  return (
    <DropdownMenuContent align="end" className={CHANNELS_MENU_CLASS}>
      <DropdownMenuItem disabled={createDisabled} onClick={createDisabled ? undefined : onCreate}>
        <CreateIcon />
        <DropdownMenuItemLabel>{createLabel}</DropdownMenuItemLabel>
      </DropdownMenuItem>
      {createIcon === "channel" ? (
        <DropdownMenuItem disabled={createDisabled}>
          <GitBranch />
          <DropdownMenuItemLabel>Create Joint Channel</DropdownMenuItemLabel>
        </DropdownMenuItem>
      ) : null}
      <DropdownMenuSeparator />
      <SidebarDropdownSortItems value={sortValue} onValueChange={onSortValueChange} />
      <DropdownMenuSeparator />
      <DropdownMenuFooter>
        <ChannelsMenuFooterText count={count} label={countLabel} />
      </DropdownMenuFooter>
    </DropdownMenuContent>
  );
}

export function SidebarSectionChannelsContextContent({
  count,
  countLabel = "channels",
  createLabel = "Create Channel",
  createIcon = "channel",
  createDisabled = false,
  onCreate,
  sortValue,
  onSortValueChange,
}: {
  count: number;
  countLabel?: string;
  createLabel?: string;
  createIcon?: "channel" | "joint";
  createDisabled?: boolean;
  onCreate?: () => void;
  sortValue?: SidebarSortValue;
  onSortValueChange?: (value: SidebarSortValue) => void;
}) {
  const CreateIcon = createIcon === "joint" ? GitBranch : Plus;
  return (
    <ContextMenuContent className={CHANNELS_MENU_CLASS}>
      <ContextMenuItem disabled={createDisabled} onClick={createDisabled ? undefined : onCreate}>
        <CreateIcon />
        <ContextMenuItemLabel>{createLabel}</ContextMenuItemLabel>
      </ContextMenuItem>
      {createIcon === "channel" ? (
        <ContextMenuItem disabled={createDisabled}>
          <GitBranch />
          <ContextMenuItemLabel>Create Joint Channel</ContextMenuItemLabel>
        </ContextMenuItem>
      ) : null}
      <ContextMenuSeparator />
      <SidebarContextSortItems value={sortValue} onValueChange={onSortValueChange} />
      <ContextMenuSeparator />
      <ContextMenuFooter>
        <ChannelsMenuFooterText count={count} label={countLabel} />
      </ContextMenuFooter>
    </ContextMenuContent>
  );
}

type SidebarCreateMenuProps = {
  disabled?: boolean;
  onCreateChannel?: () => void;
  onCreateJoint?: () => void;
};

export function SidebarCreateDropdownContent({
  disabled = false,
  onCreateChannel,
  onCreateJoint,
}: SidebarCreateMenuProps) {
  return (
    <DropdownMenuContent align="end" className={CHANNELS_MENU_CLASS}>
      <DropdownMenuItem disabled={disabled} onClick={disabled ? undefined : onCreateChannel}>
        <Plus />
        <DropdownMenuItemLabel>Create Channel</DropdownMenuItemLabel>
      </DropdownMenuItem>
      <DropdownMenuItem disabled={disabled} onClick={disabled ? undefined : onCreateJoint}>
        <GitBranch />
        <DropdownMenuItemLabel>Create Joint Channel</DropdownMenuItemLabel>
      </DropdownMenuItem>
    </DropdownMenuContent>
  );
}

export function SidebarCreateMenu({
  disabled = false,
  onCreateChannel,
  onCreateJoint,
}: SidebarCreateMenuProps) {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger
        disabled={disabled}
        render={
          <Button
            type="button"
            variant="outline"
            size="icon-md"
            aria-label="Add channel"
            disabled={disabled}
          >
            <Plus />
          </Button>
        }
      />
      <SidebarCreateDropdownContent
        disabled={disabled}
        onCreateChannel={onCreateChannel}
        onCreateJoint={onCreateJoint}
      />
    </DropdownMenu>
  );
}

/** Hover action for Pinned / DM — sort only. */
export function SidebarSectionSortMenu({
  "aria-label": ariaLabel = "Sort section",
  value,
  onValueChange,
}: {
  "aria-label"?: string;
  value?: SidebarSortValue;
  onValueChange?: (value: SidebarSortValue) => void;
}) {
  const reportPopupOpen = useContext(SectionPopupOpenContext);
  return (
    <DropdownMenu onOpenChange={reportPopupOpen ?? undefined}>
      <DropdownMenuTrigger
        render={
          <SidebarControl type="button" aria-label={ariaLabel}>
            <ArrowUpDown />
          </SidebarControl>
        }
      />
      <SidebarSectionSortDropdownContent value={value} onValueChange={onValueChange} />
    </DropdownMenu>
  );
}

/** Plain + button for Channels / Joint. */
export function SidebarSectionAddButton({
  "aria-label": ariaLabel = "Add channel",
  disabled = false,
  onClick,
}: {
  "aria-label"?: string;
  disabled?: boolean;
  onClick?: () => void;
}) {
  return (
    <SidebarControl
      type="button"
      aria-label={ariaLabel}
      disabled={disabled}
      onClick={disabled ? undefined : onClick}
    >
      <Plus />
    </SidebarControl>
  );
}

/** Channels / Joint section menu: create + sort + count footer. */
export function SidebarSectionChannelsMenu({
  "aria-label": ariaLabel = "Section menu",
  count,
  countLabel,
  createLabel,
  createIcon,
  createDisabled = false,
  onCreate,
  sortValue,
  onSortValueChange,
}: {
  "aria-label"?: string;
  count: number;
  countLabel?: string;
  createLabel?: string;
  createIcon?: "channel" | "joint";
  createDisabled?: boolean;
  onCreate?: () => void;
  sortValue?: SidebarSortValue;
  onSortValueChange?: (value: SidebarSortValue) => void;
}) {
  const reportPopupOpen = useContext(SectionPopupOpenContext);
  return (
    <DropdownMenu onOpenChange={reportPopupOpen ?? undefined}>
      <DropdownMenuTrigger
        render={
          <SidebarControl type="button" aria-label={ariaLabel}>
            <EllipsisVertical />
          </SidebarControl>
        }
      />
      <SidebarSectionChannelsDropdownContent
        count={count}
        countLabel={countLabel}
        createLabel={createLabel}
        createIcon={createIcon}
        createDisabled={createDisabled}
        onCreate={onCreate}
        sortValue={sortValue}
        onSortValueChange={onSortValueChange}
      />
    </DropdownMenu>
  );
}

type SidebarSectionHeaderMenuProps = {
  children: ReactNode;
  menu: "sort" | "channels";
  count?: number;
  countLabel?: string;
  createLabel?: string;
  createIcon?: "channel" | "joint";
  createDisabled?: boolean;
  onCreate?: () => void;
  sortValue?: SidebarSortValue;
  onSortValueChange?: (value: SidebarSortValue) => void;
};

/** Section header with context menu mirroring the hover dropdown content. */
export function SidebarSectionHeaderMenu({
  children,
  menu,
  count = 0,
  countLabel,
  createLabel,
  createIcon,
  createDisabled = false,
  onCreate,
  sortValue,
  onSortValueChange,
}: SidebarSectionHeaderMenuProps) {
  const [popupOpen, setPopupOpen] = useState(false);
  const handlePopupOpenChange = useCallback((open: boolean) => {
    setPopupOpen(open);
  }, []);

  return (
    <SectionPopupOpenContext.Provider value={handlePopupOpenChange}>
      <ContextMenu>
        <ContextMenuTrigger className="w-full min-w-0">
          <SidebarSectionHeader data-popup-open={popupOpen ? "" : undefined}>
            {children}
          </SidebarSectionHeader>
        </ContextMenuTrigger>
        {menu === "sort" ? (
          <SidebarSectionSortContextContent value={sortValue} onValueChange={onSortValueChange} />
        ) : null}
        {menu === "channels" ? (
          <SidebarSectionChannelsContextContent
            count={count}
            countLabel={countLabel}
            createLabel={createLabel}
            createIcon={createIcon}
            createDisabled={createDisabled}
            onCreate={onCreate}
            sortValue={sortValue}
            onSortValueChange={onSortValueChange}
          />
        ) : null}
      </ContextMenu>
    </SectionPopupOpenContext.Provider>
  );
}
```

## When to use

| Situation                                                  | Use                                                                 |
| ---------------------------------------------------------- | ------------------------------------------------------------------- |
| Act on one thing, from a button                            | DropdownMenu                                                        |
| The same actions, on right-click or press and hold         | [ContextMenu](https://raft-design.botiverse.build/components/context-menu.md), with the same items |
| A setting-style choice that applies at once (sort, status) | DropdownMenu with radio items                                       |
| A value submitted with a form                              | [Select](https://raft-design.botiverse.build/components/select.md)                                       |
| A little input on click (snooze, rename)                   | [Popover](https://raft-design.botiverse.build/components/popover.md)                                    |
| A place to go, on a phone                                  | A bottom sheet ([Drawer](https://raft-design.botiverse.build/components/drawer.md))                      |

Which overlay fits which job is in [Choosing a component](https://raft-design.botiverse.build/components.md#overlays).

## Anatomy

- `DropdownMenu`: the root. Owns the open state. Required.
- `DropdownMenuTrigger`: the button that opens the menu. Renders an outline Button unless you pass `render`. Required.
- `DropdownMenuContent`: the menu, portaled and positioned next to the trigger, at least 240px wide. Required.
- `DropdownMenuItem`: one action. A 16px icon, then the label. Wrap the label in `DropdownMenuItemLabel` to truncate it.
- `DropdownMenuShortcut`: the keyboard shortcut at the end of an item, in `text-mono-meta` and `foreground-hint`.
- `DropdownMenuItemCount`: a count at the end of an item.
- `DropdownMenuRadioGroup` and `DropdownMenuRadioItem`: a set of values with one current value, marked with a check mark.
- `DropdownMenuCheckboxItem`: an option that is on or off by itself.
- `DropdownMenuLabel`: a heading over a group of items. Not interactive.
- `DropdownMenuSeparator`: the line between sections.
- `DropdownMenuSubmenu` and `DropdownMenuSubmenuTrigger`: a nested menu, opened from an item with a chevron.
- `DropdownMenuFooter`: a line of quiet text at the bottom, such as a count.
- `DropdownMenuPortal` and `DropdownMenuPopup`: lower-level parts. `DropdownMenuPopup` is the menu surface without positioning.

## States

Hover, keyboard highlight, the current value and disabled items follow [States](https://raft-design.botiverse.build/foundations/states.md#usage). In a menu:

- Keep the highlight and the current value apart. The `brand-soft` highlight shows where the pointer or keyboard is; the check mark and bold text show which value is chosen. Never use the highlight to mark the current value.
- Show a disabled item with `foreground-disabled` text and icon, and nothing else.

## Behavior

- The menu opens below its trigger, aligned to the trigger's start edge, and moves or flips when it meets the window edge ([Elevation](https://raft-design.botiverse.build/foundations/elevation.md#popups)).
- Choosing an action closes the menu. Choosing a radio or checkbox item leaves it open, so people can see the result.
- Esc and a click outside close the menu.
- An item is as tall as its content: one line is 36px, and a second line, such as a description under the label, adds only its own height, with the same space above and below the text ([Forms](https://raft-design.botiverse.build/patterns/forms.md#option-height)). This holds for radio and checkbox items too.

## Content

- Write item labels in Title Case: Copy Link, Reply in Thread ([Writing](https://raft-design.botiverse.build/content/writing.md#casing)).
- Make the label of an item that opens a dialog the dialog's title ([Writing](https://raft-design.botiverse.build/content/writing.md#actions-and-their-dialogs)).

## Accessibility

- DropdownMenu wraps the Base UI Menu. It provides the `menu` role, the item roles, arrow-key movement, typeahead, Esc, and focus return to the trigger.
- Name the menu after the thing it acts on: put `aria-label` on `DropdownMenuContent` ("Message from Wug").
- Give an icon-only trigger, such as ⋯, an `aria-label` ("More Actions").
- Make every row in the menu a menu item, so arrow keys reach it. This includes each quick reaction: give it an `aria-label` ("React with thumbs up") and hide the drawing from assistive technology.
- Leave item icons decorative; the label names the action.

## Rules

- Don't color destructive items such as Delete or Stop. They open an [AlertDialog](https://raft-design.botiverse.build/components/alert-dialog.md), whose orange or red button carries the consequence ([Feedback](https://raft-design.botiverse.build/patterns/feedback.md#confirming)).
- Use the same separator between every two sections of a menu.
- Don't put a control that submits a form value in a menu; use a [Select](https://raft-design.botiverse.build/components/select.md).

### The message menu

The message menu starts with the quick reactions ([Conversation](https://raft-design.botiverse.build/patterns/conversation.md#actions); sizes in [Brand](https://raft-design.botiverse.build/foundations/brand.md#reactions)).

- End the row of quick reactions with a `smile-plus` icon button that opens every emoji.
- Put one separator between the reactions and the first action, then group the actions into sections.

## API

### DropdownMenu

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `actionsRef` | `RefObject<MenuRootActions \| null>` |  | A ref to imperative actions. - `unmount`: Manually unmounts the menu. Call this after any externally controlled closing animation finishes. - `close`: When specified, the menu can be closed imperatively. |
| `children` | `ReactNode \| PayloadChildRenderFunction<unknown>` |  | The content of the menu. This can be a regular React node or a render function that receives the `payload` of the active trigger. |
| `closeParentOnEsc` | `boolean` | `false` | When in a submenu, determines whether pressing the Escape key closes the entire menu, or only the current child menu. |
| `defaultOpen` | `boolean` | `false` | Whether the menu is initially open. |
| `defaultTriggerId` | `string` |  | ID of the trigger that the menu is associated with. This is useful in conjunction with the `defaultOpen` prop to create an initially open menu. |
| `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |
| `handle` | `BaseMenu.Handle<unknown>` |  | A handle to associate the menu with a trigger. If specified, allows external triggers to control the menu's open state. |
| `highlightItemOnHover` | `boolean` | `true` | Whether moving the pointer over items should highlight them. Disabling this prop allows CSS `:hover` to be differentiated from the `:focus` (`data-highlighted`) state. |
| `loopFocus` | `boolean` | `true` | Whether to loop keyboard focus back to the first item when the end of the list is reached while using the arrow keys. |
| `modal` | `boolean` | `true` | Determines if the menu enters a modal state when open. - `true`: user interaction is limited to the menu: document page scroll is locked and pointer interactions on outside elements are disabled. - `false`: user interaction with the rest of the document is allowed. |
| `onOpenChange` | `(open: boolean, eventDetails: BaseMenu.Root.ChangeEventDetails) => void` |  | Event handler called when the menu is opened or closed. |
| `onOpenChangeComplete` | `(open: boolean) => void` |  | Event handler called after any animations complete when the menu is opened or closed. |
| `open` | `boolean` |  | Whether the menu is currently open. |
| `orientation` | `MenuRootOrientation` | `'vertical'` | The visual orientation of the menu. Controls whether roving focus uses up/down or left/right arrow keys. |
| `triggerId` | `string` |  | ID of the trigger that the menu is associated with. This is useful in conjunction with the `open` prop to create a controlled menu. There's no need to specify this prop when the menu is uncontrolled (that is, when the `open` prop is not set). |

### DropdownMenuCheckboxItem

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `checked` | `boolean` |  | Whether the checkbox item is currently ticked. |
| `closeOnClick` | `boolean` | `false` | Whether to close the menu when the item is clicked. |
| `defaultChecked` | `boolean` | `false` | Whether the checkbox item is initially ticked. |
| `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |
| `id` | `string` |  |  |
| `label` | `string` |  | Overrides the text label to use when the item is matched during keyboard text navigation. |
| `nativeButton` | `boolean` | `false` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `true` if the rendered element is a native button. |
| `onCheckedChange` | `(checked: boolean, eventDetails: BaseMenu.CheckboxItem.ChangeEventDetails) => void` |  | Event handler called when the checkbox item is ticked or unticked. |
| `onClick` | `(event: BaseUIEvent<MouseEvent<HTMLDivElement, MouseEvent>>) => void` |  | The click handler for the menu item. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### DropdownMenuCheckboxItemIndicator

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `keepMounted` | `boolean` | `false` | Whether to keep the HTML element in the DOM when the checkbox item is not checked. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### DropdownMenuContent

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `portalProps` | `ContextMenuPortalProps` |  |  |
| `align` | `Align` | `"start"` | How to align the popup relative to the specified side. |
| `alignOffset` | `number \| OffsetFunction` | `0` | Additional offset along the alignment axis in pixels. Also accepts a function that returns the offset to read the dimensions of the anchor and positioner elements, along with its side and alignment. |
| `anchor` | `Element \| VirtualElement \| RefObject<Element \| null> \| (() => Element \| VirtualElement \| null)` |  | An element to position the popup against. By default, the popup will be positioned against the trigger. |
| `arrowPadding` | `number` | `5` | Minimum distance to maintain between the arrow and the edges of the popup. |
| `children` | `ReactNode` |  |  |
| `collisionAvoidance` | `CollisionAvoidance` |  | Determines how to handle collisions when positioning the popup. |
| `collisionBoundary` | `Boundary` | `'clipping-ancestors'` | An element or a rectangle that delimits the area that the popup is confined to. |
| `collisionPadding` | `Padding` | `5` | Additional space to maintain from the edge of the collision boundary. |
| `disableAnchorTracking` | `boolean` | `false` | Whether to disable the popup from tracking any layout shift of its positioning anchor. |
| `finalFocus` | `boolean \| RefObject<HTMLElement \| null> \| ((closeType: InteractionType) => boolean \| HTMLElement \| null \| void)` |  | Determines the element to focus when the menu is closed. |
| `id` | `string` |  |  |
| `positionMethod` | `"fixed" \| "absolute"` | `"fixed"` | Determines which CSS `position` property to use. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |
| `side` | `Side` | `"bottom"` | Which side of the anchor element to align the popup against. May automatically change to avoid collisions. |
| `sideOffset` | `number \| OffsetFunction` | `0` | Distance between the anchor and the popup in pixels. Also accepts a function that returns the distance to read the dimensions of the anchor and positioner elements, along with its side and alignment. |
| `sticky` | `boolean` | `false` | Whether to maintain the popup in the viewport after the anchor element was scrolled out of view. |

Also takes the attributes of its element.

### DropdownMenuFooter

Also takes the attributes of its element.

### DropdownMenuItem

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `closeOnClick` | `boolean` | `true` | Whether to close the menu when the item is clicked. |
| `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |
| `id` | `string` |  |  |
| `label` | `string` |  | Overrides the text label to use when the item is matched during keyboard text navigation. |
| `nativeButton` | `boolean` | `false` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `true` if the rendered element is a native button. |
| `onClick` | `(event: BaseUIEvent<MouseEvent<HTMLDivElement, MouseEvent>>) => void` |  | The click handler for the menu item. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### DropdownMenuItemCount

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `appearance` | `"outline" \| "solid" \| "soft"` |  |  |
| `variant` | `"primary" \| "accent" \| "warning" \| "danger" \| "default" \| "information" \| "muted" \| "success"` |  |  |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### DropdownMenuItemLabel

Also takes the attributes of its element.

### DropdownMenuLabel

Also takes the attributes of its element.

### DropdownMenuPopup

Also takes the attributes of its element.

### DropdownMenuPortal

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `container` | `HTMLElement \| ShadowRoot \| RefObject<HTMLElement \| ShadowRoot \| null>` |  | A parent element to render the portal element into. |
| `keepMounted` | `boolean` | `false` | Whether to keep the portal mounted in the DOM while the popup is hidden. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### DropdownMenuRadioGroup

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `ReactNode` |  | The content of the component. |
| `defaultValue` | `any` |  | The uncontrolled value of the radio item that should be initially selected. |
| `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |
| `onValueChange` | `(value: any, eventDetails: BaseMenu.RadioGroup.ChangeEventDetails) => void` |  | Function called when the selected value changes. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |
| `value` | `any` |  | The controlled value of the radio item that should be currently selected. |

Also takes the attributes of its element.

### DropdownMenuRadioItem

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `closeOnClick` | `boolean` | `false` | Whether to close the menu when the item is clicked. |
| `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |
| `id` | `string` |  |  |
| `label` | `string` |  | Overrides the text label to use when the item is matched during keyboard text navigation. |
| `nativeButton` | `boolean` | `false` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `true` if the rendered element is a native button. |
| `onClick` | `(event: BaseUIEvent<MouseEvent<HTMLDivElement, MouseEvent>>) => void` |  | The click handler for the menu item. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |
| `value` (required) | `any` |  | Value of the radio item. This is the value that will be set in the MenuRadioGroup when the item is selected. |

Also takes the attributes of its element.

### DropdownMenuRadioItemIndicator

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `keepMounted` | `boolean` | `false` | Whether to keep the HTML element in the DOM when the radio item is inactive. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### DropdownMenuSeparator

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `orientation` | `Orientation` | `'horizontal'` | The orientation of the separator. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### DropdownMenuShortcut

Also takes the attributes of its element.

### DropdownMenuSubmenu

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `actionsRef` | `RefObject<MenuRootActions \| null>` |  | A ref to imperative actions. - `unmount`: Manually unmounts the menu. Call this after any externally controlled closing animation finishes. - `close`: When specified, the menu can be closed imperatively. |
| `children` | `ReactNode` |  | The content of the submenu. |
| `closeParentOnEsc` | `boolean` | `false` | When in a submenu, determines whether pressing the Escape key closes the entire menu, or only the current child menu. |
| `defaultOpen` | `boolean` | `false` | Whether the menu is initially open. |
| `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |
| `highlightItemOnHover` | `boolean` | `true` | Whether moving the pointer over items should highlight them. Disabling this prop allows CSS `:hover` to be differentiated from the `:focus` (`data-highlighted`) state. |
| `loopFocus` | `boolean` | `true` | Whether to loop keyboard focus back to the first item when the end of the list is reached while using the arrow keys. |
| `onOpenChange` | `(open: boolean, eventDetails: BaseMenu.SubmenuRoot.ChangeEventDetails) => void` |  | Event handler called when the menu is opened or closed. |
| `onOpenChangeComplete` | `(open: boolean) => void` |  | Event handler called after any animations complete when the menu is opened or closed. |
| `open` | `boolean` |  | Whether the menu is currently open. |
| `orientation` | `MenuRootOrientation` | `'vertical'` | The visual orientation of the menu. Controls whether roving focus uses up/down or left/right arrow keys. |

### DropdownMenuSubmenuTrigger

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `closeDelay` | `number` | `0` | How long to wait before closing the menu that was opened on hover. Specified in milliseconds. |
| `delay` | `number` | `100` | How long to wait before the menu may be opened on hover. Specified in milliseconds. |
| `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |
| `id` | `string` |  |  |
| `label` | `string` |  | Overrides the text label to use when the item is matched during keyboard text navigation. |
| `nativeButton` | `boolean` | `false` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `true` if the rendered element is a native button. |
| `onClick` | `(event: BaseUIEvent<MouseEvent<HTMLDivElement, MouseEvent>>) => void` |  |  |
| `openOnHover` | `boolean` | `true` | Whether the menu should also open when the trigger is hovered. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### DropdownMenuTrigger

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `ReactNode` |  |  |
| `closeDelay` | `number` | `0` | How long to wait before closing the menu that was opened on hover. Specified in milliseconds. |
| `delay` | `number` | `100` | How long to wait before the menu may be opened on hover. Specified in milliseconds. |
| `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |
| `handle` | `BaseMenu.Handle<unknown>` |  | A handle to associate the trigger with a menu. |
| `nativeButton` | `boolean` | `true` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| `openOnHover` | `boolean` |  | Whether the menu should also open when the trigger is hovered. |
| `payload` | `{}` |  | A payload to pass to the menu when it is opened. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` | `<Button variant="outline" size="md" />` | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

## Changes

- 0.0.20 (Added): Added `ContextMenuItemLabel`, `ContextMenuFooter`, `DropdownMenuItemLabel`, and `DropdownMenuFooter` for truncatable menu labels and structured footer content.
