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:

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} />;
}

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

// 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:

<MultiSelect
data={data}
label="Your favorite frameworks/libraries"
placeholder="Pick all that you like"
searchable
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:

<MultiSelect
data={data}
label="Your favorite frameworks/libraries"
placeholder="Pick all that you like"
defaultValue={['react', 'next']}
clearButtonLabel="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(['React', 'Angular', 'Svelte', 'Vue']);
return (
<MultiSelect
label="Creatable MultiSelect"
data={data}
placeholder="Select items"
searchable
creatable
getCreateLabel={(query) => `+ Create ${query}`}
onCreate={(query) => setData((current) => [...current, query])}
/>
);
}

Group items

<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' },
]}
/>

Disable items

<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 in px:

const data = Array(50).fill(0).map((_, index) => `Item ${index}`);
<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 and valueComponent which will consume data objects
// Minimum data used in previous examples
[
{ value: 'react', label: 'React' },
{ value: 'ng', label: 'Angular' },
{ value: 'svelte', label: 'Svelte' },
{ value: 'vue', label: 'Vue' },
];

You can add any other fields to your 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',
},
];

Based on this data shape you can create custom filter function and itemComponent:

import { forwardRef } from 'react';
import { Group, Avatar, Text, MultiSelect } 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',
},
];
// !important: Forwarding ref is required
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"
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 { CloseButton, MultiSelect, Box } from '@mantine/core';
function Value({ value, label, onRemove, classNames, ...others }) {
return (
<div {...others}>
<Box
sx={(theme) => ({
display: 'flex',
cursor: 'default',
alignItems: 'center',
border: `1px solid ${theme.colors.gray[4]}`,
paddingLeft: 10,
borderRadius: 4,
})}
>
<div style={{ marginRight: 10 }}>
<FlagIcon />
</div>
<div style={{ lineHeight: 1, fontSize: 12 }}>{label}</div>
<CloseButton onMouseDown={onRemove} variant="transparent" size={22} iconSize={14} tabIndex={-1} />
</Box>
</div>
);
}
const Item = forwardRef(({ label, value, ...others }, ref) => (
<div ref={ref} {...others}>
<Box sx={{ display: 'flex' }}>
<Box mr={10}>
<FlagIcon />
</Box>
<div>{label}</div>
</Box>
</div>
));
export function CountriesSelect() {
return (
<MultiSelect
data={countriesData}
limit={20}
valueComponent={Value}
itemComponent={Item}
searchable
defaultValue={['US', 'DE']}
placeholder="Pick countries"
label="Which countries you visited last year?"
/>
);
}

Max selected values

<MultiSelect maxSelectedValues={3} />

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
<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
<MultiSelect
transitionDuration={150}
transition="pop-top-left"
transitionTimingFunction="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:

<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:

<MultiSelect icon={<Hash />} />

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
<MultiSelect disabled />

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:

<MultiSelect rightSection={<ChevronDownIcon />} styles={{ rightSection: { pointerEvents: 'none' } }} />

Input props

Component supports all props from Input and InputWrapper components:

Radius
xs
sm
md
lg
xl
Size
xs
sm
md
lg
xl
<MultiSelect
placeholder="Pick all you like"
label="Your favorite frameworks/libraries"
required
data={['React', 'Angular', 'Svelte', 'Vue']}
/>

Get input ref

You can get input ref by passing ref prop to Select component:

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

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 clearButtonLabel:

<MultiSelect clearable clearButtonLabel="Clear select field" />
Build fully functional accessible web applications faster than ever
Feedback
Your feedback is most valuable contribution to the project, please share how you use Mantine, what features are missing and what is done good
Leave feedback