packages/dev/s2-docs/pages/s2/ComboBox.mdx
import {Layout} from '../../src/Layout'; export default Layout;
import {InlineAlert, Heading, Content} from '@react-spectrum/s2'; import docs from 'docs:@react-spectrum/s2';
export const tags = ['autocomplete', 'search', 'typeahead']; export const relatedPages = [ {title: 'Testing ComboBox', url: './ComboBox/testing'} ]; export const description = 'Combines a text input with a listbox, and allows a user to filter a list of options.';
<PageDescription>{docs.exports.ComboBox.description}</PageDescription>
"use client";
import {ComboBox, ComboBoxItem} from '@react-spectrum/s2';
<ComboBox/* PROPS */>
<ComboBoxItem>Chocolate</ComboBoxItem>
<ComboBoxItem>Mint</ComboBoxItem>
<ComboBoxItem>Strawberry</ComboBoxItem>
<ComboBoxItem>Vanilla</ComboBoxItem>
<ComboBoxItem>Chocolate Chip Cookie Dough</ComboBoxItem>
</ComboBox>
ComboBox follows the Collection Components API, accepting both static and dynamic collections. This example shows a dynamic collection, passing a list of objects to the items prop, and a function to render the children.
"use client";
import {ComboBox, ComboBoxItem} from '@react-spectrum/s2';
function Example() {
let options = [
{ id: 1, name: 'Aardvark' },
{ id: 2, name: 'Cat' },
{ id: 3, name: 'Dog' },
{ id: 4, name: 'Kangaroo' },
{ id: 5, name: 'Koala' },
{ id: 6, name: 'Penguin' },
{ id: 7, name: 'Snake' },
{ id: 8, name: 'Turtle' },
{ id: 9, name: 'Wombat' }
];
return (
/*- begin highlight -*/
<ComboBox label="Animals" defaultItems={options} placeholder="Select an animal">
{(item) => <ComboBoxItem>{item.name}</ComboBoxItem>}
</ComboBox>
/*- end highlight -*/
);
}
ComboBoxItem supports icons, avatars, and label and description text slots.
"use client";
import {Avatar, ComboBox, ComboBoxItem, Text} from '@react-spectrum/s2';
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
import Comment from '@react-spectrum/s2/icons/Comment';
import Edit from '@react-spectrum/s2/icons/Edit';
import UserSettings from '@react-spectrum/s2/icons/UserSettings';
const users = Array.from({length: 5}, (_, i) => ({
id: `user${i + 1}`,
name: `User ${i + 1}`,
email: `user${i + 1}@example.com`,
avatar: 'https://i.imgur.com/kJOwAdv.png'
}));
<div className={style({display: 'flex', gap: 12, flexWrap: 'wrap'})}>
<ComboBox label="Permissions" defaultSelectedKey="read" placeholder="Select a permission level">
<ComboBoxItem id="read" textValue="Read">
<Comment />
<Text slot="label">Read</Text>
<Text slot="description">Comment only</Text>
</ComboBoxItem>
<ComboBoxItem id="write" textValue="Write">
<Edit />
<Text slot="label">Write</Text>
<Text slot="description">Read and write only</Text>
</ComboBoxItem>
<ComboBoxItem id="admin" textValue="Admin">
<UserSettings />
<Text slot="label">Admin</Text>
<Text slot="description">Full access</Text>
</ComboBoxItem>
</ComboBox>
<ComboBox label="Share" defaultItems={users} defaultSelectedKey="user1" placeholder="Select a user">
{(item) => (
<ComboBoxItem id={item.id} textValue={item.name}>
<Avatar slot="avatar" src={item.avatar} />
<Text slot="label">{item.name}</Text>
<Text slot="description">{item.email}</Text>
</ComboBoxItem>
)}
</ComboBox>
</div>
Use the <ComboBoxSection> component to group options. A <Header> element, with a <Heading> and optional description slot can be included to label a section. Sections without a header must have an aria-label.
"use client";
import {ComboBox, ComboBoxItem, ComboBoxSection, Header, Heading, Text} from '@react-spectrum/s2';
<ComboBox label="Preferred fruit or vegetable" placeholder="Select an option">
<ComboBoxSection>
<Header>
<Heading>Fruit</Heading>
<Text slot="description">Sweet and nutritious</Text>
</Header>
<ComboBoxItem id="apple">Apple</ComboBoxItem>
<ComboBoxItem id="banana">Banana</ComboBoxItem>
<ComboBoxItem id="orange">Orange</ComboBoxItem>
<ComboBoxItem id="grapes">Grapes</ComboBoxItem>
</ComboBoxSection>
<ComboBoxSection>
<Header>
<Heading>Vegetable</Heading>
<Text slot="description">Healthy and savory</Text>
</Header>
<ComboBoxItem id="broccoli">Broccoli</ComboBoxItem>
<ComboBoxItem id="carrots">Carrots</ComboBoxItem>
<ComboBoxItem id="spinach">Spinach</ComboBoxItem>
<ComboBoxItem id="lettuce">Lettuce</ComboBoxItem>
</ComboBoxSection>
</ComboBox>
Use the loadingState and onLoadMore props to enable async loading and infinite scrolling.
"use client";
import {ComboBox, ComboBoxItem, useAsyncList} from '@react-spectrum/s2';
interface Character {
name: string
}
function Example() {
let list = useAsyncList<Character>({
async load({signal, cursor, filterText}) {
if (cursor) {
cursor = cursor.replace(/^http:\/\//i, 'https://');
}
let res = await fetch(cursor || `https://swapi.py4e.com/api/people/?search=${filterText}`, {signal});
let json = await res.json();
return {
items: json.results,
cursor: json.next
};
}
});
return (
<ComboBox
label="Star Wars Character Lookup"
placeholder="Select a character"
items={list.items}
inputValue={list.filterText}
onInputChange={list.setFilterText}
/*- begin highlight -*/
loadingState={list.loadingState}
onLoadMore={list.loadMore}>
{item => <ComboBoxItem id={item.name} textValue={item.name}>{item.name}</ComboBoxItem>}
</ComboBox>
);
}
Use the onAction prop on a <ComboBoxItem> to perform a custom action when the item is pressed. This example adds a "Create" action for the current input value.
"use client";
import {ComboBox, ComboBoxItem} from '@react-spectrum/s2';
import {useState} from 'react';
function Example() {
let [inputValue, setInputValue] = useState('');
return (
<ComboBox
label="Favorite Animal"
placeholder="Select an animal"
inputValue={inputValue}
onInputChange={setInputValue}>
{inputValue.length > 0 && (
<ComboBoxItem onAction={() => alert('Creating ' + inputValue)}>
{`Create "${inputValue}"`}
</ComboBoxItem>
)}
<ComboBoxItem>Aardvark</ComboBoxItem>
<ComboBoxItem>Cat</ComboBoxItem>
<ComboBoxItem>Dog</ComboBoxItem>
<ComboBoxItem>Kangaroo</ComboBoxItem>
<ComboBoxItem>Panda</ComboBoxItem>
<ComboBoxItem>Snake</ComboBoxItem>
</ComboBox>
);
}
Use the href prop on a <ComboBoxItem> to create a link. See the getting started guide to learn how to integrate with your framework. Link items in a ComboBox are not selectable.
"use client";
import {ComboBox, ComboBoxItem} from '@react-spectrum/s2';
<ComboBox label="Bookmarks" placeholder="Select a bookmark">
<ComboBoxItem href="https://adobe.com/" target="_blank">Adobe</ComboBoxItem>
<ComboBoxItem href="https://apple.com/" target="_blank">Apple</ComboBoxItem>
<ComboBoxItem href="https://google.com/" target="_blank">Google</ComboBoxItem>
<ComboBoxItem href="https://microsoft.com/" target="_blank">Microsoft</ComboBoxItem>
</ComboBox>
Use the defaultSelectedKey or selectedKey prop to set the selected item. The selected key corresponds to the id prop of an item. Items can be disabled with the isDisabled prop. See the selection guide for more details.
"use client";
import {ComboBox, ComboBoxItem, type Key} from '@react-spectrum/s2';
import {useState} from 'react';
function Example() {
let [animal, setAnimal] = useState<Key | null>("bison");
return (
<div>
<ComboBox
label="Pick an animal"
placeholder="Select an animal"
/*- begin highlight -*/
selectedKey={animal}
onSelectionChange={setAnimal}>
<ComboBoxItem id="koala">Koala</ComboBoxItem>
<ComboBoxItem id="kangaroo">Kangaroo</ComboBoxItem>
<ComboBoxItem id="platypus" isDisabled>Platypus</ComboBoxItem>
<ComboBoxItem id="eagle">Bald Eagle</ComboBoxItem>
<ComboBoxItem id="bison">Bison</ComboBoxItem>
<ComboBoxItem id="skunk">Skunk</ComboBoxItem>
</ComboBox>
<p>Current selection: {animal}</p>
</div>
);
}
Use the inputValue or defaultInputValue prop to set the value of the input field. By default, the value will be reverted to the selected item on blur. Set the allowsCustomValue prop to enable entering values that are not in the list.
"use client";
import {ComboBox, ComboBoxItem, type Key} from '@react-spectrum/s2';
import {useState} from 'react';
function Example(props) {
let [value, setValue] = useState<Key>('Kangaroo');
return (
<div>
<ComboBox
{...props}
label="Favorite Animal"
placeholder="Select an animal"
/*- begin highlight -*/
/* PROPS */
inputValue={value}
onInputChange={setValue}>
<ComboBoxItem id="koala">Koala</ComboBoxItem>
<ComboBoxItem id="kangaroo">Kangaroo</ComboBoxItem>
<ComboBoxItem id="platypus">Platypus</ComboBoxItem>
<ComboBoxItem id="eagle">Bald Eagle</ComboBoxItem>
<ComboBoxItem id="bison">Bison</ComboBoxItem>
<ComboBoxItem id="skunk">Skunk</ComboBoxItem>
</ComboBox>
<p>Current input value: {value}</p>
</div>
);
}
Both inputValue and selectedKey can be controlled simultaneously. However, each interaction will only trigger either onInputChange or onSelectionChange, not both. When controlling both props, you must update both values accordingly.
"use client";
import {ComboBox, ComboBoxItem, type Key} from '@react-spectrum/s2';
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
import {useState} from 'react';
function ControlledComboBox() {
/*- begin collapse -*/
let options = [
{id: 1, name: 'Aerospace'},
{id: 2, name: 'Mechanical'},
{id: 3, name: 'Civil'},
{id: 4, name: 'Biomedical'},
{id: 5, name: 'Nuclear'},
{id: 6, name: 'Industrial'},
{id: 7, name: 'Chemical'},
{id: 8, name: 'Agricultural'},
{id: 9, name: 'Electrical'}
];
/*- end collapse -*/
let [fieldState, setFieldState] = useState<{selectedKey: Key | null, inputValue: string}>({
selectedKey: null,
inputValue: ''
});
let onSelectionChange = (id: Key | null) => {
// Update inputValue when selectedKey changes.
setFieldState({
inputValue: id ? (options.find(o => o.id === id)?.name ?? '') : '',
selectedKey: id
});
};
let onInputChange = (value: string) => {
// Reset selectedKey to null if the input is cleared.
setFieldState(prevState => ({
inputValue: value,
selectedKey: value === '' ? null : prevState.selectedKey
}));
};
return (
<div>
<ComboBox
label="Pick a engineering major"
placeholder="Select a major"
/*- begin highlight -*/
defaultItems={options}
selectedKey={fieldState.selectedKey}
inputValue={fieldState.inputValue}
onSelectionChange={onSelectionChange}
onInputChange={onInputChange}>
{item => <ComboBoxItem>{item.name}</ComboBoxItem>}
</ComboBox>
<pre className={style({font: 'body'})}>
Current selected major id: {fieldState.selectedKey}{'\n'}
Current input text: {fieldState.inputValue}
</pre>
</div>
);
}
Use the name prop to submit the id of the selected item to the server. Set the isRequired prop to validate that the user selects a value, or implement custom client or server-side validation. See the Forms guide to learn more.
"use client";
import {ComboBox, ComboBoxItem, Form, Button} from '@react-spectrum/s2';
function Example(props) {
return (
<Form>
<ComboBox
{...props}
label="Animal"
placeholder="e.g. Cat"
/*- begin highlight -*/
name="animal"
/* PROPS */
/*- end highlight -*/
description="Please select an animal.">
<ComboBoxItem id="aardvark">Aardvark</ComboBoxItem>
<ComboBoxItem id="cat">Cat</ComboBoxItem>
<ComboBoxItem id="dog">Dog</ComboBoxItem>
<ComboBoxItem id="kangaroo">Kangaroo</ComboBoxItem>
<ComboBoxItem id="panda">Panda</ComboBoxItem>
<ComboBoxItem id="snake">Snake</ComboBoxItem>
</ComboBox>
<Button type="submit">Submit</Button>
</Form>
);
}
Use the menuTrigger prop to control when the popover opens:
input (default): popover opens when the user edits the input text.focus: popover opens when the user focuses the input.manual: popover only opens when the user presses the trigger button or uses the arrow keys.The align, direction, shouldFlip and menuWidth props control the behavior of the popover.
"use client";
import {ComboBox, ComboBoxItem} from '@react-spectrum/s2';
<ComboBox/* PROPS */>
<ComboBoxItem id="red panda">Red Panda</ComboBoxItem>
<ComboBoxItem id="cat">Cat</ComboBoxItem>
<ComboBoxItem id="dog">Dog</ComboBoxItem>
<ComboBoxItem id="aardvark">Aardvark</ComboBoxItem>
<ComboBoxItem id="kangaroo">Kangaroo</ComboBoxItem>
<ComboBoxItem id="snake">Snake</ComboBoxItem>
</ComboBox>
<ComboBox>
<ComboBoxItem>
<Icon /> or <Avatar />
<Text slot="label" />
<Text slot="description" />
</ComboBoxItem>
<ComboBoxSection>
<Header>
<Heading />
<Text slot="description" />
</Header>
<ComboBoxItem />
</ComboBoxSection>
</ComboBox>