Combobox
Create custom select, autocomplete or multiselect inputs
Source
LLM docs
Docs
Package
Examples
This page contains only a small set of examples, as the full code of the demos is long. You can find all 50+ examples on a separate page.
Open Combobox examples pageUsage
Combobox provides a set of components and hooks to create custom select, multiselect or autocomplete components.
The component is very flexible – you have full control of the rendering and logic.
import { useState } from 'react';
import { Input, InputBase, Combobox, useCombobox } from '@mantine/core';
const groceries = ['🍎 Apples', '🍌 Bananas', '🥦 Broccoli', '🥕 Carrots', '🍫 Chocolate'];
function Demo() {
const combobox = useCombobox({
onDropdownClose: () => combobox.resetSelectedOption(),
});
const [value, setValue] = useState<string | null>(null);
const options = groceries.map((item) => (
<Combobox.Option value={item} key={item}>
{item}
</Combobox.Option>
));
return (
<Combobox
store={combobox}
onOptionSubmit={(val) => {
setValue(val);
combobox.closeDropdown();
}}
>
<Combobox.Target>
<InputBase
component="button"
type="button"
pointer
rightSection={<Combobox.Chevron />}
rightSectionPointerEvents="none"
onClick={() => combobox.toggleDropdown()}
>
{value || <Input.Placeholder>Pick value</Input.Placeholder>}
</InputBase>
</Combobox.Target>
<Combobox.Dropdown>
<Combobox.Options>{options}</Combobox.Options>
</Combobox.Dropdown>
</Combobox>
);
}useCombobox hook
useCombobox hook provides a combobox store. The store contains the current state of the component
and handlers to update it. The created store must be passed to the store prop of Combobox:
import { Combobox, useCombobox } from '@mantine/core';
function Demo() {
const combobox = useCombobox();
return (
<Combobox store={combobox}>{/* Your implementation */}</Combobox>
);
}useCombobox options
The useCombobox hook accepts an options object with the following properties:
interface UseComboboxOptions {
/** Default value for `dropdownOpened`, `false` by default */
defaultOpened?: boolean;
/** Controlled `dropdownOpened` state */
opened?: boolean;
/** Called when `dropdownOpened` state changes */
onOpenedChange?(opened: boolean): void;
/** Called when dropdown closes with event source: keyboard, mouse or unknown */
onDropdownClose?(eventSource: ComboboxDropdownEventSource): void;
/** Called when dropdown opens with event source: keyboard, mouse or unknown */
onDropdownOpen?(eventSource: ComboboxDropdownEventSource): void;
/** Determines whether arrow key presses should loop though items (first to last and last to first), `true` by default */
loop?: boolean;
/** `behavior` passed down to `element.scrollIntoView`, `'instant'` by default */
scrollBehavior?: ScrollBehavior;
}You can import the UseComboboxOptions type from the @mantine/core package:
import type { UseComboboxOptions } from '@mantine/core';Combobox store
The Combobox store is an object with the following properties:
interface ComboboxStore {
/** Current dropdown opened state */
dropdownOpened: boolean;
/** Opens dropdown */
openDropdown(eventSource?: 'keyboard' | 'mouse' | 'unknown'): void;
/** Closes dropdown */
closeDropdown(eventSource?: 'keyboard' | 'mouse' | 'unknown'): void;
/** Toggles dropdown opened state */
toggleDropdown(
eventSource?: 'keyboard' | 'mouse' | 'unknown'
): void;
/** Selected option index */
selectedOptionIndex: number;
/** Selects `Combobox.Option` by index */
selectOption(index: number): void;
/** Selects first `Combobox.Option` with `active` prop.
* If there are no such options, the function does nothing.
*/
selectActiveOption(): string | null;
/** Selects first `Combobox.Option` that is not disabled.
* If there are no such options, the function does nothing.
* */
selectFirstOption(): string | null;
/** Selects next `Combobox.Option` that is not disabled.
* If the current option is the last one, the function selects first option, if `loop` is true.
*/
selectNextOption(): string | null;
/** Selects previous `Combobox.Option` that is not disabled.
* If the current option is the first one, the function selects last option, if `loop` is true.
* */
selectPreviousOption(): string | null;
/** Resets selected option index to -1, removes `data-combobox-selected` from selected option */
resetSelectedOption(): void;
/** Triggers `onClick` event of selected option.
* If there is no selected option, the function does nothing.
*/
clickSelectedOption(): void;
/** Updates selected option index to currently selected or active option.
* The function is required to be used with searchable components to update selected option index
* when options list changes based on search query.
*/
updateSelectedOptionIndex(target?: 'active' | 'selected'): void;
/** List id, used for `aria-*` attributes */
listId: string | null;
/** Sets list id */
setListId(id: string): void;
/** Ref of `Combobox.Search` input */
searchRef: React.RefObject<HTMLInputElement | null>;
/** Moves focus to `Combobox.Search` input */
focusSearchInput(): void;
/** Ref of the target element */
targetRef: React.RefObject<HTMLElement | null>;
/** Moves focus to the target element */
focusTarget(): void;
}You can import ComboboxStore type from @mantine/core package:
import type { ComboboxStore } from '@mantine/core';useCombobox handlers
Combobox store handlers can be used to control Combobox state.
For example, to open the dropdown, call openDropdown handler:
import { Button, Combobox, useCombobox } from '@mantine/core';
function Demo() {
const combobox = useCombobox();
return (
<Combobox>
<Combobox.Target>
<Button onClick={() => combobox.openDropdown()}>
Open dropdown
</Button>
</Combobox.Target>
{/* Your implementation */}
</Combobox>
);
}You can use store handlers in useCombobox options. For example, you can
call selectFirstOption when the dropdown is opened and resetSelectedOption
when it is closed:
import { Combobox, useCombobox } from '@mantine/core';
function Demo() {
const combobox = useCombobox({
onDropdownOpen: () => combobox.selectFirstOption(),
onDropdownClose: () => combobox.resetSelectedOption(),
});
return (
<Combobox store={combobox}>{/* Your implementation */}</Combobox>
);
}Combobox.Target
Combobox.Target should be used as a wrapper for the target element or component.
Combobox.Target marks its child as a target for dropdown and sets aria-* attributes
and adds keyboard event listeners to it.
Combobox.Target requires a single child element or component. The child component
must accept ref and ...others props. You can use any Mantine component as a target without
any additional configuration, for example, Button, TextInput
or InputBase.
Example of using Combobox.Target with TextInput component:
import { useState } from 'react';
import { Combobox, TextInput, useCombobox } from '@mantine/core';
const groceries = ['🍎 Apples', '🍌 Bananas', '🥦 Broccoli', '🥕 Carrots', '🍫 Chocolate'];
function Demo() {
const combobox = useCombobox();
const [value, setValue] = useState('');
const shouldFilterOptions = !groceries.some((item) => item === value);
const filteredOptions = shouldFilterOptions
? groceries.filter((item) => item.toLowerCase().includes(value.toLowerCase().trim()))
: groceries;
const options = filteredOptions.map((item) => (
<Combobox.Option value={item} key={item}>
{item}
</Combobox.Option>
));
return (
<Combobox
store={combobox}
onOptionSubmit={(optionValue) => {
setValue(optionValue);
combobox.closeDropdown();
}}
>
<Combobox.Target>
<TextInput
label="Pick value or type anything"
placeholder="Pick value or type anything"
value={value}
onChange={(event) => {
setValue(event.currentTarget.value);
combobox.openDropdown();
combobox.updateSelectedOptionIndex();
}}
onClick={() => combobox.openDropdown()}
onFocus={() => combobox.openDropdown()}
onBlur={() => combobox.closeDropdown()}
/>
</Combobox.Target>
<Combobox.Dropdown>
<Combobox.Options>
{options.length === 0 ? <Combobox.Empty>Nothing found</Combobox.Empty> : options}
</Combobox.Options>
</Combobox.Dropdown>
</Combobox>
);
}Example of using Combobox.Target with Button component:
import { useState } from 'react';
import { Button, Combobox, useCombobox, Text, Box } from '@mantine/core';
const groceries = ['🍎 Apples', '🍌 Bananas', '🥦 Broccoli', '🥕 Carrots', '🍫 Chocolate'];
function Demo() {
const [selectedItem, setSelectedItem] = useState<string | null>(null);
const combobox = useCombobox({
onDropdownClose: () => combobox.resetSelectedOption(),
});
const options = groceries.map((item) => (
<Combobox.Option value={item} key={item}>
{item}
</Combobox.Option>
));
return (
<>
<Box mb="xs">
<Text span size="sm" c="dimmed">
Selected item:{' '}
</Text>
<Text span size="sm">
{selectedItem || 'Nothing selected'}
</Text>
</Box>
<Combobox
store={combobox}
width={250}
position="bottom-start"
withArrow
onOptionSubmit={(val) => {
setSelectedItem(val);
combobox.closeDropdown();
}}
>
<Combobox.Target>
<Button onClick={() => combobox.toggleDropdown()}>Pick item</Button>
</Combobox.Target>
<Combobox.Dropdown>
<Combobox.Options>{options}</Combobox.Options>
</Combobox.Dropdown>
</Combobox>
</>
);
}Split events and dropdown targets
In some cases, you might need to use different elements as an events target and as a dropdown.
Use Combobox.EventsTarget to add aria-* attributes and keyboard event handlers, and
Combobox.DropdownTarget to position the dropdown relative to the target.
You can have as many Combobox.EventsTarget as you need, but only one Combobox.DropdownTarget
per Combobox.
Example of using Combobox.EventsTarget and Combobox.DropdownTarget with PillsInput component
to create a searchable multiselect component:
import { useState } from 'react';
import { PillsInput, Pill, Combobox, CheckIcon, Group, useCombobox } from '@mantine/core';
const groceries = ['🍎 Apples', '🍌 Bananas', '🥦 Broccoli', '🥕 Carrots', '🍫 Chocolate'];
function Demo() {
const combobox = useCombobox({
onDropdownClose: () => combobox.resetSelectedOption(),
onDropdownOpen: () => combobox.updateSelectedOptionIndex('active'),
});
const [search, setSearch] = useState('');
const [value, setValue] = useState<string[]>([]);
const handleValueSelect = (val: string) =>
setValue((current) =>
current.includes(val) ? current.filter((v) => v !== val) : [...current, val]
);
const handleValueRemove = (val: string) =>
setValue((current) => current.filter((v) => v !== val));
const values = value.map((item) => (
<Pill key={item} withRemoveButton onRemove={() => handleValueRemove(item)}>
{item}
</Pill>
));
const options = groceries
.filter((item) => item.toLowerCase().includes(search.trim().toLowerCase()))
.map((item) => (
<Combobox.Option value={item} key={item} active={value.includes(item)}>
<Group gap="sm">
{value.includes(item) ? <CheckIcon size={12} /> : null}
<span>{item}</span>
</Group>
</Combobox.Option>
));
return (
<Combobox store={combobox} onOptionSubmit={handleValueSelect}>
<Combobox.DropdownTarget>
<PillsInput onClick={() => combobox.openDropdown()}>
<Pill.Group>
{values}
<Combobox.EventsTarget>
<PillsInput.Field
onFocus={() => combobox.openDropdown()}
onBlur={() => combobox.closeDropdown()}
value={search}
placeholder="Search values"
onChange={(event) => {
combobox.updateSelectedOptionIndex();
setSearch(event.currentTarget.value);
}}
onKeyDown={(event) => {
if (event.key === 'Backspace' && search.length === 0 && value.length > 0) {
event.preventDefault();
handleValueRemove(value[value.length - 1]);
}
}}
/>
</Combobox.EventsTarget>
</Pill.Group>
</PillsInput>
</Combobox.DropdownTarget>
<Combobox.Dropdown>
<Combobox.Options>
{options.length > 0 ? options : <Combobox.Empty>Nothing found...</Combobox.Empty>}
</Combobox.Options>
</Combobox.Dropdown>
</Combobox>
);
}Update selected option index
updateSelectedOptionIndex handler is required to be called when options list changes.
Usually, the options list changes when options are filtered based on the search query. In this case,
you need to call updateSelectedOptionIndex in onChange handler of the search input.
Example of using updateSelectedOptionIndex handler in searchable select component:
import { useState } from 'react';
import { InputBase, Combobox, useCombobox } from '@mantine/core';
const groceries = ['🍎 Apples', '🍌 Bananas', '🥦 Broccoli', '🥕 Carrots', '🍫 Chocolate'];
function Demo() {
const combobox = useCombobox({
onDropdownClose: () => combobox.resetSelectedOption(),
});
const [value, setValue] = useState<string | null>(null);
const [search, setSearch] = useState('');
const shouldFilterOptions = groceries.every((item) => item !== search);
const filteredOptions = shouldFilterOptions
? groceries.filter((item) => item.toLowerCase().includes(search.toLowerCase().trim()))
: groceries;
const options = filteredOptions.map((item) => (
<Combobox.Option value={item} key={item}>
{item}
</Combobox.Option>
));
return (
<Combobox
store={combobox}
onOptionSubmit={(val) => {
setValue(val);
setSearch(val);
combobox.closeDropdown();
}}
>
<Combobox.Target>
<InputBase
rightSection={<Combobox.Chevron />}
rightSectionPointerEvents="none"
onClick={() => combobox.openDropdown()}
onFocus={() => combobox.openDropdown()}
onBlur={() => {
combobox.closeDropdown();
setSearch(value || '');
}}
placeholder="Search value"
value={search}
onChange={(event) => {
combobox.updateSelectedOptionIndex();
setSearch(event.currentTarget.value);
}}
/>
</Combobox.Target>
<Combobox.Dropdown>
<Combobox.Options>
{options.length > 0 ? options : <Combobox.Empty>Nothing found</Combobox.Empty>}
</Combobox.Options>
</Combobox.Dropdown>
</Combobox>
);
}Search input
If you prefer search input inside the dropdown, use Combobox.Search component.
To focus the search input, call combobox.focusSearchInput, usually it is done
when the dropdown is opened. To prevent focus loss after the dropdown is closed,
call combobox.focusTarget:
import { useState } from 'react';
import { Button, Combobox, useCombobox, Text, Box } from '@mantine/core';
const groceries = ['🍎 Apples', '🍌 Bananas', '🥦 Broccoli', '🥕 Carrots', '🍫 Chocolate'];
function Demo() {
const [search, setSearch] = useState('');
const [selectedItem, setSelectedItem] = useState<string | null>(null);
const combobox = useCombobox({
onDropdownClose: () => {
combobox.resetSelectedOption();
combobox.focusTarget();
setSearch('');
},
onDropdownOpen: () => {
combobox.focusSearchInput();
},
});
const options = groceries
.filter((item) => item.toLowerCase().includes(search.toLowerCase().trim()))
.map((item) => (
<Combobox.Option value={item} key={item}>
{item}
</Combobox.Option>
));
return (
<>
<Box mb="xs">
<Text span size="sm" c="dimmed">
Selected item:{' '}
</Text>
<Text span size="sm">
{selectedItem || 'Nothing selected'}
</Text>
</Box>
<Combobox
store={combobox}
width={250}
position="bottom-start"
withArrow
onOptionSubmit={(val) => {
setSelectedItem(val);
combobox.closeDropdown();
}}
>
<Combobox.Target withAriaAttributes={false}>
<Button onClick={() => combobox.toggleDropdown()}>Pick item</Button>
</Combobox.Target>
<Combobox.Dropdown>
<Combobox.Search
value={search}
onChange={(event) => setSearch(event.currentTarget.value)}
placeholder="Search groceries"
/>
<Combobox.Options>
{options.length > 0 ? options : <Combobox.Empty>Nothing found</Combobox.Empty>}
</Combobox.Options>
</Combobox.Dropdown>
</Combobox>
</>
);
}Select first option
Use combobox.selectFirstOption function to select the first option. It is useful
if you want to select the first option when user searching for options in the list.
If there are no options available, it will do nothing.
import { useState, useEffect } from 'react';
import { Combobox, TextInput, useCombobox } from '@mantine/core';
const groceries = ['🍎 Apples', '🍌 Bananas', '🥦 Broccoli', '🥕 Carrots', '🍫 Chocolate'];
function Demo() {
const combobox = useCombobox({
onDropdownClose: () => combobox.resetSelectedOption(),
});
const [value, setValue] = useState('');
const shouldFilterOptions = !groceries.some((item) => item === value);
const filteredOptions = shouldFilterOptions
? groceries.filter((item) => item.toLowerCase().includes(value.toLowerCase().trim()))
: groceries;
const options = filteredOptions.map((item) => (
<Combobox.Option value={item} key={item}>
{item}
</Combobox.Option>
));
useEffect(() => {
// we need to wait for options to render before we can select first one
combobox.selectFirstOption();
}, [value]);
return (
<Combobox
store={combobox}
onOptionSubmit={(optionValue) => {
setValue(optionValue);
combobox.closeDropdown();
}}
>
<Combobox.Target>
<TextInput
label="Pick value or type anything"
placeholder="Pick value or type anything"
value={value}
onChange={(event) => {
setValue(event.currentTarget.value);
combobox.openDropdown();
}}
onClick={() => combobox.openDropdown()}
onFocus={() => combobox.openDropdown()}
onBlur={() => combobox.closeDropdown()}
/>
</Combobox.Target>
<Combobox.Dropdown>
<Combobox.Options>
{options.length === 0 ? <Combobox.Empty>Nothing found</Combobox.Empty> : options}
</Combobox.Options>
</Combobox.Dropdown>
</Combobox>
);
}Active option
Set active prop on Combobox.Option component to mark it as active.
By default, an active option does not have any styles, you can use data-combobox-active
data attribute to style it.
combobox.selectActiveOption function selects active option. Usually, it is called
when the dropdown is opened:
import { useState } from 'react';
import { Input, InputBase, Combobox, useCombobox, CheckIcon, Group } from '@mantine/core';
const groceries = ['🍎 Apples', '🍌 Bananas', '🥦 Broccoli', '🥕 Carrots', '🍫 Chocolate'];
function Demo() {
const combobox = useCombobox({
onDropdownClose: () => combobox.resetSelectedOption(),
onDropdownOpen: (eventSource) => {
if (eventSource === 'keyboard') {
combobox.selectActiveOption();
} else {
combobox.updateSelectedOptionIndex('active');
}
},
});
const [value, setValue] = useState<string | null>('🥦 Broccoli');
const options = groceries.map((item) => (
<Combobox.Option value={item} key={item} active={item === value}>
<Group gap="xs">
{item === value && <CheckIcon size={12} />}
<span>{item}</span>
</Group>
</Combobox.Option>
));
return (
<Combobox
store={combobox}
resetSelectionOnOptionHover
onOptionSubmit={(val) => {
setValue(val);
combobox.updateSelectedOptionIndex('active');
}}
>
<Combobox.Target targetType="button">
<InputBase
component="button"
type="button"
pointer
rightSection={<Combobox.Chevron />}
rightSectionPointerEvents="none"
onClick={() => combobox.toggleDropdown()}
>
{value || <Input.Placeholder>Pick value</Input.Placeholder>}
</InputBase>
</Combobox.Target>
<Combobox.Dropdown>
<Combobox.Options>{options}</Combobox.Options>
</Combobox.Dropdown>
</Combobox>
);
}Options groups
Render Combobox.Option components inside Combobox.Group to create options group.
Combobox.Group label will be automatically hidden if the group does not have any
children.
import { useState } from 'react';
import { Input, InputBase, Combobox, useCombobox } from '@mantine/core';
function Demo() {
const combobox = useCombobox({
onDropdownClose: () => combobox.resetSelectedOption(),
});
const [value, setValue] = useState<string | null>(null);
return (
<Combobox
store={combobox}
onOptionSubmit={(val) => {
setValue(val);
combobox.closeDropdown();
}}
>
<Combobox.Target>
<InputBase
component="button"
type="button"
pointer
rightSection={<Combobox.Chevron />}
rightSectionPointerEvents="none"
onClick={() => combobox.toggleDropdown()}
>
{value || <Input.Placeholder>Pick value</Input.Placeholder>}
</InputBase>
</Combobox.Target>
<Combobox.Dropdown>
<Combobox.Options>
<Combobox.Group label="Fruits">
<Combobox.Option value="🍎 Apples">🍎 Apples</Combobox.Option>
<Combobox.Option value="🍌 Bananas">🍌 Bananas</Combobox.Option>
<Combobox.Option value="🍇 Grape">🍇 Grape</Combobox.Option>
</Combobox.Group>
<Combobox.Group label="Vegetables">
<Combobox.Option value="🥦 Broccoli">🥦 Broccoli</Combobox.Option>
<Combobox.Option value="🥕 Carrots">🥕 Carrots</Combobox.Option>
<Combobox.Option value="🥬 Lettuce">🥬 Lettuce</Combobox.Option>
</Combobox.Group>
</Combobox.Options>
</Combobox.Dropdown>
</Combobox>
);
}Scrollable list
Set max-height style on either Combobox.Dropdown or Combobox.Options to make the
options list scrollable. You can use mah style prop to set
max-height.
import { useState } from 'react';
import { Input, InputBase, Combobox, useCombobox } from '@mantine/core';
const groceries = [
'🍎 Apples',
'🍌 Bananas',
'🥦 Broccoli',
'🥕 Carrots',
'🍫 Chocolate',
'🍇 Grapes',
'🍋 Lemon',
'🥬 Lettuce',
'🍄 Mushrooms',
'🍊 Oranges',
'🥔 Potatoes',
'🍅 Tomatoes',
'🥚 Eggs',
'🥛 Milk',
'🍞 Bread',
'🍗 Chicken',
'🍔 Hamburger',
'🧀 Cheese',
'🥩 Steak',
'🍟 French Fries',
'🍕 Pizza',
'🥦 Cauliflower',
'🥜 Peanuts',
'🍦 Ice Cream',
'🍯 Honey',
'🥖 Baguette',
'🍣 Sushi',
'🥝 Kiwi',
'🍓 Strawberries',
];
function Demo() {
const combobox = useCombobox({
onDropdownClose: () => combobox.resetSelectedOption(),
});
const [value, setValue] = useState<string | null>(null);
const options = groceries.map((item) => (
<Combobox.Option value={item} key={item}>
{item}
</Combobox.Option>
));
return (
<Combobox
store={combobox}
onOptionSubmit={(val) => {
setValue(val);
combobox.closeDropdown();
}}
>
<Combobox.Target>
<InputBase
component="button"
type="button"
pointer
rightSection={<Combobox.Chevron />}
rightSectionPointerEvents="none"
onClick={() => combobox.toggleDropdown()}
>
{value || <Input.Placeholder>Pick value</Input.Placeholder>}
</InputBase>
</Combobox.Target>
<Combobox.Dropdown>
<Combobox.Options mah={200} style={{ overflowY: 'auto' }}>
{options}
</Combobox.Options>
</Combobox.Dropdown>
</Combobox>
);
}Scrollable list with ScrollArea
You can also use ScrollArea or ScrollArea.Autosize components instead of native scrollbars:
import { useState } from 'react';
import { Input, InputBase, Combobox, useCombobox, ScrollArea } from '@mantine/core';
const groceries = [
'🍎 Apples',
'🍌 Bananas',
'🥦 Broccoli',
'🥕 Carrots',
'🍫 Chocolate',
'🍇 Grapes',
'🍋 Lemon',
'🥬 Lettuce',
'🍄 Mushrooms',
'🍊 Oranges',
'🥔 Potatoes',
'🍅 Tomatoes',
'🥚 Eggs',
'🥛 Milk',
'🍞 Bread',
'🍗 Chicken',
'🍔 Hamburger',
'🧀 Cheese',
'🥩 Steak',
'🍟 French Fries',
'🍕 Pizza',
'🥦 Cauliflower',
'🥜 Peanuts',
'🍦 Ice Cream',
'🍯 Honey',
'🥖 Baguette',
'🍣 Sushi',
'🥝 Kiwi',
'🍓 Strawberries',
];
function Demo() {
const combobox = useCombobox({
onDropdownClose: () => combobox.resetSelectedOption(),
});
const [value, setValue] = useState<string | null>(null);
const options = groceries.map((item) => (
<Combobox.Option value={item} key={item}>
{item}
</Combobox.Option>
));
return (
<Combobox
store={combobox}
onOptionSubmit={(val) => {
setValue(val);
combobox.closeDropdown();
}}
>
<Combobox.Target>
<InputBase
component="button"
type="button"
pointer
rightSection={<Combobox.Chevron />}
rightSectionPointerEvents="none"
onClick={() => combobox.toggleDropdown()}
>
{value || <Input.Placeholder>Pick value</Input.Placeholder>}
</InputBase>
</Combobox.Target>
<Combobox.Dropdown>
<Combobox.Options>
<ScrollArea.Autosize type="scroll" mah={200}>
{options}
</ScrollArea.Autosize>
</Combobox.Options>
</Combobox.Dropdown>
</Combobox>
);
}Hide dropdown
Set hidden prop on Combobox.Dropdown to hide the dropdown. For example,
it can be useful when you want to show the dropdown only when there is at least
one option available:
import { useState } from 'react';
import { Combobox, TextInput, useCombobox } from '@mantine/core';
const groceries = ['🍎 Apples', '🍌 Bananas', '🥦 Broccoli', '🥕 Carrots', '🍫 Chocolate'];
function Demo() {
const combobox = useCombobox();
const [value, setValue] = useState('');
const shouldFilterOptions = !groceries.some((item) => item === value);
const filteredOptions = shouldFilterOptions
? groceries.filter((item) => item.toLowerCase().includes(value.toLowerCase().trim()))
: groceries;
const options = filteredOptions.map((item) => (
<Combobox.Option value={item} key={item}>
{item}
</Combobox.Option>
));
return (
<Combobox
store={combobox}
onOptionSubmit={(optionValue) => {
setValue(optionValue);
combobox.closeDropdown();
}}
>
<Combobox.Target>
<TextInput
label="Pick value or type anything"
placeholder="Pick value or type anything"
value={value}
onChange={(event) => {
setValue(event.currentTarget.value);
combobox.openDropdown();
}}
onClick={() => combobox.openDropdown()}
onFocus={() => combobox.openDropdown()}
onBlur={() => combobox.closeDropdown()}
/>
</Combobox.Target>
<Combobox.Dropdown hidden={options.length === 0}>
<Combobox.Options>{options}</Combobox.Options>
</Combobox.Dropdown>
</Combobox>
);
}Control dropdown opened state
To control the dropdown opened state, pass opened to useCombobox hook:
Dropdown is opened/closed when button is clicked
import { useState } from 'react';
import { TextInput, Button, Combobox, useCombobox } from '@mantine/core';
const groceries = ['🍎 Apples', '🍌 Bananas', '🥦 Broccoli', '🥕 Carrots', '🍫 Chocolate'];
function Demo() {
const [opened, setOpened] = useState(false);
const combobox = useCombobox({ opened });
const options = groceries.map((item) => (
<Combobox.Option value={item} key={item}>
{item}
</Combobox.Option>
));
return (
<>
<Button mb="md" onClick={() => setOpened((o) => !o)}>
Toggle dropdown
</Button>
<Combobox store={combobox}>
<Combobox.Target>
<TextInput
label="Autocomplete"
description="Dropdown is opened/closed when button is clicked"
placeholder="Click button to toggle dropdown"
/>
</Combobox.Target>
<Combobox.Dropdown>
<Combobox.Options>{options}</Combobox.Options>
</Combobox.Dropdown>
</Combobox>
</>
);
}Popover props
Combobox supports most of Popover props. For example,
you can control dropdown position with position prop and disable Floating UI
middlewares with middlewares prop:
import { useState } from 'react';
import { Input, InputBase, Combobox, useCombobox } from '@mantine/core';
const groceries = ['🍎 Apples', '🍌 Bananas', '🥦 Broccoli', '🥕 Carrots', '🍫 Chocolate'];
function Demo() {
const combobox = useCombobox({
onDropdownClose: () => combobox.resetSelectedOption(),
});
const [value, setValue] = useState<string | null>(null);
const options = groceries.map((item) => (
<Combobox.Option value={item} key={item}>
{item}
</Combobox.Option>
));
return (
<Combobox
store={combobox}
position="bottom"
middlewares={{ flip: false, shift: false }}
onOptionSubmit={(val) => {
setValue(val);
combobox.closeDropdown();
}}
>
<Combobox.Target>
<InputBase
component="button"
type="button"
pointer
rightSection={<Combobox.Chevron />}
rightSectionPointerEvents="none"
onClick={() => combobox.toggleDropdown()}
>
{value || <Input.Placeholder>Pick value</Input.Placeholder>}
</InputBase>
</Combobox.Target>
<Combobox.Dropdown>
<Combobox.Options>{options}</Combobox.Options>
</Combobox.Dropdown>
</Combobox>
);
}Without dropdown
You can use Combobox without dropdown. To do so, use Combobox.EventsTarget instead
of Combobox.Target:
import { useState } from 'react';
import { Combobox, TextInput } from '@mantine/core';
function Demo() {
const [value, setValue] = useState('');
return (
<Combobox onOptionSubmit={setValue}>
<Combobox.EventsTarget>
<TextInput
placeholder="Pick value"
value={value}
onChange={(event) => setValue(event.currentTarget.value)}
/>
</Combobox.EventsTarget>
<Combobox.Options mt="sm">
<Combobox.Option value="First">First</Combobox.Option>
<Combobox.Option value="Second">Second</Combobox.Option>
<Combobox.Option value="Third">Third</Combobox.Option>
</Combobox.Options>
</Combobox>
);
}Styles API
Combobox supports the Styles API; you can add styles to any inner element of the component with the classNames prop. Follow the Styles API documentation to learn more.
Component Styles API
Hover over selectors to highlight corresponding elements
/*
* Hover over selectors to apply outline styles
*
*/Virtualization
useVirtualizedCombobox hook can be used to create comboboxes with virtualized options list.
Note that due to the nature of virtualization, the hook requires some additional configuration
compared to the useCombobox hook. In useVirtualizedCombobox hook all operations related to
option indexing do not rely on the actual DOM structure, instead, you need to preserve values in
React state and pass them to the hook.
useVirtualizedCombobox does not depend on any specific virtualization library.
The recommended option is @tanstack/react-virtual:
import { useState } from 'react';
import { useVirtualizer } from '@tanstack/react-virtual';
import { Combobox, Input, InputBase, ScrollArea, useVirtualizedCombobox } from '@mantine/core';
const ITEM_HEIGHT = 36;
const largeData = Array(10000)
.fill(0)
.map((_, index) => ({
value: `value-${index}`,
label: `Label ${index}`,
id: `item-${index}`,
disabled: false,
}));
function Demo() {
const [opened, setOpened] = useState(false);
const [selectedOptionIndex, setSelectedOptionIndex] = useState(-1);
const [activeOptionIndex, setActiveOptionIndex] = useState(-1);
const [value, setValue] = useState('');
const [scrollParent, setScrollParent] = useState<HTMLDivElement | null>(null);
const virtualizer = useVirtualizer({
count: largeData.length,
getScrollElement: () => scrollParent,
estimateSize: () => ITEM_HEIGHT,
overscan: 5,
});
const combobox = useVirtualizedCombobox({
opened,
onOpenedChange: setOpened,
onDropdownOpen: () => {
if (activeOptionIndex !== -1) {
setSelectedOptionIndex(activeOptionIndex);
requestAnimationFrame(() => {
virtualizer.scrollToIndex(activeOptionIndex, { align: 'auto' });
});
}
},
isOptionDisabled: (index) => largeData[index].disabled,
totalOptionsCount: largeData.length,
getOptionId: (index) => largeData[index].id,
selectedOptionIndex,
activeOptionIndex,
setSelectedOptionIndex: (index) => {
setSelectedOptionIndex(index);
if (index !== -1) {
virtualizer.scrollToIndex(index, { align: 'auto' });
}
},
onSelectedOptionSubmit: onOptionSubmit,
});
function onOptionSubmit(index: number) {
const option = largeData[index];
setValue(option.value);
setActiveOptionIndex(index);
combobox.closeDropdown();
combobox.resetSelectedOption();
}
return (
<Combobox store={combobox} resetSelectionOnOptionHover={false} keepMounted>
<Combobox.Target>
<InputBase component="button" onClick={() => combobox.toggleDropdown()} pointer>
{value || <Input.Placeholder>Pick a value</Input.Placeholder>}
</InputBase>
</Combobox.Target>
<Combobox.Dropdown>
<Combobox.Options>
<ScrollArea.Autosize
mah={220}
type="scroll"
scrollbarSize={4}
viewportRef={setScrollParent}
onMouseDown={(event) => event.preventDefault()}
>
<div style={{ height: virtualizer.getTotalSize(), position: 'relative' }}>
{virtualizer.getVirtualItems().map((virtualItem) => {
const item = largeData[virtualItem.index];
return (
<Combobox.Option
value={item.value}
key={item.value}
active={virtualItem.index === activeOptionIndex}
selected={virtualItem.index === selectedOptionIndex}
onClick={() => onOptionSubmit(virtualItem.index)}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: virtualItem.size,
transform: `translateY(${virtualItem.start}px)`,
}}
>
{item.label}
</Combobox.Option>
);
})}
</div>
</ScrollArea.Autosize>
</Combobox.Options>
</Combobox.Dropdown>
</Combobox>
);
}Example of implementation with react-virtuoso:
import { useRef, useState } from 'react';
import { Virtuoso, VirtuosoHandle } from 'react-virtuoso';
import { Combobox, Input, InputBase, ScrollArea, useVirtualizedCombobox } from '@mantine/core';
const largeData = Array(10000)
.fill(0)
.map((_, index) => ({
value: `value-${index}`,
label: `Label ${index}`,
id: `item-${index}`,
disabled: false,
}));
function Demo() {
const [opened, setOpened] = useState(false);
const [selectedOptionIndex, setSelectedOptionIndex] = useState(-1);
const [activeOptionIndex, setActiveOptionIndex] = useState(-1);
const [value, setValue] = useState('');
const virtuoso = useRef<VirtuosoHandle>(null);
const [scrollParent, setScrollParent] = useState<HTMLDivElement | null>(null);
const combobox = useVirtualizedCombobox({
opened,
onOpenedChange: setOpened,
onDropdownOpen: () => {
if (activeOptionIndex !== -1) {
setSelectedOptionIndex(activeOptionIndex);
requestAnimationFrame(() => {
virtuoso.current?.scrollToIndex({ index: activeOptionIndex });
});
}
},
isOptionDisabled: (index) => largeData[index].disabled,
totalOptionsCount: largeData.length,
getOptionId: (index) => largeData[index].id,
selectedOptionIndex,
activeOptionIndex,
setSelectedOptionIndex: (index) => {
setSelectedOptionIndex(index);
if (index !== -1) {
virtuoso.current?.scrollIntoView({ index });
}
},
onSelectedOptionSubmit: onOptionSubmit,
});
function onOptionSubmit(index: number) {
const option = largeData[index];
setValue(option.value);
setActiveOptionIndex(index);
combobox.closeDropdown();
combobox.resetSelectedOption();
}
return (
<Combobox store={combobox} resetSelectionOnOptionHover={false} keepMounted>
<Combobox.Target>
<InputBase component="button" onClick={() => combobox.toggleDropdown()} pointer>
{value || <Input.Placeholder>Pick a value</Input.Placeholder>}
</InputBase>
</Combobox.Target>
<Combobox.Dropdown>
<Combobox.Options>
<ScrollArea.Autosize
mah={220}
type="scroll"
scrollbarSize={4}
viewportRef={setScrollParent}
onMouseDown={(event) => event.preventDefault()}
>
<Virtuoso
data={largeData}
ref={virtuoso}
style={{ height: 420 }}
customScrollParent={scrollParent ?? undefined}
itemContent={(index, item) => (
<Combobox.Option
value={item.value}
key={item.value}
active={index === activeOptionIndex}
selected={index === selectedOptionIndex}
onClick={() => onOptionSubmit(index)}
>
{item.label}
</Combobox.Option>
)}
/>
</ScrollArea.Autosize>
</Combobox.Options>
</Combobox.Dropdown>
</Combobox>
);
}You can find more virtualization examples on the Combobox examples page.
Hook options:
export interface UseVirtualizedComboboxOptions {
/** Default value for `dropdownOpened`, `false` by default */
defaultOpened?: boolean;
/** Controlled `dropdownOpened` state */
opened?: boolean;
/** Called when `dropdownOpened` state changes */
onOpenedChange?: (opened: boolean) => void;
/** Called when dropdown closes */
onDropdownClose?: (eventSource: ComboboxDropdownEventSource) => void;
/** Called when dropdown opens */
onDropdownOpen?: (eventSource: ComboboxDropdownEventSource) => void;
/** Determines whether arrow key presses should loop though items (first to last and last to first), `true` by default */
loop?: boolean;
/** Function to determine whether the option is disabled */
isOptionDisabled?: (optionIndex: number) => boolean;
/** Total number of options in the virtualized list. Required for proper keyboard navigation and index calculations. */
totalOptionsCount: number;
/** Function that returns the id of the option at the given index. Required for setting aria attributes and element references. */
getOptionId: (index: number) => string | null;
/** Current selected option index. Must be controlled by parent component. */
selectedOptionIndex: number;
/** Callback to update the selected option index. Called when user navigates or selects options. */
setSelectedOptionIndex: (index: number) => void;
/** Currently active/highlighted option index. Used to determine which option to select when selectActiveOption is called. */
activeOptionIndex?: number;
/** Called when the selected option is submitted (e.g., via Enter key or clicking). Receives the selected option index. */
onSelectedOptionSubmit: (index: number) => void;
}