How to use
Dropdown is a compound menu component: Dropdown owns the open state and positioning, DropdownTrigger wraps whatever element opens it, and DropdownMenu holds DropdownItems (or DropdownSections of them). It supports single/multiple selection, nested submenus, and opt-in virtualization for long lists, on top of full keyboard navigation and typeahead.
import { Dropdown, DropdownTrigger, DropdownMenu, DropdownItem, Button } from "oks-ui";DropdownTrigger takes exactly one child — the element that opens the menu on press — and DropdownMenu holds the items. onAction fires with the activated item's key; that's usually all you need instead of an onClick per item.
<Dropdown>
<DropdownTrigger>
<Button variant="bordered">Actions</Button>
</DropdownTrigger>
<DropdownMenu aria-label="Actions" onAction={(key) => handleAction(String(key))}>
<DropdownItem key="edit">Edit</DropdownItem>
<DropdownItem key="duplicate">Duplicate</DropdownItem>
<DropdownItem key="delete" className="text-danger">Delete</DropdownItem>
</DropdownMenu>
</Dropdown>selectionMode defaults to "none" (a plain action menu). Set it to "single" or "multiple" for a menu that tracks its own selected item(s) — pair with selectedKeys/onSelectionChange to control it, or defaultSelectedKeys to just seed the initial state and let Dropdown manage it from there.
<DropdownMenu
aria-label="Sort by"
selectionMode="single"
selectedKeys={[sortKey]}
onSelectionChange={(keys) => setSortKey([...keys][0] as string)}
>
<DropdownItem key="name">Name</DropdownItem>
<DropdownItem key="date">Date</DropdownItem>
</DropdownMenu>An item with its own DropdownMenu inside it becomes a submenu, opened on hover or ArrowRight — submenuPlacement controls which side it opens on when the default (right-start) would run out of viewport space.
<DropdownItem key="export">
Export
<DropdownMenu aria-label="Export format">
<DropdownItem key="csv">CSV</DropdownItem>
<DropdownItem key="json">JSON</DropdownItem>
</DropdownMenu>
</DropdownItem>For a menu with hundreds of items (a searchable list of every country, every user in a workspace), pass virtualization={{ itemHeight }} on DropdownMenu so only the visible slice of items actually renders to the DOM. Leave it off for ordinary menus — it's specifically for lists long enough that full rendering would be the bottleneck.
<DropdownMenu aria-label="Country" items={countries} virtualization={{ itemHeight: 36 }}>
{(country) => <DropdownItem key={country.code}>{country.name}</DropdownItem>}
</DropdownMenu>placement (12 directional options) plus shouldFlip (on by default, so the menu flips to the opposite side automatically when it would otherwise run off-screen) control layout; isDismissable/isKeyboardDismissDisabled/shouldCloseOnInteractOutside control how it can be closed, and closeOnSelect (on by default) decides whether picking an item closes the menu — turn it off for a multi-select menu you want to stay open across several picks.
All available props for Dropdown. See the full component page for an interactive playground.
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | A DropdownTrigger and a DropdownMenu. |
children
ReactNode
Default: —
A DropdownTrigger and a DropdownMenu.
| Prop | Type | Default | Description |
|---|---|---|---|
placement | "bottom-start" | "bottom-end" | "top-start" | ... (12-way) | "bottom-start" | Menu position relative to the trigger. |
offset | number | 8 | Distance from the trigger. |
containerPadding | number | 8 | Minimum distance kept from the viewport edge. |
shouldFlip | boolean | true | Flips placement to stay in the viewport. |
portalContainer | Element | DocumentFragment | null | — | Custom portal mount target. |
placement
"bottom-start" | "bottom-end" | "top-start" | ... (12-way)
Default: "bottom-start"
Menu position relative to the trigger.
offset
number
Default: 8
Distance from the trigger.
containerPadding
number
Default: 8
Minimum distance kept from the viewport edge.
shouldFlip
boolean
Default: true
Flips placement to stay in the viewport.
portalContainer
Element | DocumentFragment | null
Default: —
Custom portal mount target.
| Prop | Type | Default | Description |
|---|---|---|---|
isOpen | boolean | — | Controlled open state. |
defaultOpen | boolean | false | Uncontrolled initial open state. |
isDismissable | boolean | true | Allows dismissing via outside click/Escape. |
isKeyboardDismissDisabled | boolean | false | Disables closing via Escape. |
shouldCloseOnInteractOutside | (target: Element) => boolean | — | Custom predicate for outside-interaction dismissal. |
closeOnSelect | boolean | true | Closes the menu after an item is selected. |
isOpen
boolean
Default: —
Controlled open state.
defaultOpen
boolean
Default: false
Uncontrolled initial open state.
isDismissable
boolean
Default: true
Allows dismissing via outside click/Escape.
isKeyboardDismissDisabled
boolean
Default: false
Disables closing via Escape.
shouldCloseOnInteractOutside
(target: Element) => boolean
Default: —
Custom predicate for outside-interaction dismissal.
closeOnSelect
boolean
Default: true
Closes the menu after an item is selected.
| Prop | Type | Default | Description |
|---|---|---|---|
onOpenChange | (isOpen: boolean) => void | — | Called when open state changes. |
onOpenChange
(isOpen: boolean) => void
Default: —
Called when open state changes.
| Prop | Type | Default | Description |
|---|---|---|---|
classNames | Partial<Record<"base" | "content", string>> | — | Per-slot class overrides. |
className | string | — | Class applied to the root. |
style | CSSProperties | — | Inline styles for the root. |
classNames
Partial<Record<"base" | "content", string>>
Default: —
Per-slot class overrides.
className
string
Default: —
Class applied to the root.
style
CSSProperties
Default: —
Inline styles for the root.
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactElement | — | The single element that opens the menu on press (required). |
children
ReactElement
Default: —
The single element that opens the menu on press (required).
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | ((item: T) => ReactElement) | — | Declarative DropdownItem/DropdownSection children, or a render function with items. |
items | Iterable<T> | — | Dynamic items, used with a render-function children. |
topContent / bottomContent | ReactNode | — | Content rendered above/below the item list. |
emptyContent | ReactNode | "No items." | Shown when there are no items. |
children
ReactNode | ((item: T) => ReactElement)
Default: —
Declarative DropdownItem/DropdownSection children, or a render function with items.
items
Iterable<T>
Default: —
Dynamic items, used with a render-function children.
topContent / bottomContent
ReactNode
Default: —
Content rendered above/below the item list.
emptyContent
ReactNode
Default: "No items."
Shown when there are no items.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "solid" | "bordered" | "light" | "flat" | "faded" | "shadow" | solid | Visual style variant. |
color | DropdownColor | default | Semantic color. |
hideSelectedIcon | boolean | false | Hides the selected-item checkmark. |
variant
"solid" | "bordered" | "light" | "flat" | "faded" | "shadow"
Default: solid
Visual style variant.
color
DropdownColor
Default: default
Semantic color.
hideSelectedIcon
boolean
Default: false
Hides the selected-item checkmark.
| Prop | Type | Default | Description |
|---|---|---|---|
selectionMode | "none" | "single" | "multiple" | none | Item selection behavior. |
selectedKeys | "all" | Iterable<Key> | — | Controlled selected keys. |
defaultSelectedKeys | "all" | Iterable<Key> | — | Uncontrolled initial selected keys. |
disabledKeys | Iterable<Key> | — | Keys of items to disable. |
disallowEmptySelection | boolean | false | Prevents deselecting the last selected item. |
autoFocus | boolean | "first" | "last" | false | Focuses an item automatically when the menu opens. |
hideEmptyContent | boolean | false | Hides the empty-state content entirely. |
shouldFocusWrap | boolean | false | Wraps keyboard focus from the last item to the first. |
closeOnSelect | boolean | parent Dropdown's value | Per-menu override of the root's closeOnSelect. |
virtualization | { itemHeight: number; overscan?: number } | — | Enables fixed-height virtualized rendering for long lists. |
selectionMode
"none" | "single" | "multiple"
Default: none
Item selection behavior.
selectedKeys
"all" | Iterable<Key>
Default: —
Controlled selected keys.
defaultSelectedKeys
"all" | Iterable<Key>
Default: —
Uncontrolled initial selected keys.
disabledKeys
Iterable<Key>
Default: —
Keys of items to disable.
disallowEmptySelection
boolean
Default: false
Prevents deselecting the last selected item.
autoFocus
boolean | "first" | "last"
Default: false
Focuses an item automatically when the menu opens.
hideEmptyContent
boolean
Default: false
Hides the empty-state content entirely.
shouldFocusWrap
boolean
Default: false
Wraps keyboard focus from the last item to the first.
closeOnSelect
boolean
Default: parent Dropdown's value
Per-menu override of the root's closeOnSelect.
virtualization
{ itemHeight: number; overscan?: number }
Default: —
Enables fixed-height virtualized rendering for long lists.
| Prop | Type | Default | Description |
|---|---|---|---|
disableAnimation | boolean | false | Disables the menu's open animation. |
disableAnimation
boolean
Default: false
Disables the menu's open animation.
| Prop | Type | Default | Description |
|---|---|---|---|
onAction | (key: Key) => void | — | Called when an item is activated. |
onSelectionChange | (keys) => void | — | Called when selection changes. |
onClose | () => void | — | Called when the menu requests to close. |
onAction
(key: Key) => void
Default: —
Called when an item is activated.
onSelectionChange
(keys) => void
Default: —
Called when selection changes.
onClose
() => void
Default: —
Called when the menu requests to close.
| Prop | Type | Default | Description |
|---|---|---|---|
classNames | DropdownMenuClassNames | — | Per-slot class overrides (base/list/emptyContent). |
itemClasses | Partial<Record<DropdownItemSlot, string>> | — | Per-slot class overrides applied to every item. |
classNames
DropdownMenuClassNames
Default: —
Per-slot class overrides (base/list/emptyContent).
itemClasses
Partial<Record<DropdownItemSlot, string>>
Default: —
Per-slot class overrides applied to every item.
| Prop | Type | Default | Description |
|---|---|---|---|
children / title | ReactNode | — | Item label. |
textValue | string | — | Plain-text override for typeahead/accessible naming. |
description | ReactNode | — | Secondary text below the label. |
shortcut | ReactNode | — | Keyboard shortcut hint. |
startContent / endContent | ReactNode | — | Content rendered before/after the label. |
selectedIcon | ReactNode | — | Overrides the default selected checkmark. |
children / title
ReactNode
Default: —
Item label.
textValue
string
Default: —
Plain-text override for typeahead/accessible naming.
description
ReactNode
Default: —
Secondary text below the label.
shortcut
ReactNode
Default: —
Keyboard shortcut hint.
startContent / endContent
ReactNode
Default: —
Content rendered before/after the label.
selectedIcon
ReactNode
Default: —
Overrides the default selected checkmark.
| Prop | Type | Default | Description |
|---|---|---|---|
submenuPlacement | DropdownPlacement | "right-start" | Position of a nested DropdownMenu, when this item has one. |
submenuPlacement
DropdownPlacement
Default: "right-start"
Position of a nested DropdownMenu, when this item has one.
| Prop | Type | Default | Description |
|---|---|---|---|
showDivider | boolean | false | Shows a divider below this item. |
href / target / rel / download | string | — | Renders the item as a link when href is set. |
isDisabled | boolean | false | Disables this item. |
isReadOnly | boolean | false | Not selectable, but not visually disabled. |
closeOnSelect | boolean | — | Per-item override of the menu's closeOnSelect. |
itemKey | Key | — | Explicit key, when not using items+render-function. |
showDivider
boolean
Default: false
Shows a divider below this item.
href / target / rel / download
string
Default: —
Renders the item as a link when href is set.
isDisabled
boolean
Default: false
Disables this item.
isReadOnly
boolean
Default: false
Not selectable, but not visually disabled.
closeOnSelect
boolean
Default: —
Per-item override of the menu's closeOnSelect.
itemKey
Key
Default: —
Explicit key, when not using items+render-function.
| Prop | Type | Default | Description |
|---|---|---|---|
onAction / onClose / onPress / onKeyDown / onClick | callback | — | Interaction callbacks. |
onAction / onClose / onPress / onKeyDown / onClick
callback
Default: —
Interaction callbacks.