MultiSelect

Custom searchable multi select
Import

Usage

MultiSelect component allows user to pick any amount of option from the given data:

A bare minimum example:

import { MultiSelect } from '@mantine/core';
const data = [
{ value: 'react', label: 'React' },
{ value: 'ng', label: 'Angular' },
{ value: 'svelte', label: 'Svelte' },
{ value: 'vue', label: 'Vue' },
{ value: 'riot', label: 'Riot' },
{ value: 'next', label: 'Next.js' },
{ value: 'blitz', label: 'Blitz.js' },
];
function Demo() {
return (
<MultiSelect
data={data}
label="Your favorite frameworks/libraries"
placeholder="Pick all that you like"
/>
);
}

Controlled

import { useState } from 'react';
import { MultiSelect } from '@mantine/core';
function Demo() {
const [value, setValue] = useState([]);
return <MultiSelect value={value} onChange={setValue} data={[]} />;
}

Note that MultiSelect value should always be an array of either string or undefined:

// Incorrect, will have bugs
<MultiSelect data={[{ value: 1, label: '1' }]} value={[1]} />
// Correct, works as expected
<MultiSelect data={[{ value: '1', label: '1' }]} value={['1']} />

Data prop

MultiSelect support two different data formats:

  1. An array of strings – use when you do not need to customize item component or display label different than value
  2. An array of objects with required value and label properties and any other additional properties
// Data as an array of strings, will be mapped to
// [
// { value: 'React', label: 'React' },
// { value: 'Angular', label: 'Angular' },
// { value: 'Svelte', label: 'Svelte' },
// { value: 'Vue', label: 'Vue' },
// ]
<MultiSelect data={['React', 'Angular', 'Svelte', 'Vue']} />
// Data as an array of objects:
// Minimal example (same as first example above)
<MultiSelect data={[
{ value: 'React', label: 'React' },
{ value: 'Angular', label: 'Angular' },
{ value: 'Svelte', label: 'Svelte' },
{ value: 'Vue', label: 'Vue' },
]} />
// Additional data properties for custom item component (see documentation below)
<MultiSelect
valueComponent={({ value, label, image, name }) => /* Your custom value component with data properties */}
itemComponent={({ value, label, image, name }) => /* Your custom item component with data properties */}
data={[
{
value: 'bob@handsome.inc',
label: 'bob@handsome.inc',
image: 'image-link',
name: 'Bob Handsome',
},
{
value: 'bill@outlook.com',
label: 'bill@outlook.com',
image: 'image-link',
name: 'Bill Rataconda',
},
{
value: 'amy@wong.cn',
label: 'amy@wong.cn',
image: 'image-link',
name: 'Amy Wong',
},
]}
/>

Searchable

Set searchable prop to enable search in select and nothingFound prop to provide label that will be shown when no options were found:

import { MultiSelect } from '@mantine/core';
function Demo() {
return (
<MultiSelect
data={['React', 'Angular', 'Svelte', 'Vue', 'Riot', 'Next.js', 'Blitz.js']}
label="Your favorite frameworks/libraries"
placeholder="Pick all that you like"
searchable
nothingFound="Nothing found"
/>
);
}

Controlled search

Set searchValue and onSearchChange prop to enable controlled search in select:

import { MultiSelect } from '@mantine/core';
function Demo() {
const [searchValue, onSearchChange] = useState('');
return (
<MultiSelect
data={['React', 'Angular', 'Svelte', 'Vue', 'Riot', 'Next.js', 'Blitz.js']}
label="Your favorite frameworks/libraries"
placeholder="Pick all that you like"
searchable
searchValue={searchValue}
onSearchChange={onSearchChange}
nothingFound="Nothing found"
/>
);
}

Clearable

Set clearable prop to enable clearing all values at once. When prop is true and at least value is selected clear button will replace chevron in right section:

import { MultiSelect } from '@mantine/core';
function Demo() {
return (
<MultiSelect
data={['React', 'Angular', 'Svelte', 'Vue', 'Riot', 'Next.js', 'Blitz.js']}
label="Your favorite frameworks/libraries"
placeholder="Pick all that you like"
defaultValue={['react', 'next']}
clearButtonProps={{ 'aria-label': 'Clear selection' }}
clearable
/>
);
}

Creatable

Set creatable and getCreateLabel props to enable creating new select item. Note that you will need to handle data state to manage item creation correctly:

import { useState } from 'react';
import { MultiSelect } from '@mantine/core';
function Demo() {
const [data, setData] = useState([
{ value: 'react', label: 'React' },
{ value: 'ng', label: 'Angular' },
]);
return (
<MultiSelect
label="Creatable MultiSelect"
data={data}
placeholder="Select items"
searchable
creatable
getCreateLabel={(query) => `+ Create ${query}`}
onCreate={(query) => {
const item = { value: query, label: query };
setData((current) => [...current, item]);
return item;
}}
/>
);
}

Group items

import { MultiSelect } from '@mantine/core';
function Demo() {
return (
<MultiSelect
label="Your favorite Rick and Morty character"
placeholder="Pick all that you like"
data={[
{ value: 'rick', label: 'Rick', group: 'Used to be a pickle' },
{ value: 'morty', label: 'Morty', group: 'Never was a pickle' },
{ value: 'beth', label: 'Beth', group: 'Never was a pickle' },
{ value: 'summer', label: 'Summer', group: 'Never was a pickle' },
]}
/>
);
}

Disabled state

import { MultiSelect } from '@mantine/core';
function Demo() {
return <MultiSelect disabled />;
}

Disable items

import { MultiSelect } from '@mantine/core';
function Demo() {
return (
<MultiSelect
label="MultiSelect with disabled items"
placeholder="Select items"
data={[
{ value: 'react', label: 'React' },
{ value: 'ng', label: 'Angular', disabled: true },
{ value: 'svelte', label: 'Svelte' },
{ value: 'vue', label: 'Vue', disabled: true },
]}
/>
);
}

Large data set

When dropdown height is exceeded dropdown becomes scrollable, to change max-height set maxDropdownHeight prop with value:

import { MultiSelect } from '@mantine/core';
const data = Array(50).fill(0).map((_, index) => `Item ${index}`);
function Demo() {
return (
<MultiSelect
data={data}
label="Large data set"
placeholder="Scroll to see all options"
maxDropdownHeight={160}
/>
);
}

Custom item component

You can change select item component and filtering logic that is used in search. To do so you will need to:

  • Add extra props to data objects
  • Create filter function which determines whether item should be added to the search results
  • Provide itemComponent which will consume data objects
import { forwardRef } from 'react';
import { MultiSelect, Avatar, Group, Text } from '@mantine/core';
const data = [
{
image: 'https://img.icons8.com/clouds/256/000000/futurama-bender.png',
label: 'Bender Bending Rodríguez',
value: 'Bender Bending Rodríguez',
description: 'Fascinated with cooking',
},
{
image: 'https://img.icons8.com/clouds/256/000000/futurama-mom.png',
label: 'Carol Miller',
value: 'Carol Miller',
description: 'One of the richest people on Earth',
},
{
image: 'https://img.icons8.com/clouds/256/000000/homer-simpson.png',
label: 'Homer Simpson',
value: 'Homer Simpson',
description: 'Overweight, lazy, and often ignorant',
},
{
image: 'https://img.icons8.com/clouds/256/000000/spongebob-squarepants.png',
label: 'Spongebob Squarepants',
value: 'Spongebob Squarepants',
description: 'Not just a sponge',
},
];
interface ItemProps extends React.ComponentPropsWithoutRef<'div'> {
image: string;
label: string;
description: string;
}
const SelectItem = forwardRef<HTMLDivElement, ItemProps>(
({ image, label, description, ...others }: ItemProps, ref) => (
<div ref={ref} {...others}>
<Group noWrap>
<Avatar src={image} />
<div>
<Text>{label}</Text>
<Text size="xs" color="dimmed">
{description}
</Text>
</div>
</Group>
</div>
)
);
function Demo() {
return (
<MultiSelect
label="Choose employees of the month"
placeholder="Pick all you like"
itemComponent={SelectItem}
data={data}
searchable
nothingFound="Nobody here"
maxDropdownHeight={400}
filter={(value, selected, item) =>
!selected &&
(item.label.toLowerCase().includes(value.toLowerCase().trim()) ||
item.description.toLowerCase().includes(value.toLowerCase().trim()))
}
/>
);
}

Custom label component

Apart from itemComponent you can customize appearance of label by providing valueComponent:

import { forwardRef } from 'react';
import {
MultiSelect,
Box,
CloseButton,
SelectItemProps,
MultiSelectValueProps,
rem,
Flex,
} from '@mantine/core';
const countriesData = [
{ label: 'United States', value: 'US' },
{ label: 'Great Britain', value: 'GB' },
{ label: 'Finland', value: 'FI' },
{ label: 'France', value: 'FR' },
{ label: 'Russia', value: 'RU' },
];
const flags = { /* Record with flag icon components */ };
function Value({
value,
label,
onRemove,
classNames,
...others
}: MultiSelectValueProps & { value: string }) {
const Flag = flags[value];
return (
<div {...others}>
<Box
sx={(theme) => ({
display: 'flex',
cursor: 'default',
alignItems: 'center',
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[7] : theme.white,
border: `${rem(1)} solid ${
theme.colorScheme === 'dark' ? theme.colors.dark[7] : theme.colors.gray[4]
}`,
paddingLeft: theme.spacing.xs,
borderRadius: theme.radius.sm,
})}
>
<Box mr={10}>
<Flag />
</Box>
<Box sx={{ lineHeight: 1, fontSize: rem(12) }}>{label}</Box>
<CloseButton
onMouseDown={onRemove}
variant="transparent"
size={22}
iconSize={14}
tabIndex={-1}
/>
</Box>
</div>
);
}
const Item = forwardRef<HTMLDivElement, SelectItemProps>(({ label, value, ...others }, ref) => {
const Flag = flags[value];
return (
<div ref={ref} {...others}>
<Flex align="center">
<Box mr={10}>
<Flag />
</Box>
<div>{label}</div>
</Flex>
</div>
);
});
function Demo() {
return (
<MultiSelect
data={countriesData}
limit={20}
valueComponent={Value}
itemComponent={Item}
searchable
defaultValue={['US', 'FI']}
placeholder="Pick countries"
label="Which countries did you visit last year?"
/>
);
}

Max selected values

import { MultiSelect } from '@mantine/core';
function Demo() {
return <MultiSelect maxSelectedValues={3} />;
}

Disable selected item filtering

import { MultiSelect } from '@mantine/core';
function Demo() {
return (
<MultiSelect
data={['React', 'Angular', 'Svelte', 'Vue', 'Riot', 'Next.js', 'Blitz.js']}
disableSelectedItemFiltering
label="Your favorite frameworks/libraries"
nothingFound="Nothing found"
placeholder="Pick all that you like"
searchable
/>
);
}

When used along filter, be aware that the second parameter selected will always be false.

<MultiSelect
disableSelectedItemFiltering
filter={(value, selected, item) => {
console.log(selected); // false
}}
searchable
/>

Dropdown position

By default, dropdown is placed below the input and when there is not enough space, it flips to be above the input. To change this behavior, set dropdownPosition prop:

DropdownPosition
import { MultiSelect } from '@mantine/core';
function Demo() {
return <MultiSelect />;
}

Performance

If you have a large data set (> 100 items) you will have to optimize items rendering. The best strategy is to use searchable option with limit:

// Only 20 items are rendered at a time
// See countries list example above
<MultiSelect searchable limit={20} />

Animations

By default, dropdown animations are turned off to increase responsiveness. You can enable them by setting optional props:

  • transition – premade transition name or custom transition styles object, see Transition component for all available options
  • transitionDuration – transition duration in ms, defaults to 0
  • transitionTimingFunction – defaults to theme.transitionTimingFunction
import { MultiSelect } from '@mantine/core';
function Demo() {
return (
<MultiSelect
data={[/* ...data */]}
label="Your favorite frameworks/libraries"
placeholder="Pick all that you like"
transitionProps={{ duration: 150, transition: 'pop-top-left', timingFunction: 'ease' }}
/>
);
}

Native scrollbars

By default, MultiSelect uses ScrollArea to render dropdown. If you want to use native scrollbars instead, set div as a dropdown component:

import { MultiSelect } from '@mantine/core';
const data = Array(50).fill(0).map((_, index) => `Item ${index}`);
function Demo() {
return (
<MultiSelect
data={data}
label="MultiSelect with native scrollbars"
placeholder="Dropdown rendered as div element"
dropdownComponent="div"
/>
);
}

With icon

You can use any React node as icon:

import { MultiSelect } from '@mantine/core';
import { IconHash } from '@tabler/icons-react';
function Demo() {
return <MultiSelect icon={<IconHash />} />;
}

Invalid state and error

Pick at least one item
// Error as boolean – red border color
<MultiSelect error />
// Error as React node – red border color and message below input
<MultiSelect error="Pick at least one item" />

Disabled state

In disabled state:

  • options to remove, add or search is disabled
  • input cannot be cleared with clear button
  • cursor is changed to not-allowed
import { MultiSelect } from '@mantine/core';
function Demo() {
return <MultiSelect disabled />;
}

Read only

import { MultiSelect } from '@mantine/core';
const data = [
//...
];
function Demo() {
return (
<MultiSelect
data={data}
label="Read only multiselect"
placeholder="Pick all that you like"
defaultValue={['react', 'ng']}
readOnly
/>
);
}

Right section

You can replace icon in right section with rightSection prop. Note that in this case clearable option will not work and will need to handle it yourself:

import { MultiSelect } from '@mantine/core';
import { IconChevronDown } from '@tabler/icons-react';
function Demo() {
return (
<MultiSelect
data={['React', 'Angular', 'Svelte', 'Vue', 'Riot', 'Next.js', 'Blitz.js']}
label="Your favorite frameworks/libraries"
placeholder="Pick all that you like"
rightSection={<IconChevronDown size="1rem" />}
styles={{ rightSection: { pointerEvents: 'none' } }}
rightSectionWidth={40}
/>
);
}

Input props

Radius
xs
sm
md
lg
xl
Size
xs
sm
md
lg
xl
import { MultiSelect } from '@mantine/core';
function Demo() {
return (
<MultiSelect
data={['React', 'Angular', 'Svelte', 'Vue']}
placeholder="Pick all you like"
label="Your favorite frameworks/libraries"
withAsterisk
/>
);
}

Get input ref

import { useRef } from 'react';
import { MultiSelect } from '@mantine/core';
function Demo() {
const ref = useRef<HTMLInputElement>(null);
return <MultiSelect ref={ref} data={[]} />;
}

Accessibility

Provide aria-label in case you use component without label for screen reader support:

<MultiSelect /> // -> not ok, select is not labeled
<MultiSelect label="My select" /> // -> ok, select and label is connected
<MultiSelect aria-label="My select" /> // -> ok, label is not visible but will be announced by screen reader

If you use clearable option set clear button label:

<MultiSelect clearable clearButtonProps={{ 'aria-label': 'Clear select field' }} />

MultiSelect component props

NameTypeDescription
clearButtonProps
Pick<DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "key" | keyof ButtonHTMLAttributes<...>>
Props added to clear button
clearSearchOnBlur
boolean
Clear search field value on blur
clearSearchOnChange
boolean
Clear search value when item is selected
clearable
boolean
Allow to clear item
creatable
boolean
Allow creatable option
data *
readonly (string | SelectItem)[]
Select data used to render items in dropdown
defaultValue
string[]
Uncontrolled input defaultValue
description
ReactNode
Input description, displayed after label
descriptionProps
Record<string, any>
Props spread to description element
disableSelectedItemFiltering
boolean
Disable removing selected items from the list
disabled
boolean
Disabled input state
dropdownComponent
any
Change dropdown component, can be used to add custom scrollbars
dropdownPosition
"bottom" | "top" | "flip"
Dropdown positioning behavior
error
ReactNode
Displays error message after input
errorProps
Record<string, any>
Props spread to error element
filter
(value: string, selected: boolean, item: SelectItem) => boolean
Function based on which items in dropdown are filtered
getCreateLabel
(query: string) => ReactNode
Function to get create Label
hoverOnSearchChange
boolean
Hovers the first result when search query changes
icon
ReactNode
Adds icon on the left side of input
iconWidth
Width<string | number>
Width of icon section
initiallyOpened
boolean
Initial dropdown opened state
inputContainer
(children: ReactNode) => ReactNode
Input container component, defaults to React.Fragment
inputWrapperOrder
("input" | "label" | "error" | "description")[]
Controls order of the Input.Wrapper elements
itemComponent
FC<any>
Change item renderer
label
ReactNode
Input label, displayed before input
labelProps
Record<string, any>
Props spread to label element
limit
number
Limit amount of items displayed at a time for searchable select
maxDropdownHeight
string | number
Maximum dropdown height
maxSelectedValues
number
Limit amount of items selected
nothingFound
ReactNode
Nothing found label
onChange
(value: string[]) => void
Controlled input onChange handler
onCreate
(query: string) => string | SelectItem
Called when create option is selected
onDropdownClose
() => void
Called when dropdown is closed
onDropdownOpen
() => void
Called when dropdown is opened
onSearchChange
(query: string) => void
Called each time search query changes
portalProps
Omit<PortalProps, "children" | "withinPortal">
Props to pass down to the portal when withinPortal is true
positionDependencies
any[]
useEffect dependencies to force update dropdown position
radius
number | "xs" | "sm" | "md" | "lg" | "xl"
Key of theme.radius or any valid CSS value to set border-radius, theme.defaultRadius by default
required
boolean
Adds required attribute to the input and red asterisk on the right side of label Sets required on input element
rightSection
ReactNode
Right section of input, similar to icon but on the right
rightSectionProps
Record<string, any>
Props spread to rightSection div element
rightSectionWidth
Width<string | number>
Width of right section, is used to calculate input padding-right
searchValue
string
Controlled search input value
searchable
boolean
Enable items searching
selectOnBlur
boolean
Select highlighted item on blur
shadow
MantineShadow
Dropdown shadow from theme or any value to set box-shadow
shouldCreate
(query: string, data: SelectItem[]) => boolean
Function to determine if create label should be displayed
size
"xs" | "sm" | "md" | "lg" | "xl"
Input size
switchDirectionOnFlip
boolean
Whether to switch item order and keyboard navigation on dropdown position flip
transitionProps
Partial<Omit<TransitionProps, "mounted">>
Props added to Transition component that used to animate dropdown presence, use to configure duration and animation type, { duration: 0, transition: 'fade' } by default
value
string[]
Controlled input value
valueComponent
FC<any>
Component used to render values
variant
Variants<"unstyled" | "default" | "filled">
Defines input appearance, defaults to default in light color scheme and filled in dark
withAsterisk
boolean
Determines whether required asterisk should be rendered, overrides required prop, does not add required attribute to the input
withinPortal
boolean
Whether to render the dropdown in a Portal
wrapperProps
Record<string, any>
Properties spread to root element
zIndex
ZIndex
Dropdown z-index

MultiSelect component Styles API

NameStatic selectorDescription
wrapper.mantine-MultiSelect-wrapperRoot Input element
dropdown.mantine-MultiSelect-dropdownDropdown element
item.mantine-MultiSelect-itemItem element, rendered inside dropdown
nothingFound.mantine-MultiSelect-nothingFoundNothing found label
values.mantine-MultiSelect-valuesValues wrapper
value.mantine-MultiSelect-valueValue element
searchInput.mantine-MultiSelect-searchInputSearch input, rendered after all values
defaultValue.mantine-MultiSelect-defaultValueDefault value component wrapper
defaultValueRemove.mantine-MultiSelect-defaultValueRemoveDefault value remove control
defaultValueLabel.mantine-MultiSelect-defaultValueLabelDefault value label
separator.mantine-MultiSelect-separatorDivider wrapper
separatorLabel.mantine-MultiSelect-separatorLabelDivider Label
itemsWrapper.mantine-MultiSelect-itemsWrapperWraps all items in dropdown
icon.mantine-MultiSelect-iconInput icon wrapper on the left side of the input, controlled by icon prop
input.mantine-MultiSelect-inputMain input element
root.mantine-MultiSelect-rootRoot element
label.mantine-MultiSelect-labelLabel element styles, defined by label prop
error.mantine-MultiSelect-errorError element styles, defined by error prop
description.mantine-MultiSelect-descriptionDescription element styles, defined by description prop
required.mantine-MultiSelect-requiredRequired asterisk element styles, defined by required prop