Components

Menu (base-ui powered)

Menu is a family of Base UI powered components for action menus, split buttons, selects, comboboxes and searchable selects.

It is built on Base UI, which handles focus management, keyboard navigation and ARIA roles, so every menu below is accessible by default. Seeds supplies the styling and a simpler API on top.

Every example on this page is live: edit the code to try different props.

Using Menu

Menu ships from the base entry point of @sproutsocial/seeds-react-menu. Import its stylesheet once in your app, then import the components you need:

import "@sproutsocial/seeds-react-menu/base/css";
import {
ActionMenu,
MenuItem,
MenuButton,
SingleSelect,
MultiSelect,
} from "@sproutsocial/seeds-react-menu/base";
Pro tip
The live examples on this page read the components from a MenuV3 object (for example const { ActionMenu, MenuItem } = MenuV3;) and the sample book data from menuV3Data. In your own code, import them directly as shown above.

Choosing a menu

ComponentUse it for
ActionMenuA list of actions opened from a trigger, with optional groups, icons, checkbox/radio items and nested submenus.
MenuButtonA split button: a primary action plus a dropdown of related actions.
SingleSelectChoosing one option from a short or medium list.
MultiSelectChoosing several options from a short or medium list.
SingleComboboxTyping to filter a long list and choose one option.
MultiComboboxTyping to filter a long list and choose several options, shown as tokens.
SingleSearchableSelectA select with a search field inside the popup, for one option.
MultiSearchableSelectA select with a search field inside the popup, for several options.

Working with data

The select and combobox components share a data-driven API:

  • data is an array of items. Each item needs a unique id, and can set disabled: true.
  • itemToString turns an item into the text used for its accessible name, for filtering, and for the default display.
  • renderItem (optional) customizes how each option looks in the list.
  • Selection is tracked by id. Single components use selectedItemId / defaultSelectedItemId / onSelectedItemIdChange. Multi components use selectedItemIds / defaultSelectedItemIds / onSelectedItemIdsChange.
  • Labels come from wrapping the component in a FormField and passing its id through, as in every example below.

ActionMenu

ActionMenu opens a list of commands from a trigger. Use it when each item does something (compose, edit, delete) rather than choosing a value. If people are picking a value, use one of the select or combobox components below instead.

Pass any button as the trigger, and compose the list from MenuItem, MenuGroup, MenuRadioGroup, MenuSub and MenuSubTrigger.

Basic

Pass a trigger and a list of MenuItems. Each item's onClick runs when it is chosen, and the menu closes afterward.

Example:

function BasicActionMenuExample() {
const { ActionMenu, MenuItem } = MenuV3;
const action = (label) => (...args) => console.log(label, ...args);
return (
<ActionMenu trigger={<Button appearance="secondary">Open Menu</Button>}>
<MenuItem onClick={action("Compose")}>Compose</MenuItem>
<MenuItem onClick={action("View Messages")}>View Messages</MenuItem>
<MenuItem onClick={action("Analyze post")}>Analyze post</MenuItem>
</ActionMenu>
);
}

With Groups

Wrap related items in MenuGroup to give them a label. Add showSeparator to draw a divider after the group.

Example:

function WithGroupsActionMenuExample() {
const { ActionMenu, MenuGroup, MenuItem } = MenuV3;
const action = (label) => (...args) => console.log(label, ...args);
return (
<ActionMenu trigger={<Button appearance="secondary">Open Menu</Button>}>
<MenuGroup label="Actions" showSeparator>
<MenuItem onClick={action("Compose")}>Compose</MenuItem>
<MenuItem onClick={action("View Messages")}>View Messages</MenuItem>
</MenuGroup>
<MenuGroup label="Settings">
<MenuItem onClick={action("Settings")}>Settings</MenuItem>
<MenuItem onClick={action("Help")}>Help</MenuItem>
</MenuGroup>
</ActionMenu>
);
}

With Icons

Item content can be any element. Pair an Icon (with aria-hidden) with text to help people scan the menu. Give an item an href (and optionally target) to render it as a link.

Example:

function WithIconsActionMenuExample() {
const { ActionMenu, MenuItem } = MenuV3;
const action = (label) => (...args) => console.log(label, ...args);
return (
<ActionMenu trigger={<Button appearance="secondary">Open Menu</Button>}>
<MenuItem onClick={action("Compose")}>
<Box display="flex" alignItems="center" gap="300">
<Icon name="pencil-to-square-outline" aria-hidden />
Compose
</Box>
</MenuItem>
<MenuItem onClick={action("View Messages")}>
<Box display="flex" alignItems="center" gap="300">
<Icon name="envelope-outline" aria-hidden />
View Messages
</Box>
</MenuItem>
<MenuItem href="https://sproutsocial.com" target="_blank">
<Box display="flex" alignItems="center" gap="300">
<Icon name="circle-question-outline" aria-hidden />
Visit Help Center
<Icon
name="arrow-right-up"
aria-label="open in a new window"
style={{ marginLeft: "auto" }}
/>
</Box>
</MenuItem>
</ActionMenu>
);
}

With Disabled Items

Set disabled on a MenuItem to show an action that isn't available right now. Disabled items stay visible so people know the action exists.

Example:

function WithDisabledItemsActionMenuExample() {
const { ActionMenu, MenuItem } = MenuV3;
const action = (label) => (...args) => console.log(label, ...args);
return (
<ActionMenu trigger={<Button appearance="secondary">Open Menu</Button>}>
<MenuItem onClick={action("Compose")}>Compose</MenuItem>
<MenuItem onClick={action("View Messages")} disabled>
View Messages (disabled)
</MenuItem>
<MenuItem onClick={action("Analyze post")}>Analyze post</MenuItem>
</ActionMenu>
);
}

Checkbox Items

Set type="checkbox" on a MenuItem to make it a toggle. Control it with checked and onCheckedChange. Checkbox items keep the menu open when clicked, so several can be toggled at once.

Example:

function CheckboxItemsActionMenuExample() {
const { ActionMenu, MenuItem } = MenuV3;
const [highContrast, setHighContrast] = React.useState(false);
const [darkMode, setDarkMode] = React.useState(false);
const [notifications, setNotifications] = React.useState(true);
return (
<ActionMenu trigger={<Button appearance="secondary">Preferences</Button>}>
<MenuItem
type="checkbox"
checked={highContrast}
onCheckedChange={setHighContrast}
>
High contrast
</MenuItem>
<MenuItem
type="checkbox"
checked={darkMode}
onCheckedChange={setDarkMode}
>
Dark mode
</MenuItem>
<MenuItem
type="checkbox"
checked={notifications}
onCheckedChange={setNotifications}
>
Notifications
</MenuItem>
</ActionMenu>
);
}

Radio Items

Wrap items with type="radio" in a MenuRadioGroup to let people choose one option from a set. Control the group with value and onValueChange.

Example:

function RadioItemsActionMenuExample() {
const { ActionMenu, MenuGroup, MenuItem, MenuRadioGroup } = MenuV3;
const [theme, setTheme] = React.useState("system");
return (
<ActionMenu trigger={<Button appearance="secondary">Theme</Button>}>
<MenuGroup label="Appearance">
<MenuRadioGroup value={theme} onValueChange={setTheme}>
<MenuItem type="radio" value="light">
Light
</MenuItem>
<MenuItem type="radio" value="dark">
Dark
</MenuItem>
<MenuItem type="radio" value="system">
System default
</MenuItem>
</MenuRadioGroup>
</MenuGroup>
</ActionMenu>
);
}

Controlled Open

Control whether the menu is open with open and onOpenChange. Use this when something else on the page needs to open or close the menu.

Example:

function ControlledOpenActionMenuExample() {
const { ActionMenu, MenuItem } = MenuV3;
const action = (label) => (...args) => console.log(label, ...args);
const [open, setOpen] = React.useState(false);
return (
<Box>
<Button appearance="primary" onClick={() => setOpen(true)} mb={300}>
Open programmatically
</Button>
<ActionMenu
trigger={<Button appearance="secondary">Menu</Button>}
open={open}
onOpenChange={(isOpen) => {
setOpen(isOpen);
}}
>
<MenuItem onClick={action("Compose")}>Compose</MenuItem>
<MenuItem onClick={action("View Messages")}>View Messages</MenuItem>
<MenuItem onClick={action("Analyze post")}>Analyze post</MenuItem>
</ActionMenu>
</Box>
);
}

Nested Submenu

Use MenuSub with a MenuSubTrigger to open a second-level menu.

Example:

function NestedSubmenuActionMenuExample() {
const { ActionMenu, MenuItem, MenuSub, MenuSubTrigger } = MenuV3;
const action = (label) => (...args) => console.log(label, ...args);
return (
<ActionMenu trigger={<Button appearance="secondary">Open Menu</Button>}>
<MenuItem onClick={action("Compose")}>Compose</MenuItem>
<MenuItem onClick={action("View Messages")}>View Messages</MenuItem>
<MenuSub trigger={<MenuSubTrigger>Share</MenuSubTrigger>}>
<MenuItem onClick={action("Share to Twitter")}>Twitter</MenuItem>
<MenuItem onClick={action("Share to LinkedIn")}>LinkedIn</MenuItem>
<MenuSub trigger={<MenuSubTrigger>More platforms</MenuSubTrigger>}>
<MenuItem onClick={action("Share to Facebook")}>Facebook</MenuItem>
<MenuItem onClick={action("Share to Instagram")}>Instagram</MenuItem>
</MenuSub>
</MenuSub>
<MenuItem onClick={action("Delete")} disabled>
Delete
</MenuItem>
</ActionMenu>
);
}

Mixed Item Types

Action, checkbox and radio items can be combined in one menu. Use groups and separators to keep each kind of item distinct.

Example:

function MixedItemTypesActionMenuExample() {
const { ActionMenu, MenuGroup, MenuItem, MenuRadioGroup } = MenuV3;
const action = (label) => (...args) => console.log(label, ...args);
const [notifications, setNotifications] = React.useState(true);
const [theme, setTheme] = React.useState("system");
return (
<ActionMenu trigger={<Button appearance="secondary">Settings</Button>}>
<MenuGroup label="Actions" showSeparator>
<MenuItem onClick={action("Edit profile")}>Edit profile</MenuItem>
<MenuItem onClick={action("Manage team")}>Manage team</MenuItem>
</MenuGroup>
<MenuGroup label="Preferences" showSeparator>
<MenuItem
type="checkbox"
checked={notifications}
onCheckedChange={setNotifications}
>
Email notifications
</MenuItem>
</MenuGroup>
<MenuGroup label="Appearance">
<MenuRadioGroup value={theme} onValueChange={setTheme}>
<MenuItem type="radio" value="light">
Light
</MenuItem>
<MenuItem type="radio" value="dark">
Dark
</MenuItem>
<MenuItem type="radio" value="system">
System
</MenuItem>
</MenuRadioGroup>
</MenuGroup>
</ActionMenu>
);
}

MenuButton is a split button: a main action with a chevron next to it that opens related actions. Use it when one action is the most common but people sometimes need an alternative, like "Save as draft" next to "Save and activate".

Pass the dropdown's MenuItems through actionMenu. menuButtonAriaLabel is required, because the chevron button has no visible text.

Basic

The main button's onClick runs the primary action. The chevron opens the items passed in actionMenu.

Example:

function BasicMenuButtonExample() {
const { MenuItem, MenuButton } = MenuV3;
const action = (label) => (...args) => console.log(label, ...args);
return (
<MenuButton
menuButtonAriaLabel="More compose options"
actionMenu={
<>
<MenuItem onClick={action("Compose")}>Compose</MenuItem>
<MenuItem onClick={action("View Messages")}>View Messages</MenuItem>
<MenuItem onClick={action("Analyze post")}>Analyze post</MenuItem>
</>
}
onClick={action("Open Menu")}
>
Open Menu
</MenuButton>
);
}

Primary

Use appearance="primary" for the main call to action. In this example the dropdown changes which action the main button performs, and the button label updates to match.

Example:

function PrimaryMenuButtonExample() {
const { MenuItem, MenuButton } = MenuV3;
const action = (label) => (...args) => console.log(label, ...args);
// The dropdown selects the action; the main button reflects and performs it.
const actions = [
{ label: "Save as Draft", run: action("Save as Draft") },
{ label: "Save and Activate", run: action("Save and Activate") },
];
const [selected, setSelected] = React.useState(actions[0]);
return (
<MenuButton
appearance="primary"
menuButtonAriaLabel="More save options"
actionMenu={
<>
{actions.map((a) => (
<MenuItem key={a.label} onClick={() => setSelected(a)}>
{a.label}
</MenuItem>
))}
</>
}
onClick={(e) => selected.run(e)}
>
{selected.label}
</MenuButton>
);
}

Secondary

Use appearance="secondary" when the split button is not the main action on the page.

Example:

function SecondaryMenuButtonExample() {
const { MenuItem, MenuButton } = MenuV3;
const action = (label) => (...args) => console.log(label, ...args);
return (
<MenuButton
appearance="secondary"
menuButtonAriaLabel="More actions"
actionMenu={
<>
<MenuItem onClick={action("Compose")}>Compose</MenuItem>
<MenuItem onClick={action("View Messages")}>View Messages</MenuItem>
</>
}
onClick={action("Open Menu")}
>
Open Menu
</MenuButton>
);
}

Disabled

Set disabled to disable both halves of the split button.

Example:

function DisabledMenuButtonExample() {
const { MenuItem, MenuButton } = MenuV3;
const action = (label) => (...args) => console.log(label, ...args);
return (
<MenuButton
appearance="primary"
disabled
menuButtonAriaLabel="More actions"
actionMenu={
<>
<MenuItem onClick={action("Compose")}>Compose</MenuItem>
<MenuItem onClick={action("View Messages")}>View Messages</MenuItem>
</>
}
onClick={action("Open Menu")}
>
Open Menu
</MenuButton>
);
}

SingleSelect

SingleSelect lets people choose one option from a list. Use it for short to medium lists where typing to search isn't needed. For long lists, use SingleCombobox or SingleSearchableSelect.

Pass your items as data, along with an itemToString function. Every item needs a unique id. Wrap the select in a FormField so it gets a visible label.

Basic

Pass your items as data and an itemToString function. placeholder is shown until something is selected.

Example:

function BasicSingleSelectExample() {
const { SingleSelect } = MenuV3;
const { books, itemToString } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book">
{({ id }) => (
<SingleSelect
id={id}
data={books}
itemToString={itemToString}
placeholder="Select a book"
/>
)}
</FormField>
</div>
);
}

Invalid

Pass isInvalid to show the error state. Pair it with an error on the surrounding FormField so people know what to fix.

Example:

function InvalidSingleSelectExample() {
const { SingleSelect } = MenuV3;
const { books, itemToString } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book" isInvalid error="Please pick a book.">
{({ id, isInvalid }) => (
<SingleSelect
id={id}
isInvalid={isInvalid}
data={books}
itemToString={itemToString}
placeholder="Select a book"
/>
)}
</FormField>
</div>
);
}

Empty

A select with no data. Disable the select when there is nothing to choose from, rather than opening an empty list.

Example:

function EmptySingleSelectExample() {
const { SingleSelect } = MenuV3;
const { itemToString } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book">
{({ id }) => (
<SingleSelect
id={"placeholder"}
disabled
data={[]}
itemToString={() => ""}
placeholder=""
/>
)}
</FormField>
</div>
);
}

Disabled

Set disabled to prevent people from opening the select.

Example:

function DisabledSingleSelectExample() {
const { SingleSelect } = MenuV3;
const { books, itemToString } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book">
{({ id }) => (
<SingleSelect
id={id}
data={books}
itemToString={itemToString}
placeholder="Select a book"
disabled
/>
)}
</FormField>
</div>
);
}

Include placeholder item

Set includePlaceholderItem to add the placeholder as the first option in the list, so people can clear their choice by selecting it again.

Example:

function IncludePlaceholderItemSingleSelectExample() {
const { SingleSelect } = MenuV3;
const { books, itemToString } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book">
{({ id }) => (
<SingleSelect
id={id}
data={books}
itemToString={itemToString}
placeholder="Select a book"
includePlaceholderItem
/>
)}
</FormField>
</div>
);
}

Custom render item

Use renderItem to control how each option looks in the list. Here each book shows its title in bold with the author beside it. itemToString is still used for the accessible name and for filtering.

Example:

function CustomRenderItemSingleSelectExample() {
const { SingleSelect } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book">
{({ id }) => (
<SingleSelect
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select a book"
/>
)}
</FormField>
</div>
);
}

Radio

Set inputType="radio" to show a radio indicator next to each option instead of the default check icon.

Example:

function RadioSingleSelectExample() {
const { SingleSelect } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book">
{({ id }) => (
<SingleSelect
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select a book"
inputType="radio"
/>
)}
</FormField>
</div>
);
}

Grouped

Pass a groupBy function to organize options under headings. Every item that returns the same string is placed in the same group.

Example:

function GroupedSingleSelectExample() {
const { SingleSelect } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book">
{({ id }) => (
<SingleSelect
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select a book"
groupBy={(book) => book.author}
/>
)}
</FormField>
</div>
);
}

Default selected

Use defaultSelectedItemId to preselect an item while letting the component manage selection itself (uncontrolled).

Example:

function DefaultSelectedSingleSelectExample() {
const { SingleSelect } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book">
{({ id }) => (
<SingleSelect
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select a book"
defaultSelectedItemId="book-3"
/>
)}
</FormField>
</div>
);
}

Controlled

Control the selection with selectedItemId and onSelectedItemIdChange. Most single selection menus should control the selection state, so the selected value can be saved, validated or reset by your app.

Example:

function ControlledSingleSelectExample() {
const { SingleSelect } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
const [selectedId, setSelectedId] = React.useState("book-1");
const selected = books.find((b) => b.id === selectedId);
return (
<div
style={{
display: "flex",
flexDirection: "column",
gap: 16,
widows: "300px",
}}
>
<FormField label="Pick a book">
{({ id }) => (
<SingleSelect
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select a book"
selectedItemId={selectedId}
onSelectedItemIdChange={(id) => setSelectedId(id)}
/>
)}
</FormField>
<div style={{ fontSize: 13 }}>
Selected: <strong>{selected?.title ?? "none"}</strong>
</div>
<div style={{ display: "flex", gap: 8 }}>
<button onClick={() => setSelectedId(null)}>Clear</button>
<button onClick={() => setSelectedId("book-5")}>Select 1984</button>
</div>
</div>
);
}

Custom renderSelection

Use renderSelection to change how the selected item appears in the closed trigger, independently of how options look in the list.

Example:

function CustomSelectionSingleSelectExample() {
const { SingleSelect } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book">
{({ id }) => (
<SingleSelect
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select a book"
renderSelection={(item) => (
<span>
<span style={{ fontWeight: "bold" }}>{item.title}</span>
<span
style={{ fontSize: "11px", color: "#888", marginLeft: "6px" }}
>
by {item.author}
</span>
</span>
)}
/>
)}
</FormField>
</div>
);
}

Disabled items

Set disabled: true on any item in data to keep it visible but unselectable.

Example:

function DisabledItemsSingleSelectExample() {
const { SingleSelect } = MenuV3;
const { booksWithDisabled, itemToString } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book">
{({ id }) => (
<SingleSelect
id={id}
data={booksWithDisabled}
itemToString={itemToString}
placeholder="Select a book"
/>
)}
</FormField>
</div>
);
}

Inline (fullWidth=false)

By default the trigger fills its container. Set fullWidth={false} to size the trigger to its content, which is useful inline with text or in toolbars.

Example:

function InlineSingleSelectExample() {
const { SingleSelect } = MenuV3;
const { books, itemToString } = menuV3Data;
return (
<FormField label="Pick a book">
{({ id }) => (
<SingleSelect
id={id}
data={books}
itemToString={itemToString}
placeholder="Select a book"
fullWidth={false}
/>
)}
</FormField>
);
}

MultiSelect

MultiSelect lets people choose several options from a list, with a checkbox next to each one. Use it for short to medium lists where typing to search isn't needed.

Selection is tracked by item id through selectedItemIds / defaultSelectedItemIds.

Basic

Pass your items as data and an itemToString function. People can check as many options as they need, and the menu stays open between choices.

Example:

function BasicMultiSelectExample() {
const { MultiSelect } = MenuV3;
const { books, itemToString } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiSelect
id={id}
data={books}
itemToString={itemToString}
placeholder="Select books..."
/>
)}
</FormField>
</div>
);
}

Invalid

Pass isInvalid to show the error state. Pair it with an error on the surrounding FormField so people know what to fix.

Example:

function InvalidMultiSelectExample() {
const { MultiSelect } = MenuV3;
const { books, itemToString } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField
label="Pick books"
isInvalid
error="Please pick at least one book."
>
{({ id, isInvalid }) => (
<MultiSelect
id={id}
isInvalid={isInvalid}
data={books}
itemToString={itemToString}
placeholder="Select books..."
/>
)}
</FormField>
</div>
);
}

Custom render item

Use renderItem to control how each option looks in the list. Here each book shows its title in bold with the author beside it. itemToString is still used for the accessible name and for filtering.

Example:

function CustomRenderItemMultiSelectExample() {
const { MultiSelect } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiSelect
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select books..."
/>
)}
</FormField>
</div>
);
}

Grouped

Pass a groupBy function to organize options under headings. Every item that returns the same string is placed in the same group.

Example:

function GroupedMultiSelectExample() {
const { MultiSelect } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiSelect
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select books..."
groupBy={(book) => book.author}
/>
)}
</FormField>
</div>
);
}

Default selected

Use defaultSelectedItemIds to preselect items while letting the component manage selection itself (uncontrolled).

Example:

function DefaultSelectedMultiSelectExample() {
const { MultiSelect } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiSelect
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select books..."
defaultSelectedItemIds={["book-1", "book-3"]}
/>
)}
</FormField>
</div>
);
}

Controlled

Control the selection with selectedItemIds and onSelectedItemIdsChange. Control the selection when your app needs to save, validate or reset the chosen items.

Example:

function ControlledMultiSelectExample() {
const { MultiSelect } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
const [selectedIds, setSelectedIds] = React.useState(["book-2"]);
const selected = books.filter((b) => selectedIds.includes(b.id));
return (
<div
style={{
width: "300px",
display: "flex",
flexDirection: "column",
gap: 16,
}}
>
<FormField label="Pick books">
{({ id }) => (
<MultiSelect
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select books..."
selectedItemIds={selectedIds}
onSelectedItemIdsChange={(ids) => setSelectedIds(ids)}
/>
)}
</FormField>
<div style={{ fontSize: 13 }}>
Selected:{" "}
<strong>
{selected.length > 0
? selected.map((b) => b.title).join(", ")
: "none"}
</strong>
</div>
<div style={{ display: "flex", gap: 8 }}>
<button onClick={() => setSelectedIds([])}>Clear all</button>
<button onClick={() => setSelectedIds(["book-5", "book-7"])}>
Select 1984 &amp; Meditations
</button>
</div>
</div>
);
}

Custom renderSelection

Use renderSelection to replace how the selected items are summarized in the closed trigger, for example as a count or a comma-separated list.

Example:

function CustomSelectionMultiSelectExample() {
const { MultiSelect } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiSelect
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select books..."
renderSelection={(selected) => {
if (selected.length === 0)
return <span style={{ color: "#888" }}>Select books...</span>;
const first = itemToString(selected[0]);
const extra =
selected.length > 1 ? ` (+${selected.length - 1})` : "";
return (
<span>
{first}
{extra}
</span>
);
}}
/>
)}
</FormField>
</div>
);
}

Disabled items

Set disabled: true on any item in data to keep it visible but unselectable.

Example:

function DisabledItemsMultiSelectExample() {
const { MultiSelect } = MenuV3;
const { books, booksWithDisabled, itemToString } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiSelect
id={id}
data={booksWithDisabled}
itemToString={itemToString}
placeholder="Select books..."
/>
)}
</FormField>
</div>
);
}

Remove selected items

Set removeSelectedItems to hide items from the list once they are selected. They appear only as selections until they are removed.

Example:

function RemoveSelectedItemsMultiSelectExample() {
const { MultiSelect } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiSelect
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select books..."
removeSelectedItems
/>
)}
</FormField>
</div>
);
}

Inline (fullWidth=false)

By default the trigger fills its container. Set fullWidth={false} to size the trigger to its content, which is useful inline with text or in toolbars.

Example:

function InlineMultiSelectExample() {
const { MultiSelect } = MenuV3;
const { books, itemToString } = menuV3Data;
return (
<FormField label="Pick books">
{({ id }) => (
<MultiSelect
id={id}
data={books}
itemToString={itemToString}
placeholder="Select books..."
fullWidth={false}
/>
)}
</FormField>
);
}

SingleCombobox

SingleCombobox is a text input that filters a list as people type, and lets them choose one option. Use it for long lists or when people already know what they are looking for.

Basic

Pass your items as data and an itemToString function. Typing filters the list by each item's string value.

Example:

function BasicSingleComboboxExample() {
const { SingleCombobox } = MenuV3;
const { books, itemToString } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book">
{({ id }) => (
<SingleCombobox
id={id}
data={books}
itemToString={itemToString}
placeholder="Search books..."
/>
)}
</FormField>
</div>
);
}

Invalid

Pass isInvalid to show the error state. Pair it with an error on the surrounding FormField so people know what to fix.

Example:

function InvalidSingleComboboxExample() {
const { SingleCombobox } = MenuV3;
const { books, itemToString } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book" isInvalid error="Please pick a book.">
{({ id, isInvalid }) => (
<SingleCombobox
id={id}
isInvalid={isInvalid}
data={books}
itemToString={itemToString}
placeholder="Search books..."
/>
)}
</FormField>
</div>
);
}

Custom render item

Use renderItem to control how each option looks in the list. Here each book shows its title in bold with the author beside it. itemToString is still used for the accessible name and for filtering.

Example:

function CustomRenderItemSingleComboboxExample() {
const { SingleCombobox } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book">
{({ id }) => (
<SingleCombobox
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Search books..."
/>
)}
</FormField>
</div>
);
}

Radio

Set inputType="radio" to show a radio indicator next to each option instead of the default check icon.

Example:

function RadioSingleComboboxExample() {
const { SingleCombobox } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book">
{({ id }) => (
<SingleCombobox
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Search books..."
inputType="radio"
/>
)}
</FormField>
</div>
);
}

Grouped

Pass a groupBy function to organize options under headings. Every item that returns the same string is placed in the same group.

Example:

function GroupedSingleComboboxExample() {
const { SingleCombobox } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book">
{({ id }) => (
<SingleCombobox
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Search books..."
groupBy={(book) => book.author}
/>
)}
</FormField>
</div>
);
}

Default selected

Use defaultSelectedItemId to preselect an item while letting the component manage selection itself (uncontrolled).

Example:

function DefaultSelectedSingleComboboxExample() {
const { SingleCombobox } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book">
{({ id }) => (
<SingleCombobox
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Search books..."
defaultSelectedItemId="book-3"
/>
)}
</FormField>
</div>
);
}

Controlled

Control the selection with selectedItemId and onSelectedItemIdChange. Most single selection menus should control the selection state, so the selected value can be saved, validated or reset by your app.

Example:

function ControlledSingleComboboxExample() {
const { SingleCombobox } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
const [selectedId, setSelectedId] = React.useState("book-1");
const selected = books.find((b) => b.id === selectedId);
return (
<div
style={{
display: "flex",
flexDirection: "column",
gap: 16,
width: "300px",
}}
>
<FormField label="Pick a book">
{({ id }) => (
<SingleCombobox
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Search books..."
selectedItemId={selectedId}
onSelectedItemIdChange={(id) => setSelectedId(id)}
/>
)}
</FormField>
<div style={{ fontSize: 13 }}>
Selected: <strong>{selected?.title ?? "none"}</strong>
</div>
<div style={{ display: "flex", gap: 8 }}>
<button onClick={() => setSelectedId(null)}>Clear</button>
<button onClick={() => setSelectedId("book-5")}>Select 1984</button>
</div>
</div>
);
}

Disabled items

Set disabled: true on any item in data to keep it visible but unselectable.

Example:

function DisabledItemsSingleComboboxExample() {
const { SingleCombobox } = MenuV3;
const { books, booksWithDisabled, itemToString } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book">
{({ id }) => (
<SingleCombobox
id={id}
data={booksWithDisabled}
itemToString={itemToString}
placeholder="Search books..."
/>
)}
</FormField>
</div>
);
}

Virtualized

Set isVirtualized for long lists. Only the rows in view are rendered, so lists with thousands of items stay fast. This example has 1,000 books.

Example:

function VirtualizedSingleComboboxExample() {
const { SingleCombobox } = MenuV3;
const { books, largeBookSet, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book">
{({ id }) => (
<SingleCombobox
id={id}
data={largeBookSet}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Search books..."
isVirtualized
/>
)}
</FormField>
</div>
);
}

Inline (fullWidth=false)

By default the trigger fills its container. Set fullWidth={false} to size the trigger to its content, which is useful inline with text or in toolbars.

Example:

function InlineSingleComboboxExample() {
const { SingleCombobox } = MenuV3;
const { books, itemToString } = menuV3Data;
return (
<FormField label="Pick a book">
{({ id }) => (
<SingleCombobox
id={id}
data={books}
itemToString={itemToString}
placeholder="Search books..."
fullWidth={false}
/>
)}
</FormField>
);
}

Custom search (title + author)

Pass a filter function to change how typed text matches items. This example matches on the author as well as the title. Pass filter={null} to turn filtering off, for example when results come from the server.

Example:

function CustomSearchSingleComboboxExample() {
const { SingleCombobox } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book">
{({ id }) => (
<SingleCombobox
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Search by title or author..."
filter={(item, query) => {
const book = item;
const q = query.toLowerCase();
return (
book.title.toLowerCase().includes(q) ||
book.author.toLowerCase().includes(q)
);
}}
/>
)}
</FormField>
</div>
);
}

MultiCombobox

MultiCombobox is a text input that filters a list as people type, and lets them choose several options. Selections appear as removable tokens in the input. It supports grouping, select all, virtualization, loading states and creating new items.

Basic

Pass your items as data and an itemToString function. Each selected item becomes a token in the input. Remove a token with its × button.

Example:

function BasicMultiComboboxExample() {
const { MultiCombobox } = MenuV3;
const { books, itemToString } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiCombobox
id={id}
data={books}
itemToString={itemToString}
placeholder="Search books..."
/>
)}
</FormField>
</div>
);
}

Custom render item

Use renderItem to control how each option looks in the list. Here each book shows its title in bold with the author beside it. itemToString is still used for the accessible name and for filtering.

Example:

function CustomRenderItemMultiComboboxExample() {
const { MultiCombobox } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiCombobox
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Search books..."
/>
)}
</FormField>
</div>
);
}

Grouped

Pass a groupBy function to organize options under headings. Every item that returns the same string is placed in the same group.

Example:

function GroupedMultiComboboxExample() {
const { MultiCombobox } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiCombobox
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Search books..."
groupBy={(book) => book.author}
/>
)}
</FormField>
</div>
);
}

Default selected

Use defaultSelectedItemIds to preselect items while letting the component manage selection itself (uncontrolled).

Example:

function DefaultSelectedMultiComboboxExample() {
const { MultiCombobox } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiCombobox
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Search books..."
defaultSelectedItemIds={["book-1", "book-3"]}
/>
)}
</FormField>
</div>
);
}

Controlled

Control the selection with selectedItemIds and onSelectedItemIdsChange, and the typed text with inputValue and onInputValueChange. Control both when your app needs to save, validate or reset them.

Example:

function ControlledMultiComboboxExample() {
const { MultiCombobox } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
const [selectedIds, setSelectedIds] = React.useState(["book-2"]);
const [inputValue, setInputValue] = React.useState("");
const selected = books.filter((b) => selectedIds.includes(b.id));
return (
<div
style={{
width: "300px",
display: "flex",
flexDirection: "column",
gap: 16,
}}
>
<FormField label="Pick books">
{({ id }) => (
<MultiCombobox
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Search books..."
selectedItemIds={selectedIds}
onSelectedItemIdsChange={(ids) => setSelectedIds(ids)}
inputValue={inputValue}
onInputValueChange={setInputValue}
/>
)}
</FormField>
<div style={{ fontSize: 13 }}>
Input: <strong>"{inputValue}"</strong>
</div>
<div style={{ fontSize: 13 }}>
Selected:{" "}
<strong>
{selected.length > 0
? selected.map((b) => b.title).join(", ")
: "none"}
</strong>
</div>
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
<button onClick={() => setSelectedIds([])}>Clear all</button>
<button onClick={() => setSelectedIds(["book-5", "book-7"])}>
Select 1984 &amp; Meditations
</button>
<button onClick={() => setInputValue("War")}>
Set input to "War"
</button>
</div>
</div>
);
}

Disabled items

Set disabled: true on any item in data to keep it visible but unselectable.

Example:

function DisabledItemsMultiComboboxExample() {
const { MultiCombobox } = MenuV3;
const { books, booksWithDisabled, itemToString } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiCombobox
id={id}
data={booksWithDisabled}
itemToString={itemToString}
placeholder="Search books..."
/>
)}
</FormField>
</div>
);
}

Selectable headings

With groupBy and selectHeadings, each group heading becomes a checkbox that selects or clears every item in that group.

Example:

function SelectableHeadingsMultiComboboxExample() {
const { MultiCombobox } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiCombobox
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Search books..."
groupBy={(book) => book.author}
selectHeadings
/>
)}
</FormField>
</div>
);
}

Disabled group headings

With selectHeadings, use isGroupHeaderDisabled to turn off the heading checkbox for specific groups.

Example:

function DisabledGroupHeadingsMultiComboboxExample() {
const { MultiCombobox } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiCombobox
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Search books..."
groupBy={(book) => book.author}
selectHeadings
isGroupHeaderDisabled={(groupName) => groupName === "Harper Lee"}
/>
)}
</FormField>
</div>
);
}

Select all

Set selectAll to add a "Select all" option at the top of the list. Pass a string instead of true to change its label.

Example:

function SelectAllMultiComboboxExample() {
const { MultiCombobox } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "600px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiCombobox
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Search books..."
selectHeadings
selectAll
/>
)}
</FormField>
</div>
);
}

Select all with grouping

selectAll can be combined with groupBy and selectHeadings, giving people a way to select everything, a whole group, or individual items.

Example:

function SelectAllWithGroupingMultiComboboxExample() {
const { MultiCombobox } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "600px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiCombobox
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Search books..."
groupBy={(book) => book.author}
selectHeadings
selectAll
/>
)}
</FormField>
</div>
);
}

Select all (localized label)

Pass a string to selectAll to change the label of the select all option. Always pass a translated label in the product.

Example:

function SelectAllLocalizedMultiComboboxExample() {
const { MultiCombobox } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "600px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiCombobox
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Search books..."
selectAll="Tout sélectionner"
/>
)}
</FormField>
</div>
);
}

Virtualized

Set isVirtualized for long lists. Only the rows in view are rendered, so lists with thousands of items stay fast. This example has 1,000 books.

Example:

function VirtualizedMultiComboboxExample() {
const { MultiCombobox } = MenuV3;
const { books, largeBookSet, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiCombobox
id={id}
data={largeBookSet}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Search books..."
selectHeadings
isVirtualized
/>
)}
</FormField>
</div>
);
}

Max tokens

Set maxTokens to limit how many tokens are shown in the input. The rest are collapsed into a "+N" count, which keeps the input from growing too tall.

Example:

function MaxTokensMultiComboboxExample() {
const { MultiCombobox } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiCombobox
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Search books..."
defaultSelectedItemIds={[
"book-1",
"book-2",
"book-3",
"book-4",
"book-5",
]}
maxTokens={2}
/>
)}
</FormField>
</div>
);
}

Large book set with max tokens

maxTokens, selectAll and isVirtualized together handle very large lists where people may select hundreds of items.

Example:

function LargeBookSetWithMaxTokensMultiComboboxExample() {
const { MultiCombobox } = MenuV3;
const { books, largeBookSet, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiCombobox
id={id}
data={largeBookSet}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Search books..."
isVirtualized
selectAll
maxTokens={5}
/>
)}
</FormField>
</div>
);
}

Loading

Set isLoading while results are being fetched. The list shows a loading message (customizable with loadingText) instead of options.

Example:

function LoadingMultiComboboxExample() {
const { MultiCombobox } = MenuV3;
const { books, itemToString } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiCombobox
id={id}
data={[]}
itemToString={itemToString}
placeholder="Search books..."
isLoading
/>
)}
</FormField>
</div>
);
}

Loading (toggle)

Toggle isLoading to see how the menu moves between the loading state and loaded results, as it would during a real fetch.

Example:

function LoadingWithToggleMultiComboboxExample() {
const { MultiCombobox } = MenuV3;
const { books, itemToString } = menuV3Data;
const [isLoading, setIsLoading] = React.useState(true);
const data = isLoading ? [] : books;
return (
<div
style={{
width: "300px",
display: "flex",
flexDirection: "column",
gap: 16,
}}
>
<FormField label="Pick books">
{({ id }) => (
<MultiCombobox
id={id}
data={data}
itemToString={itemToString}
placeholder="Search books..."
isLoading={isLoading}
/>
)}
</FormField>
<button onClick={() => setIsLoading((v) => !v)}>
{isLoading ? "Finish loading" : "Set loading"}
</button>
</div>
);
}

Remove selected items

Set removeSelectedItems to hide items from the list once they are selected. They appear only as selections until they are removed.

Example:

function RemoveSelectedItemsMultiComboboxExample() {
const { MultiCombobox } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiCombobox
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Search books..."
removeSelectedItems
/>
)}
</FormField>
</div>
);
}

Virtualized + select all

Virtualization, grouped headings and select all can be combined, which keeps very large grouped lists usable.

Example:

function VirtualizedSelectAllMultiComboboxExample() {
const { MultiCombobox } = MenuV3;
const { books, largeBookSet, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiCombobox
id={id}
data={largeBookSet}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Search books..."
groupBy={(book) => book.author}
selectHeadings
selectAll
isVirtualized
/>
)}
</FormField>
</div>
);
}

Inline (fullWidth=false)

By default the trigger fills its container. Set fullWidth={false} to size the trigger to its content, which is useful inline with text or in toolbars.

Example:

function InlineMultiComboboxExample() {
const { MultiCombobox } = MenuV3;
const { books, itemToString } = menuV3Data;
return (
<FormField label="Pick books">
{({ id }) => (
<MultiCombobox
id={id}
data={books}
itemToString={itemToString}
placeholder="Search books..."
fullWidth={false}
/>
)}
</FormField>
);
}

Custom search (title + author)

Pass a filter function to change how typed text matches items. This example matches on the author as well as the title. Pass filter={null} to turn filtering off, for example when results come from the server.

Example:

function CustomSearchMultiComboboxExample() {
const { MultiCombobox } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiCombobox
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Search by title or author..."
filter={(item, query) => {
const book = item;
const q = query.toLowerCase();
return (
book.title.toLowerCase().includes(q) ||
book.author.toLowerCase().includes(q)
);
}}
/>
)}
</FormField>
</div>
);
}

Creatable

Set creatable to let people add an option that isn't in the list. When they choose the create option, onCreate receives the typed text and must return the new item. Add that item to your data.

Example:

function CreatableMultiComboboxExample() {
const { MultiCombobox } = MenuV3;
const { books, itemToString } = menuV3Data;
const [bookList, setBookList] = React.useState(books);
return (
<div style={{ width: "300px" }}>
<FormField label="Pick or create books">
{({ id }) => (
<MultiCombobox
id={id}
data={bookList}
itemToString={itemToString}
placeholder="Search or create..."
isVirtualized
creatable
onCreate={(value) => {
const newBook = {
id: `book-custom-${value.toLowerCase().replace(/\s+/g, "-")}`,
title: value,
author: "Unknown",
};
setBookList((prev) => [...prev, newBook]);
return newBook;
}}
/>
)}
</FormField>
</div>
);
}

Invalid

Pass isInvalid to show the error state. Pair it with an error on the surrounding FormField so people know what to fix.

Example:

function InvalidMultiComboboxExample() {
const { MultiCombobox } = MenuV3;
const { books, itemToString } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField
label="Pick books"
isInvalid
error="Please pick at least one book."
>
{({ id, isInvalid }) => (
<MultiCombobox
id={id}
isInvalid={isInvalid}
data={books}
itemToString={itemToString}
placeholder="Search books..."
/>
)}
</FormField>
</div>
);
}

Required

Set required to mark the field as required for forms and assistive technology.

Example:

function RequiredMultiComboboxExample() {
const { MultiCombobox } = MenuV3;
const { books, itemToString } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books" required>
{({ id, required }) => (
<MultiCombobox
id={id}
required={required}
data={books}
itemToString={itemToString}
placeholder="Search books..."
/>
)}
</FormField>
</div>
);
}

Large book set with max tokens (in modal)

The same large, virtualized list with maxTokens and selectAll, inside a Modal.

Example:

function LargeBookSetWithMaxTokensInModalMultiComboboxExample() {
const { MultiCombobox } = MenuV3;
const { books, largeBookSet, itemToString, renderItem } = menuV3Data;
return (
<Modal
title="Pick some books"
modalTrigger={<Button appearance="primary">Open Modal</Button>}
closeButtonAriaLabel="Close"
>
<ModalBody>
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiCombobox
id={id}
data={largeBookSet}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Search books..."
isVirtualized
selectAll
maxTokens={5}
/>
)}
</FormField>
</div>
</ModalBody>
<ModalFooter cancelButton={<Button>Cancel</Button>} />
</Modal>
);
}

Escape Key in Modal (a11y)

Inside a Modal, pressing Escape closes the open dropdown and returns focus to the input, leaving the modal open. Pressing Escape again closes the modal.

Example:

function EscapeKeyInModalMultiComboboxExample() {
const { MultiCombobox } = MenuV3;
const { books, itemToString } = menuV3Data;
return (
<Modal
title="Pick some books"
modalTrigger={<Button appearance="primary">Open Modal</Button>}
closeButtonAriaLabel="Close"
>
<ModalBody>
<div style={{ width: "300px", padding: "16px 0" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiCombobox
id={id}
data={books}
itemToString={itemToString}
placeholder="Search books..."
/>
)}
</FormField>
<p style={{ marginTop: 16, fontSize: 14 }}>
<strong>To verify:</strong> open the combobox, press{" "}
<kbd>Escape</kbd>. The dropdown should close and focus should return
to the input. The modal should remain open. Press <kbd>Escape</kbd>{" "}
again to close the modal.
</p>
</div>
</ModalBody>
<ModalFooter cancelButton={<Button>Cancel</Button>} />
</Modal>
);
}

SingleSearchableSelect

SingleSearchableSelect looks like a regular select when closed. When it opens, a search field appears at the top of the list. Use it when you want the compact look of a select but the list is long enough to need search.

Set the search field's placeholder with searchPlaceholder.

Basic

Pass your items as data and an itemToString function. The trigger shows placeholder, and the search field inside the popup shows searchPlaceholder.

Example:

function BasicSingleSearchableSelectExample() {
const { SingleSearchableSelect } = MenuV3;
const { books, itemToString } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book">
{({ id }) => (
<SingleSearchableSelect
id={id}
data={books}
itemToString={itemToString}
placeholder="Select a book"
searchPlaceholder="Search books..."
/>
)}
</FormField>
</div>
);
}

Invalid

Pass isInvalid to show the error state. Pair it with an error on the surrounding FormField so people know what to fix.

Example:

function InvalidSingleSearchableSelectExample() {
const { SingleSearchableSelect } = MenuV3;
const { books, itemToString } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book" isInvalid error="Please pick a book.">
{({ id, isInvalid }) => (
<SingleSearchableSelect
id={id}
isInvalid={isInvalid}
data={books}
itemToString={itemToString}
placeholder="Select a book"
searchPlaceholder="Search books..."
/>
)}
</FormField>
</div>
);
}

Custom render item

Use renderItem to control how each option looks in the list. Here each book shows its title in bold with the author beside it. itemToString is still used for the accessible name and for filtering.

Example:

function CustomRenderItemSingleSearchableSelectExample() {
const { SingleSearchableSelect } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book">
{({ id }) => (
<SingleSearchableSelect
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select a book"
searchPlaceholder="Search books..."
/>
)}
</FormField>
</div>
);
}

Grouped

Pass a groupBy function to organize options under headings. Every item that returns the same string is placed in the same group.

Example:

function GroupedSingleSearchableSelectExample() {
const { SingleSearchableSelect } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book">
{({ id }) => (
<SingleSearchableSelect
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select a book"
searchPlaceholder="Search books..."
groupBy={(book) => book.author}
/>
)}
</FormField>
</div>
);
}

Default selected

Use defaultSelectedItemId to preselect an item while letting the component manage selection itself (uncontrolled).

Example:

function DefaultSelectedSingleSearchableSelectExample() {
const { SingleSearchableSelect } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book">
{({ id }) => (
<SingleSearchableSelect
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select a book"
searchPlaceholder="Search books..."
defaultSelectedItemId="book-3"
/>
)}
</FormField>
</div>
);
}

Controlled

Control the selection with selectedItemId and onSelectedItemIdChange. Most single selection menus should control the selection state, so the selected value can be saved, validated or reset by your app.

Example:

function ControlledSingleSearchableSelectExample() {
const { SingleSearchableSelect } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
const [selectedId, setSelectedId] = React.useState("book-1");
const selected = books.find((b) => b.id === selectedId);
return (
<div
style={{
width: "300px",
display: "flex",
flexDirection: "column",
gap: 16,
}}
>
<FormField label="Pick a book">
{({ id }) => (
<SingleSearchableSelect
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select a book"
searchPlaceholder="Search books..."
selectedItemId={selectedId}
onSelectedItemIdChange={(id) => setSelectedId(id)}
/>
)}
</FormField>
<div style={{ fontSize: 13 }}>
Selected: <strong>{selected?.title ?? "none"}</strong>
</div>
<div style={{ display: "flex", gap: 8 }}>
<button onClick={() => setSelectedId(null)}>Clear</button>
<button onClick={() => setSelectedId("book-5")}>Select 1984</button>
</div>
</div>
);
}

Custom renderSelection

Use renderSelection to change how the selected item appears in the closed trigger, independently of how options look in the list.

Example:

function CustomSelectionSingleSearchableSelectExample() {
const { SingleSearchableSelect } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book">
{({ id }) => (
<SingleSearchableSelect
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select a book"
searchPlaceholder="Search books..."
renderSelection={(item) => (
<span>
<span style={{ fontWeight: "bold" }}>{item.title}</span>
<span
style={{ fontSize: "11px", color: "#888", marginLeft: "6px" }}
>
by {item.author}
</span>
</span>
)}
/>
)}
</FormField>
</div>
);
}

Disabled items

Set disabled: true on any item in data to keep it visible but unselectable.

Example:

function DisabledItemsSingleSearchableSelectExample() {
const { SingleSearchableSelect } = MenuV3;
const { books, booksWithDisabled, itemToString } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book">
{({ id }) => (
<SingleSearchableSelect
id={id}
data={booksWithDisabled}
itemToString={itemToString}
placeholder="Select a book"
searchPlaceholder="Search books..."
/>
)}
</FormField>
</div>
);
}

Include placeholder item

Set includePlaceholderItem to add the placeholder as the first option in the list, so people can clear their choice by selecting it again.

Example:

function IncludePlaceholderItemSingleSearchableSelectExample() {
const { SingleSearchableSelect } = MenuV3;
const { books, itemToString } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book">
{({ id }) => (
<SingleSearchableSelect
id={id}
data={books}
itemToString={itemToString}
placeholder="Select a book"
searchPlaceholder="Search books..."
includePlaceholderItem
/>
)}
</FormField>
</div>
);
}

Include placeholder item (virtualized)

includePlaceholderItem also works with isVirtualized lists.

Example:

function IncludePlaceholderItemVirtualizedSingleSearchableSelectExample() {
const { SingleSearchableSelect } = MenuV3;
const { books, largeBookSet, itemToString } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book">
{({ id }) => (
<SingleSearchableSelect
id={id}
data={largeBookSet}
itemToString={itemToString}
placeholder="Select a book"
searchPlaceholder="Search books..."
includePlaceholderItem
isVirtualized
/>
)}
</FormField>
</div>
);
}

Virtualized

Set isVirtualized for long lists. Only the rows in view are rendered, so lists with thousands of items stay fast. This example has 1,000 books.

Example:

function VirtualizedSingleSearchableSelectExample() {
const { SingleSearchableSelect } = MenuV3;
const { books, largeBookSet, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book">
{({ id }) => (
<SingleSearchableSelect
id={id}
data={largeBookSet}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select a book"
searchPlaceholder="Search books..."
isVirtualized
/>
)}
</FormField>
</div>
);
}

Loading

Set isLoading while results are being fetched. The list shows a loading message (customizable with loadingText) instead of options.

Example:

function LoadingSingleSearchableSelectExample() {
const { SingleSearchableSelect } = MenuV3;
const { books, itemToString } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book">
{({ id }) => (
<SingleSearchableSelect
id={id}
data={[]}
itemToString={itemToString}
placeholder="Select a book"
searchPlaceholder="Search books..."
isLoading
/>
)}
</FormField>
</div>
);
}

Loading (toggle)

Toggle isLoading to see how the menu moves between the loading state and loaded results, as it would during a real fetch.

Example:

function LoadingWithToggleSingleSearchableSelectExample() {
const { SingleSearchableSelect } = MenuV3;
const { books, itemToString } = menuV3Data;
const [isLoading, setIsLoading] = React.useState(true);
const data = isLoading ? [] : books;
return (
<div
style={{
width: "300px",
display: "flex",
flexDirection: "column",
gap: 16,
}}
>
<FormField label="Pick a book">
{({ id }) => (
<SingleSearchableSelect
id={id}
data={data}
itemToString={itemToString}
placeholder="Select a book"
searchPlaceholder="Search books..."
isLoading={isLoading}
/>
)}
</FormField>
<button onClick={() => setIsLoading((v) => !v)}>
{isLoading ? "Finish loading" : "Set loading"}
</button>
</div>
);
}

Inline (fullWidth=false)

By default the trigger fills its container. Set fullWidth={false} to size the trigger to its content, which is useful inline with text or in toolbars.

Example:

function InlineSingleSearchableSelectExample() {
const { SingleSearchableSelect } = MenuV3;
const { books, itemToString } = menuV3Data;
return (
<FormField label="Pick a book">
{({ id }) => (
<SingleSearchableSelect
id={id}
data={books}
itemToString={itemToString}
placeholder="Select a book"
searchPlaceholder="Search books..."
fullWidth={false}
/>
)}
</FormField>
);
}

Custom search (title + author)

Pass a filter function to change how typed text matches items. This example matches on the author as well as the title. Pass filter={null} to turn filtering off, for example when results come from the server.

Example:

function CustomSearchSingleSearchableSelectExample() {
const { SingleSearchableSelect } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick a book">
{({ id }) => (
<SingleSearchableSelect
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select a book"
searchPlaceholder="Search by title or author..."
filter={(item, query) => {
const book = item;
const q = query.toLowerCase();
return (
book.title.toLowerCase().includes(q) ||
book.author.toLowerCase().includes(q)
);
}}
/>
)}
</FormField>
</div>
);
}

MultiSearchableSelect

MultiSearchableSelect looks like a regular select when closed, with a search field inside the popup, and lets people choose several options. Selected items are summarized in the trigger.

Basic

Pass your items as data and an itemToString function. Selected items are summarized in the trigger, and the list stays open while people choose.

Example:

function BasicMultiSearchableSelectExample() {
const { MultiSearchableSelect } = MenuV3;
const { books, itemToString } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiSearchableSelect
id={id}
data={books}
itemToString={itemToString}
placeholder="Select books"
searchPlaceholder="Search books..."
/>
)}
</FormField>
</div>
);
}

Invalid

Pass isInvalid to show the error state. Pair it with an error on the surrounding FormField so people know what to fix.

Example:

function InvalidMultiSearchableSelectExample() {
const { MultiSearchableSelect } = MenuV3;
const { books, itemToString } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField
label="Pick books"
isInvalid
error="Please pick at least one book."
>
{({ id, isInvalid }) => (
<MultiSearchableSelect
id={id}
isInvalid={isInvalid}
data={books}
itemToString={itemToString}
placeholder="Select books"
searchPlaceholder="Search books..."
/>
)}
</FormField>
</div>
);
}

Custom render item

Use renderItem to control how each option looks in the list. Here each book shows its title in bold with the author beside it. itemToString is still used for the accessible name and for filtering.

Example:

function CustomRenderItemMultiSearchableSelectExample() {
const { MultiSearchableSelect } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiSearchableSelect
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select books"
searchPlaceholder="Search books..."
/>
)}
</FormField>
</div>
);
}

Grouped

Pass a groupBy function to organize options under headings. Every item that returns the same string is placed in the same group.

Example:

function GroupedMultiSearchableSelectExample() {
const { MultiSearchableSelect } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiSearchableSelect
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select books"
searchPlaceholder="Search books..."
groupBy={(book) => book.author}
/>
)}
</FormField>
</div>
);
}

Default selected

Use defaultSelectedItemIds to preselect items while letting the component manage selection itself (uncontrolled).

Example:

function DefaultSelectedMultiSearchableSelectExample() {
const { MultiSearchableSelect } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiSearchableSelect
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select books"
searchPlaceholder="Search books..."
defaultSelectedItemIds={["book-1", "book-3"]}
/>
)}
</FormField>
</div>
);
}

Controlled

Control the selection with selectedItemIds and onSelectedItemIdsChange. Control the selection when your app needs to save, validate or reset the chosen items.

Example:

function ControlledMultiSearchableSelectExample() {
const { MultiSearchableSelect } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
const [selectedIds, setSelectedIds] = React.useState(["book-2"]);
const selected = books.filter((b) => selectedIds.includes(b.id));
return (
<div
style={{
width: "300px",
display: "flex",
flexDirection: "column",
gap: 16,
}}
>
<FormField label="Pick books">
{({ id }) => (
<MultiSearchableSelect
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select books"
searchPlaceholder="Search books..."
selectedItemIds={selectedIds}
onSelectedItemIdsChange={(ids) => setSelectedIds(ids)}
/>
)}
</FormField>
<div style={{ fontSize: 13 }}>
Selected:{" "}
<strong>
{selected.length > 0
? selected.map((b) => b.title).join(", ")
: "none"}
</strong>
</div>
<div style={{ display: "flex", gap: 8 }}>
<button onClick={() => setSelectedIds([])}>Clear all</button>
<button onClick={() => setSelectedIds(["book-5", "book-7"])}>
Select 1984 &amp; Meditations
</button>
</div>
</div>
);
}

Custom renderSelection

Use renderSelection to replace how the selected items are summarized in the closed trigger, for example as a count or a comma-separated list.

Example:

function CustomSelectionMultiSearchableSelectExample() {
const { MultiSearchableSelect } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiSearchableSelect
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select books"
searchPlaceholder="Search books..."
renderSelection={(selected) => {
if (selected.length === 0) return "Select books";
const first = itemToString(selected[0]);
const extra =
selected.length > 1 ? ` (+${selected.length - 1})` : "";
return (
<span style={{ fontSize: 13 }}>
{first}
{extra}
</span>
);
}}
/>
)}
</FormField>
</div>
);
}

Custom customRenderSelections

Use customRenderSelections to change the text used for each selected item in the trigger summary, without replacing the whole summary like renderSelection does.

Example:

function CustomRenderSelectionsMultiSearchableSelectExample() {
const { MultiSearchableSelect } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiSearchableSelect
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select books"
searchPlaceholder="Search books..."
customRenderSelections={(item) => `${item.title} (${item.author})`}
/>
)}
</FormField>
</div>
);
}

Disabled items

Set disabled: true on any item in data to keep it visible but unselectable.

Example:

function DisabledItemsMultiSearchableSelectExample() {
const { MultiSearchableSelect } = MenuV3;
const { books, booksWithDisabled, itemToString } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiSearchableSelect
id={id}
data={booksWithDisabled}
itemToString={itemToString}
placeholder="Select books"
searchPlaceholder="Search books..."
/>
)}
</FormField>
</div>
);
}

Selectable headings

With groupBy and selectHeadings, each group heading becomes a checkbox that selects or clears every item in that group.

Example:

function SelectableHeadingsMultiSearchableSelectExample() {
const { MultiSearchableSelect } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiSearchableSelect
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select books"
searchPlaceholder="Search books..."
groupBy={(book) => book.author}
selectHeadings
/>
)}
</FormField>
</div>
);
}

Select all

Set selectAll to add a "Select all" option at the top of the list. Pass a string instead of true to change its label.

Example:

function SelectAllMultiSearchableSelectExample() {
const { MultiSearchableSelect } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiSearchableSelect
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select books"
searchPlaceholder="Search books..."
groupBy={(book) => book.author}
selectHeadings
selectAll
/>
)}
</FormField>
</div>
);
}

Virtualized

Set isVirtualized for long lists. Only the rows in view are rendered, so lists with thousands of items stay fast. This example has 1,000 books.

Example:

function VirtualizedMultiSearchableSelectExample() {
const { MultiSearchableSelect } = MenuV3;
const { books, largeBookSet, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiSearchableSelect
id={id}
data={largeBookSet}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select books"
searchPlaceholder="Search books..."
groupBy={(book) => book.author}
selectHeadings
isVirtualized
/>
)}
</FormField>
</div>
);
}

Max selections

Set maxSelections to limit how many selected items are listed in the trigger. The rest are summarized as a "+N" count.

Example:

function MaxSelectionsMultiSearchableSelectExample() {
const { MultiSearchableSelect } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiSearchableSelect
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select books"
searchPlaceholder="Search books..."
defaultSelectedItemIds={[
"book-1",
"book-2",
"book-3",
"book-4",
"book-5",
]}
maxSelections={2}
/>
)}
</FormField>
</div>
);
}

Remove selected items

Set removeSelectedItems to hide items from the list once they are selected. They appear only as selections until they are removed.

Example:

function RemoveSelectedItemsMultiSearchableSelectExample() {
const { MultiSearchableSelect } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiSearchableSelect
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select books"
searchPlaceholder="Search books..."
removeSelectedItems
/>
)}
</FormField>
</div>
);
}

Virtualized + select all

Virtualization, grouped headings and select all can be combined, which keeps very large grouped lists usable.

Example:

function VirtualizedSelectAllMultiSearchableSelectExample() {
const { MultiSearchableSelect } = MenuV3;
const { books, largeBookSet, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiSearchableSelect
id={id}
data={largeBookSet}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select books"
searchPlaceholder="Search books..."
groupBy={(book) => book.author}
selectHeadings
selectAll
isVirtualized
/>
)}
</FormField>
</div>
);
}

Loading

Set isLoading while results are being fetched. The list shows a loading message (customizable with loadingText) instead of options.

Example:

function LoadingMultiSearchableSelectExample() {
const { MultiSearchableSelect } = MenuV3;
const { books, itemToString } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiSearchableSelect
id={id}
data={[]}
itemToString={itemToString}
placeholder="Select books"
searchPlaceholder="Search books..."
isLoading
/>
)}
</FormField>
</div>
);
}

Loading (toggle)

Toggle isLoading to see how the menu moves between the loading state and loaded results, as it would during a real fetch.

Example:

function LoadingWithToggleMultiSearchableSelectExample() {
const { MultiSearchableSelect } = MenuV3;
const { books, itemToString } = menuV3Data;
const [isLoading, setIsLoading] = React.useState(true);
const data = isLoading ? [] : books;
return (
<div
style={{
width: "300px",
display: "flex",
flexDirection: "column",
gap: 16,
}}
>
<FormField label="Pick books">
{({ id }) => (
<MultiSearchableSelect
id={id}
data={data}
itemToString={itemToString}
placeholder="Select books"
searchPlaceholder="Search books..."
isLoading={isLoading}
/>
)}
</FormField>
<button onClick={() => setIsLoading((v) => !v)}>
{isLoading ? "Finish loading" : "Set loading"}
</button>
</div>
);
}

Inline (fullWidth=false)

By default the trigger fills its container. Set fullWidth={false} to size the trigger to its content, which is useful inline with text or in toolbars.

Example:

function InlineMultiSearchableSelectExample() {
const { MultiSearchableSelect } = MenuV3;
const { books, itemToString } = menuV3Data;
return (
<FormField label="Pick books">
{({ id }) => (
<MultiSearchableSelect
id={id}
data={books}
itemToString={itemToString}
placeholder="Select books"
searchPlaceholder="Search books..."
fullWidth={false}
/>
)}
</FormField>
);
}

Custom search (title + author)

Pass a filter function to change how typed text matches items. This example matches on the author as well as the title. Pass filter={null} to turn filtering off, for example when results come from the server.

Example:

function CustomSearchMultiSearchableSelectExample() {
const { MultiSearchableSelect } = MenuV3;
const { books, itemToString, renderItem } = menuV3Data;
return (
<div style={{ width: "300px" }}>
<FormField label="Pick books">
{({ id }) => (
<MultiSearchableSelect
id={id}
data={books}
itemToString={itemToString}
renderItem={renderItem}
placeholder="Select books"
searchPlaceholder="Search by title or author..."
filter={(item, query) => {
const book = item;
const q = query.toLowerCase();
return (
book.title.toLowerCase().includes(q) ||
book.author.toLowerCase().includes(q)
);
}}
/>
)}
</FormField>
</div>
);
}

In a Modal or PeekIn

All of these menus render their popups into a portal positioned relative to the trigger, so they work inside overlays like Modal and PeekIn without being clipped.

V2 Modal

Every select and combobox inside a Modal, next to standard inputs, so you can compare them. The same fields also render below the trigger, outside the modal.

Example:

function V2ModalInaModalorPeekInExample() {
const { SingleSelect, MultiSelect, SingleCombobox, MultiCombobox, SingleSearchableSelect, MultiSearchableSelect } = MenuV3;
const { books, itemToString } = menuV3Data;
function AllComponents() {
return (
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
<FormField label="Input">
{({ id }) => (
<Input
id={id}
name="input"
value="Some Value"
placeholder="Select a book"
/>
)}
</FormField>
<FormField label="Token Input">
{({ id }) => (
<TokenInput
id={id}
name="token-input"
tokens={[{ id: "token", value: "Token" }]}
value="Some Value"
placeholder="Select a book"
/>
)}
</FormField>
<FormField label="Token Input2">
{({ id }) => (
<TokenInput
id={id}
name="token-input"
tokens={[]}
value="Some Value"
placeholder="Select a book"
/>
)}
</FormField>
<FormField label="Single Select">
{({ id }) => (
<SingleSelect
id={id}
data={books}
itemToString={itemToString}
placeholder="Select a book"
/>
)}
</FormField>
<FormField label="Multi Select">
{({ id }) => (
<MultiSelect
id={id}
data={books}
itemToString={itemToString}
placeholder="Select books"
/>
)}
</FormField>
<FormField label="Single Combobox">
{({ id }) => (
<SingleCombobox
id={id}
data={books}
itemToString={itemToString}
placeholder="Search books..."
/>
)}
</FormField>
<FormField label="Multi Combobox">
{({ id }) => (
<MultiCombobox
id={id}
data={books}
itemToString={itemToString}
placeholder="Search books..."
/>
)}
</FormField>
<FormField label="Single Searchable Select">
{({ id }) => (
<SingleSearchableSelect
id={id}
data={books}
itemToString={itemToString}
placeholder="Select a book"
searchPlaceholder="Search books..."
/>
)}
</FormField>
<FormField label="Multi Searchable Select">
{({ id }) => (
<MultiSearchableSelect
id={id}
data={books}
itemToString={itemToString}
placeholder="Select books"
searchPlaceholder="Search books..."
/>
)}
</FormField>
</div>
);
}
return (
<>
<Modal
title="Pick some books"
modalTrigger={<Button appearance="primary">Open V2 Modal</Button>}
closeButtonAriaLabel="Close"
>
<ModalBody>
<AllComponents />
</ModalBody>
<ModalFooter cancelButton={<Button>Cancel</Button>} />
</Modal>
<AllComponents />
</>
);
}

Action Menu in PeekIn

An ActionMenu with groups and a submenu inside a PeekIn.

Example:

function ActionMenuInPeekInInaModalorPeekInExample() {
const { ActionMenu, MenuGroup, MenuItem, MenuSub, MenuSubTrigger } = MenuV3;
const action = (label) => (...args) => console.log(label, ...args);
const [open, setOpen] = React.useState(false);
return (
<>
<Button appearance="primary" onClick={() => setOpen(true)}>
Open PeekIn
</Button>
<PeekIn open={open} onOpenChange={setOpen} closeButtonAriaLabel="Close">
<PeekInHeader title="Actions">
<ActionMenu
trigger={<Button appearance="pill">More actions</Button>}
>
<MenuItem onClick={action("Edit")}>Edit</MenuItem>
<MenuItem onClick={action("Duplicate")}>Duplicate</MenuItem>
<MenuGroup label="Danger">
<MenuItem onClick={action("Delete")} disabled>
Delete
</MenuItem>
</MenuGroup>
</ActionMenu>
</PeekInHeader>
<PeekInContent>
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
<ActionMenu
trigger={<Button appearance="secondary">Basic Menu</Button>}
>
<MenuItem onClick={action("Compose")}>Compose</MenuItem>
<MenuItem onClick={action("View Messages")}>
View Messages
</MenuItem>
<MenuItem onClick={action("Analyze post")}>
Analyze post
</MenuItem>
</ActionMenu>
<ActionMenu
trigger={
<Button appearance="secondary">Menu with Groups</Button>
}
>
<MenuGroup label="Actions" showSeparator>
<MenuItem onClick={action("Edit")}>Edit</MenuItem>
<MenuItem onClick={action("Duplicate")}>Duplicate</MenuItem>
</MenuGroup>
<MenuGroup label="Danger">
<MenuItem onClick={action("Delete")} disabled>
Delete
</MenuItem>
</MenuGroup>
</ActionMenu>
<ActionMenu
trigger={
<Button appearance="secondary">Menu with Submenu</Button>
}
>
<MenuItem onClick={action("Compose")}>Compose</MenuItem>
<MenuSub trigger={<MenuSubTrigger>Share</MenuSubTrigger>}>
<MenuItem onClick={action("Share to Twitter")}>
Twitter
</MenuItem>
<MenuItem onClick={action("Share to LinkedIn")}>
LinkedIn
</MenuItem>
<MenuSub
trigger={<MenuSubTrigger>More platforms</MenuSubTrigger>}
>
<MenuItem onClick={action("Share to Facebook")}>
Facebook
</MenuItem>
<MenuItem onClick={action("Share to Instagram")}>
Instagram
</MenuItem>
</MenuSub>
</MenuSub>
<MenuItem onClick={action("Delete")} disabled>
Delete
</MenuItem>
</ActionMenu>
</div>
</PeekInContent>
<PeekInPanel title="Details">
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
<ActionMenu
trigger={
<Button appearance="secondary">Panel Action Menu</Button>
}
>
<MenuItem onClick={action("Pin")}>Pin</MenuItem>
<MenuItem onClick={action("Add to report")}>
Add to report
</MenuItem>
<MenuGroup label="Danger" showSeparator>
<MenuItem onClick={action("Remove")}>Remove</MenuItem>
</MenuGroup>
</ActionMenu>
</div>
</PeekInPanel>
</PeekIn>
</>
);
}

Action Menu in V2 Modal

An ActionMenu with groups and a submenu inside a Modal.

Example:

function ActionMenuInModalInaModalorPeekInExample() {
const { ActionMenu, MenuGroup, MenuItem, MenuSub, MenuSubTrigger } = MenuV3;
const action = (label) => (...args) => console.log(label, ...args);
return (
<Modal
title="Actions"
modalTrigger={<Button appearance="primary">Open Modal</Button>}
closeButtonAriaLabel="Close"
>
<ModalBody>
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
<ActionMenu
trigger={<Button appearance="secondary">Basic Menu</Button>}
>
<MenuItem onClick={action("Compose")}>Compose</MenuItem>
<MenuItem onClick={action("View Messages")}>View Messages</MenuItem>
<MenuItem onClick={action("Analyze post")}>Analyze post</MenuItem>
</ActionMenu>
<ActionMenu
trigger={<Button appearance="secondary">Menu with Groups</Button>}
>
<MenuGroup label="Actions" showSeparator>
<MenuItem onClick={action("Edit")}>Edit</MenuItem>
<MenuItem onClick={action("Duplicate")}>Duplicate</MenuItem>
</MenuGroup>
<MenuGroup label="Danger">
<MenuItem onClick={action("Delete")} disabled>
Delete
</MenuItem>
</MenuGroup>
</ActionMenu>
<ActionMenu
trigger={<Button appearance="secondary">Menu with Submenu</Button>}
>
<MenuItem onClick={action("Compose")}>Compose</MenuItem>
<MenuSub trigger={<MenuSubTrigger>Share</MenuSubTrigger>}>
<MenuItem onClick={action("Share to Twitter")}>Twitter</MenuItem>
<MenuItem onClick={action("Share to LinkedIn")}>
LinkedIn
</MenuItem>
<MenuSub
trigger={<MenuSubTrigger>More platforms</MenuSubTrigger>}
>
<MenuItem onClick={action("Share to Facebook")}>
Facebook
</MenuItem>
<MenuItem onClick={action("Share to Instagram")}>
Instagram
</MenuItem>
</MenuSub>
</MenuSub>
<MenuItem onClick={action("Delete")} disabled>
Delete
</MenuItem>
</ActionMenu>
</div>
</ModalBody>
<ModalFooter cancelButton={<Button>Close</Button>} />
</Modal>
);
}