Skip to content
GitHub

Overlays

SearchableMenu

A menu of actions you can type into.

When to reach for it

A DropdownMenu past about a dozen rows stops being scannable, and nesting submenus makes it worse. This is the same list with a filter over it. Not a Command palette: that is page-level and modal; this is anchored to a control.

Examples

default

nine actions, one filter

Notes

A menu of actions you can type into.

The fourth corner of a square the system otherwise had three of, and the distinction is worth stating because reaching for the wrong one is easy:

| | few options | many options | |---|---|---| | sets a value | Select | Combobox | | runs an action | DropdownMenu | SearchableMenu |

A DropdownMenu past about a dozen rows stops being scannable, and the usual response — nesting submenus — makes it worse. This is the same list with a filter over it.

Not the same thing as Command: that is a page-level palette, usually modal and usually bound to ⌘K. This is anchored to a control, like the menu it replaces.

The rows are options inside a listbox rather than menuitems, because that is what the filtering pattern requires — the highlight moves through aria-activedescendant while focus stays in the input, and a menu cannot do that. The trade is deliberate: a menu that cannot be filtered is worse for the reader than a listbox that runs actions.

Props

SearchableMenu props
PropTypeDefaultDescription
actionsrequiredMenuAction[]
childrenrequiredReactNodeThe trigger's text.
labelrequiredstringNames the menu for assistive tech.
align'start' | 'center' | 'end''start'
classNamestring
emptyMessagestring'Nothing matches.'
searchPlaceholderstring'Filter…'

Keyboard

SearchableMenu keyboard interactions
KeyDoes
EnterSpaceOpens the menu.
Moves the highlight while focus stays in the filter.
EnterRuns the highlighted action.
EscapeCloses without running anything.

Accessibility

  • Inside a bounded frame — a device preview, an embedded console — wrap the subtree in `<OverlayContainer container={el}>`. The panel then renders into that element and collides with its edges instead of the viewport’s, and inherits the `dir` and `data-density` set there.
  • The rows are options inside a listbox rather than menuitems, because filtering requires it — the highlight moves through aria-activedescendant while focus stays in the input, and a menu cannot do that.
  • The trade is deliberate: a menu that cannot be filtered is worse for the reader than a listbox that runs actions.