Skip to main content
Dropdown combines a positioned popup, visual rows, keyboard navigation, focus, and dismissal. Your application owns data, filtering, fetching, pagination, and selected values. Import it from twenty-ui/components. Dropdown.Content accepts numeric offsets or functions for sideOffset and alignOffset. Offset functions receive the anchor and popup dimensions, side, and alignment. Use them to preserve an anchored field picker’s overlap when the cell is scaled.

Choose an interaction type

Set type on Dropdown.Root to match the popup’s contents. Pickers allow creation actions and selectable values in the same list, in any order. Options expose their selected state with aria-pressed; commands keep their button semantics. Omit selected on an option that navigates or applies without a selection state, so it exposes no pressed state. Give Search an accessible name. Content takes its accessible name from Trigger, or from SubmenuTrigger in a submenu. Pass aria-label on Content when the trigger shows a value, such as the selected option, or when the root has no trigger. An explicit aria-label or aria-labelledby, and a Title inside a Header, take precedence over the trigger name. The underlying Menu, Select, and Popover remain available for direct composition.

Anatomy

Content includes the portal and positioner. Use width, side, align, sideOffset, and alignOffset to control placement, and collisionPadding to keep the popup further from the edges of the viewport before it shifts or flips (5 pixels by default). Section groups rows with shared spacing, and Separator divides sections. A scrollable Section caps its height and scrolls; nest labelled sections inside it to scroll them as one list. Rows use ListItem for their appearance.
Pass custom trigger elements through render so the trigger remains one interactive element. Custom components must forward the supplied props and ref. ActionItem also accepts render for links.

Headers

Header lays out a row at the top of the popup. Put a Title in it to name the popup after the title instead of the trigger. Close closes the popup the same way an outside press does. It renders an icon button with an X by default, so give it an aria-label, or pass render and children for another control. Other controls, such as a button that returns to a previous step, can sit in the same row. Use Dropdown.Back when navigating the root’s page history.

Search and selection

Search provides the input and navigation into the result rows. Pass value and onValueChange; compute or fetch matching results in your application. Render Loading while a request is pending and Empty when no results match. Both accept caller-provided status text.
While Search holds text, the first enabled OptionItem is highlighted and Enter activates it. Action rows are never picked this way. With an empty search, Enter does nothing; ArrowDown moves focus into the results. When rows are pinned above the matches, such as the current value or a “none” row, render them only when they match the search text, so Enter picks the first real match. The popup stays open when no option matches. Filtering remains caller-owned. Actions and single selection close by default. Set multiple on the root to keep option selection open. The application still owns the selected values. closeOnClick on actions and closeOnSelect on options override those defaults, including repeated actions or asynchronous progress. Use disabled to prevent activation while retaining the row’s appearance.

Icon grids

Set columns on Section to lay out items in equal-width grid columns. The same count drives keyboard navigation: Left and Right move within a row, while Up and Down move between rows. Navigation skips disabled options and follows the reading direction. When no enabled option is left in the column, Up and Down move to the nearest enabled option of the next row. Up from the first row returns to the search field when no earlier section exists. A short final row uses its last item when the requested column is missing. Inside a submenu, the backward arrow closes the submenu when no enabled option remains to its left. Render each icon as an OptionItem with an accessible label and an icon button through render. Use indicator="none" to show selection through the button background.

Pages and submenus

Use Page for navigation that replaces the contents of the same popup. Wrap the first page in Page id="root", or choose another initial page with defaultPage. Set page on an action to navigate without closing. Back returns to the previous page and restores focus to its invoking row. Closing the dropdown resets its page history. A page can override the root’s type. For example, an action menu can open a searchable picker page or a form panel. Search receives focus when entering a searchable page; other pages focus their first row after Back.
To navigate from code, call useDropdownPage in a component rendered inside the root. It returns the current page, goToPage(page), goBack() and canGoBack. For example, an option can return to the previous page after a selection while the popup stays open:
Use Submenu with SubmenuTrigger and a nested Content for a side-by-side menu. Arrow Right enters the submenu; Arrow Left returns to its parent. Selecting a closing action closes the whole dropdown.

Independent nested pickers

Place another Dropdown.Root inside Content when a control opens its own picker, such as a sort direction selector. Selecting an inner option closes only that picker and restores focus to its trigger. Escape and outside presses dismiss one layer at a time. Use Submenu for commands that should close the entire menu tree when selected.

Panels and controlled state

Use type="panel" for form inputs. Input interaction keeps the panel open; a save action can close it. Own open and onOpenChange when another component or a shortcut needs to open the dropdown. Keep that state with the feature that coordinates the interaction. Escape dismisses the popup and restores focus to its trigger. Outside interaction also dismisses it, and the click that dismisses it doesn’t activate what it lands on. Keys pressed inside the popup stay inside it, except Ctrl or Cmd shortcuts the dropdown doesn’t handle, so page-level shortcuts keep working. Content accepts focus and portal options from Popover, including an explicit return-focus target for editor workflows.

Dismissal

Escape, a press outside the popup and Tab out of it close the dropdown. Pass onEscapeKeyDown or onInteractOutside to Root to react before it closes, and call event.preventDefault() to keep it open. event.target is the element pressed outside, or null when focus leaves with Tab. A prevented outside press still reaches the element it lands on.

Anchoring without a trigger

A root can open without a Trigger, for example as a context menu. Control it with open and onOpenChange, and pass anchor to Content: an element, a ref, or a virtual element that returns a rectangle in viewport coordinates. Give Content an aria-label, since there is no trigger to name it after. On close, focus returns to the element that was focused when the popup opened.
Right-clicking elsewhere while the menu is open moves it to the new point.

Labels

Each part owns its text: supply Search.placeholder and native accessible names, Close accessible attributes, and children for Back, Loading, and Empty. Back defaults to Back; loading and empty states have no built-in message. Item parts accept shortcutJoinLabel to replace the default then between sequential shortcut steps. Native aria-labelledby takes precedence over aria-label.

Props

boolean
Whether the popup is initially open when uncontrolled.
string
ID of the Page shown when the popup opens. Closing the popup resets the page history to it. Defaults to root.
boolean
Allows selecting several options. Options then keep the popup open and show a checkbox by default.
((event: DropdownDismissEvent) => void)
Called when Escape is about to close the popup. Call event.preventDefault() to keep it open.
((event: DropdownDismissEvent) => void)
Called when a press outside the popup, or Tab out of it, is about to close it. event.target is the element pressed outside, or null after Tab. Call event.preventDefault() to keep it open.
((open: boolean) => void)
Called with the next open state when the popup opens or closes.
boolean
Whether the popup is open. Use with onOpenChange to control it.
"menu" | "panel" | "picker"
required
Interaction model: menu for commands and links, picker for search and selectable options, or panel for forms. It sets the ARIA roles, initial focus, and keyboard navigation.
string | ((state: PopoverTriggerState) => string | undefined)
CSS class applied to the element, or a function that returns a class based on the component’s state.
number
default:"0"
How long to wait before closing the popover that was opened on hover. Specified in milliseconds.Requires the openOnHover prop.
number
default:"300"
How long to wait before the popover may be opened on hover. Specified in milliseconds.Requires the openOnHover prop.
string
ID of the trigger. In addition to being forwarded to the rendered element, it is also used to specify the active trigger for the popover in controlled mode (with the PopoverRoot triggerId prop).
boolean
default:"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 (e.g. <div>).
boolean
default:"false"
Whether the popover should also open when the trigger is hovered.
ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, PopoverTriggerState>
Allows you to replace the component’s HTML element with a different tag, or compose it with another component.Accepts a ReactElement or a function that returns the element to render.
CSSProperties | ((state: PopoverTriggerState) => CSSProperties | undefined)
Style applied to the element, or a function that returns a style object based on the component’s state.
"center" | "end" | "start"
default:"start"
Alignment of the popup along the anchor.
number | OffsetFunction
Offset in pixels along the alignment axis.
Element | VirtualElement | RefObject<Element | null> | (() => Element | VirtualElement | null) | null
Element or position the popup is anchored to. Defaults to the trigger.
string | ((state: PopoverPopupState) => string | undefined)
CSS class applied to the element, or a function that returns a class based on the component’s state.
Padding
Space in pixels kept between the popup and the edges of its collision boundary, for all sides or per side. Defaults to 5.
HTMLElement | ShadowRoot | RefObject<HTMLElement | ShadowRoot | null> | null
Element the popup is portaled into. Defaults to the theme’s portal container.
boolean | RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | void | HTMLElement | null)
Determines the element to focus when the popover is closed.
  • false: Do not move focus.
  • true: Move focus based on the default behavior (trigger or previously focused element).
  • RefObject: Move focus to the ref element.
  • function: Called with the interaction type (mouse, touch, pen, or keyboard). Return an element to focus, true to use the default behavior, null to fall back to the default behavior, or false/undefined to do nothing.
boolean | RefObject<HTMLElement | null> | ((openType: InteractionType) => boolean | void | HTMLElement | null)
Determines the element to focus when the popover is opened. By default, focus moves to the first tabbable element inside the popup, except when the popover is opened by touch — then the popup itself is focused to avoid opening the virtual keyboard.
  • false: Do not move focus.
  • true: Move focus based on the default behavior (first tabbable element or popup).
  • RefObject: Move focus to the ref element.
  • function: Called with the interaction type (mouse, touch, pen, or keyboard). Return an element to focus, true to use the default behavior, null to fall back to the default behavior, or false/undefined to do nothing.
boolean
Keeps the popup mounted in the DOM while the popover is closed.
ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, PopoverPopupState>
Allows you to replace the component’s HTML element with a different tag, or compose it with another component.Accepts a ReactElement or a function that returns the element to render.
"bottom" | "inline-end" | "inline-start" | "left" | "right" | "top"
Side of the anchor the popup is placed on.
number | OffsetFunction
default:"0"
Distance in pixels between the anchor and the popup.
CSSProperties | ((state: PopoverPopupState) => CSSProperties | undefined)
Style applied to the element, or a function that returns a style object based on the component’s state.
Width<string | number>
default:"200"
CSS width of the popup. Numbers are in pixels.
string
CSS class applied to the row.
boolean
default:"true"
Closes the dropdown after activation, along with parent menus when inside a submenu. Ignored when page is set.
"danger" | "neutral"
Color of the text and icons. danger marks a destructive action.
ReactNode
Supporting text, placed according to descriptionPlacement.
"end" | "inline"
Where the description renders: inline after the content or at the end of the row.
ReactNode
Icon rendered after the content.
boolean
default:"false"
Whether the button should be focusable when disabled.
boolean
Shows a chevron indicating that the item opens a submenu.
boolean
default:"true when render is omitted; false otherwise"
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>).
string
ID of the Page to show on activation. The popup stays open, and the row shows a chevron by default.
ReactElement<unknown, string | JSXElementConstructor<any>>
Element rendered as the row instead of a native button. Custom components must forward the supplied props and ref.
ShortcutDefinition
Keyboard shortcut keys displayed at the end of the row. Registering the shortcut is up to the application.
string
Text between sequential shortcut steps. Defaults to then.
ReactNode
Icon rendered before the content.
CSSProperties
Inline styles applied to the row.
string
CSS class applied to the row.
boolean
Closes the dropdown after selection. Defaults to true, or false when multiple is set.
"danger" | "neutral"
Color of the text and icons. danger marks a destructive action.
ReactNode
Supporting text, placed according to descriptionPlacement.
"end" | "inline"
Where the description renders: inline after the content or at the end of the row.
ReactNode
Icon rendered after the content.
boolean
default:"false"
Whether the button should be focusable when disabled.
boolean
Shows a chevron indicating that the item opens a submenu.
"check" | "checkbox" | "none"
Selection indicator: a check icon after the content, a checkbox before it, or none. Defaults to checkbox when multiple is set, otherwise check, and to none when selected is omitted.
boolean
default:"true when render is omitted; false otherwise"
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>).
(() => void)
Called when the option is activated. The application owns the selected value.
ReactElement<unknown, string | JSXElementConstructor<any>>
Element rendered as the row instead of a native button. Custom components must forward the supplied props and ref.
boolean
Whether the option is selected. Exposed as aria-checked in menus and aria-pressed in other dropdown types, or as aria-current when render is not a button, such as a link. Omit it for options that navigate or apply without a selection state.
ShortcutDefinition
Keyboard shortcut keys displayed at the end of the row. Registering the shortcut is up to the application.
string
Text between sequential shortcut steps. Defaults to then.
ReactNode
Icon rendered before the content.
CSSProperties
Inline styles applied to the row.
string | ((state: InputState) => string | undefined)
CSS class applied to the element, or a function that returns a class based on the component’s state.
string | number | readonly string[]
The default value of the input. Use when uncontrolled.
((value: string) => void)
Called with the search text on every change. The application filters the results.
ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, InputState>
Allows you to replace the component’s HTML element with a different tag, or compose it with another component.Accepts a ReactElement or a function that returns the element to render.
CSSProperties | ((state: InputState) => CSSProperties | undefined)
Style applied to the element, or a function that returns a style object based on the component’s state.
string | number | readonly string[]
The value of the input. Use when controlled.
ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, {}>
Allows you to replace the component’s HTML element with a different tag, or compose it with another component.Accepts a ReactElement or a function that returns the element to render.
string | ((state: PopoverTitleState) => string | undefined)
CSS class applied to the element, or a function that returns a class based on the component’s state.
ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, PopoverTitleState>
Allows you to replace the component’s HTML element with a different tag, or compose it with another component.Accepts a ReactElement or a function that returns the element to render.
CSSProperties | ((state: PopoverTitleState) => CSSProperties | undefined)
Style applied to the element, or a function that returns a style object based on the component’s state.
string
required
Accessible name of the close control. Required because the default control only shows an icon.
string | ((state: PopoverCloseState) => string | undefined)
CSS class applied to the element, or a function that returns a class based on the component’s state.
boolean
default:"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>).
ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, PopoverCloseState>
Allows you to replace the component’s HTML element with a different tag, or compose it with another component.Accepts a ReactElement or a function that returns the element to render.
CSSProperties | ((state: PopoverCloseState) => CSSProperties | undefined)
Style applied to the element, or a function that returns a style object based on the component’s state.
string
required
Identifier matched by defaultPage, the page prop of actions, and goToPage. The page renders only while it is current.
"menu" | "panel" | "picker"
Interaction model while the page is shown. Defaults to the root type.
string
CSS class applied to the row.
"danger" | "neutral"
Color of the text and icons. danger marks a destructive action.
ReactNode
Supporting text, placed according to descriptionPlacement.
"end" | "inline"
Where the description renders: inline after the content or at the end of the row.
ReactNode
Icon rendered after the content.
boolean
default:"false"
Whether the button should be focusable when disabled.
boolean
Shows a chevron indicating that the item opens a submenu.
boolean
default:"true when render is omitted; false otherwise"
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>).
ReactElement<unknown, string | JSXElementConstructor<any>>
Element rendered as the row instead of a native button. Custom components must forward the supplied props and ref.
ShortcutDefinition
Keyboard shortcut keys displayed at the end of the row. Registering the shortcut is up to the application.
string
Text between sequential shortcut steps. Defaults to then.
ReactNode
Icon rendered before the content.
CSSProperties
Inline styles applied to the row.
boolean
Whether the popup is initially open when uncontrolled.
string
ID of the Page shown when the popup opens. Closing the popup resets the page history to it. Defaults to root.
boolean
Allows selecting several options. Options then keep the popup open and show a checkbox by default.
((open: boolean) => void)
Called with the next open state when the popup opens or closes.
boolean
Whether the popup is open. Use with onOpenChange to control it.
"menu" | "panel" | "picker"
default:"menu"
Interaction model: menu for commands and links, picker for search and selectable options, or panel for forms. It sets the ARIA roles, initial focus, and keyboard navigation.
string
CSS class applied to the row.
number
Delay in milliseconds before a submenu opened on hover closes. Requires openOnHover. Defaults to 0.
"danger" | "neutral"
Color of the text and icons. danger marks a destructive action.
number
Delay in milliseconds before the submenu opens on hover. Requires openOnHover. Defaults to 300.
ReactNode
Supporting text, placed according to descriptionPlacement.
"end" | "inline"
Where the description renders: inline after the content or at the end of the row.
ReactNode
Icon rendered after the content.
boolean
default:"false"
Whether the button should be focusable when disabled.
boolean
default:"true"
Shows a chevron indicating that the item opens a submenu.
boolean
default:"true when render is omitted; false otherwise"
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>).
boolean
default:"true"
Also opens the submenu when the row is hovered.
ReactElement<unknown, string | JSXElementConstructor<any>>
Element rendered as the row instead of a native button. Custom components must forward the supplied props and ref.
ShortcutDefinition
Keyboard shortcut keys displayed at the end of the row. Registering the shortcut is up to the application.
string
Text between sequential shortcut steps. Defaults to then.
ReactNode
Icon rendered before the content.
CSSProperties
Inline styles applied to the row.
number
Number of grid columns. Sets the grid layout and enables horizontal arrow navigation and vertical movement by row.
ReactNode
Heading displayed above the rows. It also names the group for assistive technologies.
boolean
Caps the section height and scrolls its rows. Nest labelled sections inside it to scroll them as one list.
string
Class applied to the separator. Native div attributes and refs are also accepted.
ReactNode
Loading message announced as a polite, busy status.
string
Class applied to the status. Native div attributes and refs are also accepted.
ReactNode
Empty-state message announced as a polite status.
string
Class applied to the status. Native div attributes and refs are also accepted.