Skip to main content

MultiSelect

Listboxes are a great foundation for building custom, accessible Multi-Select menus for your app, complete with robust support for keyboard navigation.

import { MultiSelect } from "rizzui";

Default

The default style of MultiSelect component.

Multi Select
import { useState } from 'react'
import { MultiSelect } from "rizzui";

const options = [
{ label: 'Apple 🍎', value: 'apple' },
{ label: 'Banana 🍌', value: 'banana' },
{ label: 'Cherry 🍒', value: 'cherry' },
...
];

export default function App() {
const [value, setValue] = useState([]);
return (
<MultiSelect
value={value}
options={options}
label="Multi Select"
onChange={setValue}
/>
);
}

With Clearable Button

You can clear MultiSelect values using clearable property.

Multi Select
import { useState } from 'react'
import { MultiSelect } from "rizzui";

const options = [
{ label: 'Apple 🍎', value: 'apple' },
{ label: 'Banana 🍌', value: 'banana' },
{ label: 'Cherry 🍒', value: 'cherry' },
...
];

export default function App() {
const [value, setValue] = useState(options[0].value, options[1].value);
return (
<MultiSelect
value={value}
options={options}
label="Multi Select"
onChange={setValue}
clearable={true}
onClear={() => setValue([])}
/>
);
}

With Custom Option

Here is the custom option MultiSelect example.

Multi Select
import { useState } from 'react'
import { MultiSelect, cn } from "rizzui";

const customOptions = [
{
label: "Wolverine",
value: "wolverine@rizzui.io",
avatar: "https://randomuser.me/api/portraits/men/43.jpg",
},
{
label: "MessiJr",
value: "messijr@rizzui.io",
avatar: "https://randomuser.me/api/portraits/women/65.jpg",
},
...
];

export default function App() {
const [value, setValue] = React.useState([customOptions[0].value, customOptions[1].value]);

return (
<MultiSelect
label = "Multi Select",
value={value}
options={customOptions}
onChange={setValue}
displayValue={renderDisplayValue}
getOptionDisplayValue={renderOptionDisplayValue}
optionClassName="p-0"
/>
);
}

function renderDisplayValue(option: MultiSelectOption, handleClearItem: (value: string) => void) {
return (
<div className="flex items-center gap-3 p-1">
<img
src={option.avatar}
alt={option.label}
className="size-8 object-cover rounded-full bg-muted"
/>
<div>
<Text fontWeight="medium">{option.label}</Text>
<Text>{option.value}</Text>
</div>
<span
className="p-1 hover:bg-muted rounded-full cursor-pointer"
onClick={(e) => {
e.stopPropagation();
handleClearItem(option.value);
}}
>
<XMarkIcon className="size-4" />
</span>
</div>
);
}

function renderOptionDisplayValue(option: MultiSelectOption, selected: boolean) {
return (
<div className={cn("flex items-center gap-3 py-1.5 px-3 pe-4 w-full")}>
<img
src={option.avatar}
alt={option.label}
className="size-9 object-cover rounded bg-muted"
/>
<div>
<Text fontWeight="medium">{option.label}</Text>
<Text>{option.value}</Text>
</div>
{selected && <CheckIcon className="ms-auto size-5" />}
</div>
);
}

Multi Select
import { useState } from 'react'
import { MultiSelect, cn } from "rizzui";

const customOptions = [
{
label: "Wolverine",
value: "wolverine@rizzui.io",
avatar: "https://randomuser.me/api/portraits/men/43.jpg",
},
{
label: "MessiJr",
value: "messijr@rizzui.io",
avatar: "https://randomuser.me/api/portraits/women/65.jpg",
},
...
];

export default function App() {
const [value, setValue] = React.useState([customOptions[0].value, customOptions[1].value, customOptions[2].value]);

return (
<MultiSelect
clearable
value={value}
suffix={<></>}
onChange={setValue}
label='Multi Select'
optionClassName="p-0"
options={customOptions}
dropdownClassName="min-w-80"
onClear={() => setValue([])}
displayValue={renderDisplayValue}
getOptionDisplayValue={renderOptionDisplayValue}
/>
);
}

function renderDisplayValue(
selectedItems: string[],
options: MultiSelectOption[],
handleClearItem: (value: string) => void
) {
const filteredItems = options.filter((option) => selectedItems.includes(option.value));
const isEmpty = filteredItems.length === 0;
const isLongerThanTwo = filteredItems.length > 2;

return (
<div className={cn("flex w-full flex-wrap items-center gap-2 text-start", !isEmpty && "me-6")}>
<div className="flex items-center gap-1">
<PlusCircleIcon className="size-5 text-muted-foreground" />
Status
</div>
{isLongerThanTwo ? (
<span className="border-s border-muted ps-2 ms-2">{filteredItems.length} Selected</span>
) : (
<div className="ps-2 border-s border-muted flex items-center gap-2">
{filteredItems.slice(0, 2).map((item) => (
<div className="flex items-center gap-3 border border-muted rounded ps-2">
<Text fontWeight="medium">{item.label}</Text>
<span
className="p-1 hover:bg-muted rounded-full cursor-pointer"
onClick={(e) => {
e.stopPropagation();
handleClearItem(item.value);
}}
>
<XMarkIcon className="size-4" />
</span>
</div>
))}
</div>
)}
</div>
);
}

function renderOptionDisplayValue(option: MultiSelectOption, selected: boolean) {
return (
<div className={cn("flex items-center gap-3 py-1.5 px-3 pe-4 w-full")}>
<img
src={option.avatar}
alt={option.label}
className="size-9 object-cover rounded bg-muted"
/>
<div>
<Text fontWeight="medium">{option.label}</Text>
<Text>{option.value}</Text>
</div>
{selected && <CheckIcon className="ms-auto size-5" />}
</div>
);
}

Hide Selected Options

You can hide MultiSelect options using hideSelectedOptions property.

Multi Select
import { useState } from 'react'
import { MultiSelect } from "rizzui";

const options = [
{ label: 'Apple 🍎', value: 'apple' },
{ label: 'Banana 🍌', value: 'banana' },
{ label: 'Cherry 🍒', value: 'cherry' },
...
];

export default function App() {
const [value, setValue] = useState([]);
return (
<MultiSelect
value={value}
options={options}
label="Multi Select"
onChange={setValue}
hideSelectedOptions={true}
/>
);
}

Use Custom Key as Value

You can use custom option key of MultiSelect options as value property. In this case we are going to use customOptionKey as value. Open the console to see the selected values.

Multi Select
import { useState } from 'react'
import { MultiSelect } from "rizzui";

const options = [
{ label: "Apple 🍎", value: "apple", customOptionKey: "movie" },
{ label: "Banana 🍌", value: "banana", customOptionKey: "footballPlayer" },
{ label: "Cherry 🍒", value: "cherry", customOptionKey: "fight" },
...
];

export default function App() {
const [value, setValue] = useState([]);
return (
<MultiSelect
value={value}
options={options}
onChange={setValue}
label="Multi Select"
getOptionValueKey="customOptionKey"
/>
);
}

With Searchable

You can search inside MultiSelect options.

Multi Select
import { useState } from 'react'
import { MultiSelect } from "rizzui";

const options = [
{ label: "Apple 🍎", value: "apple" },
{ label: "Banana 🍌", value: "banana" },
{ label: "Cherry 🍒", value: "cherry" },
...
];

export default function App() {
const [value, setValue] = useState([]);
return (
<MultiSelect
value={value}
clearable={true}
searchable={true}
options={options}
onChange={setValue}
onClear={() => setValue([])}
label="Multi Select"
/>
);
}

With React Hook Form and Zod Validation

In this example, we are going to use React Hook Form and Zod for validation. Open browser console to see the submitted data.

Multi Select
import { z } from "zod";
import { MultiSelect, Button } from "rizzui";
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";

const options = [
{ label: "Apple 🍎", value: "apple" },
{ label: "Banana 🍌", value: "banana" },
{ label: "Cherry 🍒", value: "cherry" },
...
];

const schema = z.object({
multiSelect: z.array(z.string()).min(1, { message: "Minimum 1 item required!" }),
});

type SchemaType = z.infer<typeof schema>;

export function MultiSelectWithForm() {
const { handleSubmit, control } = useForm({
defaultValues: {
multiSelect: ["apple", "banana"],
},
resolver: zodResolver(schema),
});

const onSubmit = (data: SchemaType) => {
console.log("Submitted data", data);
};

return (
<form
onSubmit={handleSubmit(onSubmit)}
className="w-full max-w-md"
>
<Controller
control={control}
name="multiSelect"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<MultiSelect
label="Multi Select"
value={value}
options={options}
onChange={onChange}
error={error?.message}
className="w-full max-w-md"
clearable={true}
onClear={() => onChange([])}
/>
)}
/>

<Button className="mt-4 w-full">Submit</Button>
</form>
);
}

API Reference


MultiSelect Props

Here is the API documentation of the MultiSelect component.

PropsTypeDescriptionDefault
valuestring[]The selected value. value prop is required.__
optionsMultiSelectOption[]Add options data using this prop. options prop is required.__
onChange(value: string[]) => voidThe function to call when a new option is selected.. onChange prop is required.__
onSearchChange(value: string) => voidThe function to call when type in options search panel.__
onClear() => void clear event__
labelReactNodeSet field label__
labelWeightLabelWeightSet label font weight"medium"
optionCheckBoxbooleanShow checkbox in option list or not."true"
variantMultiSelectVariantsThe variants of the component are:"outline"
sizeMultiSelectSizesThe size of the component. "sm" is equivalent to the dense select styling."md"
roundedMultiSelectRoundedThe rounded variants are:"md"
autoFocusbooleanWhether select is focused by default or not"false"
inPortalbooleanWhether select options is rendered on the portal or not"true"
modalbooleanWhether the body scrollbar is hidden or not when dropdown is visible."false"
gapnumberSets the gap between trigger and dropdown if portal is true"6"
placeholderstringSet select placeholder text"Select..."
disabledbooleanWhether the select is disabled or not__
clearablebooleanAdd clearable option__
prefixReactNodeThe prefix is design for adding any icon or text on the Select field's start (it's a left icon for the ltr and right icon for the rtl)__
suffixReactNodeThe suffix is design for adding any icon or text on the Select field's end (it's a right icon for the ltr and left icon for the rtl)__
helperTextReactNodeAdd helper text. It could be string or a React component__
errorstringShow error message using this prop__
displayValuefunctionA function to determine the display value of the selected item. @param selectedItems - @param options - @param handleClearItem - The value of the selected item, @returns ReactNode to display for the selected item.__
getOptionDisplayValuefunctionUse this function when you want to change the default options look. @param option - @param selected - The SelectOption for which to get the display value, @returns ReactNode to display for the specified option.__
getOptionValueKeystringCan use any property as value inside your options object rather then value__
hideSelectedOptionsbooleanRemoves the selected items from options dropdown if set to truefalse
classNamestringAdd custom classes to the root of the component__
labelClassNamestringOverride default CSS style of label__
selectClassNamestringOverride default CSS style of select button__
dropdownClassNamestringOverride default CSS style of select dropdown__
optionClassNamestringOverride default CSS style of select option__
prefixClassNamestringOverride default CSS style of prefix__
suffixClassNamestringOverride default CSS style of suffix__
helperClassNamestringOverride default CSS style of helperText__
errorClassNamestringOverride default CSS style of error message__
selectContainerClassNamestringAdd custom class to the container of selected values__
selectedItemClassNamestringAdd custom class to the selected item__
selectedOptionClassNamestringAdd custom class to the selected options__
searchablebooleanIs select options searchable or not"false"
searchPlaceholderstringSet search input placeholder text"Search..."
searchTypetext searchSet search input type"text"
searchReadOnlybooleanSet search input is readonly or not"false"
searchDisabledbooleanSet search input is disabled or not"false"
searchPrefixReactNodeThe prefix is design for adding any icon or text on the Search field's start (it's a left icon for the ltr and right icon for the rtl)__
searchSuffixReactNodeThe suffix is design for adding any icon or text on the Search field's end (it's a right icon for the ltr and left icon for the rtl)__
searchContainerClassNamestringOverride default CSS style of search root__
searchPrefixClassNamestringOverride default CSS style of search prefix__
searchClassNamestringOverride default CSS style of search input__
searchSuffixClassNamestringOverride default CSS style of search suffix__
searchByKeystringSet a custom key to search in options'label'
searchPropsHTMLInputElementSet's extra attributes for search input element__

MultiSelect Option type

type MultiSelectOption = {
label: string;
value: string;
disabled?: boolean;
[key: string]: any;
};

Label Weight

type LabelWeight = "normal" | "medium" | "semibold" | "bold";

MultiSelect Variants

type MultiSelectVariants = "outline" | "flat" | "text";

MultiSelect Sizes

type MultiSelectSizes = "sm" | "md" | "lg" | "xl";

MultiSelect Rounded

type MultiSelectRounded = "sm" | "md" | "lg" | "none" | "pill";