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.
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.
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.
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>
);
}
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.
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.
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.
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.
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.
Props | Type | Description | Default |
---|---|---|---|
value | string[] | The selected value. value prop is required. | __ |
options | MultiSelectOption[] | Add options data using this prop. options prop is required. | __ |
onChange | (value: string[]) => void | The function to call when a new option is selected.. onChange prop is required. | __ |
onSearchChange | (value: string) => void | The function to call when type in options search panel. | __ |
onClear | () => void | clear event | __ |
label | ReactNode | Set field label | __ |
labelWeight | LabelWeight | Set label font weight | "medium" |
optionCheckBox | boolean | Show checkbox in option list or not. | "true" |
variant | MultiSelectVariants | The variants of the component are: | "outline" |
size | MultiSelectSizes | The size of the component. "sm" is equivalent to the dense select styling. | "md" |
rounded | MultiSelectRounded | The rounded variants are: | "md" |
autoFocus | boolean | Whether select is focused by default or not | "false" |
inPortal | boolean | Whether select options is rendered on the portal or not | "true" |
modal | boolean | Whether the body scrollbar is hidden or not when dropdown is visible. | "false" |
gap | number | Sets the gap between trigger and dropdown if portal is true | "6" |
placeholder | string | Set select placeholder text | "Select..." |
disabled | boolean | Whether the select is disabled or not | __ |
clearable | boolean | Add clearable option | __ |
prefix | ReactNode | The 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 ) | __ |
suffix | ReactNode | The 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 ) | __ |
helperText | ReactNode | Add helper text. It could be string or a React component | __ |
error | string | Show error message using this prop | __ |
displayValue | function | A 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. | __ |
getOptionDisplayValue | function | Use 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. | __ |
getOptionValueKey | string | Can use any property as value inside your options object rather then value | __ |
hideSelectedOptions | boolean | Removes the selected items from options dropdown if set to true | false |
className | string | Add custom classes to the root of the component | __ |
labelClassName | string | Override default CSS style of label | __ |
selectClassName | string | Override default CSS style of select button | __ |
dropdownClassName | string | Override default CSS style of select dropdown | __ |
optionClassName | string | Override default CSS style of select option | __ |
prefixClassName | string | Override default CSS style of prefix | __ |
suffixClassName | string | Override default CSS style of suffix | __ |
helperClassName | string | Override default CSS style of helperText | __ |
errorClassName | string | Override default CSS style of error message | __ |
selectContainerClassName | string | Add custom class to the container of selected values | __ |
selectedItemClassName | string | Add custom class to the selected item | __ |
selectedOptionClassName | string | Add custom class to the selected options | __ |
searchable | boolean | Is select options searchable or not | "false" |
searchPlaceholder | string | Set search input placeholder text | "Search..." |
searchType | text search | Set search input type | "text" |
searchReadOnly | boolean | Set search input is readonly or not | "false" |
searchDisabled | boolean | Set search input is disabled or not | "false" |
searchPrefix | ReactNode | The 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 ) | __ |
searchSuffix | ReactNode | The 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 ) | __ |
searchContainerClassName | string | Override default CSS style of search root | __ |
searchPrefixClassName | string | Override default CSS style of search prefix | __ |
searchClassName | string | Override default CSS style of search input | __ |
searchSuffixClassName | string | Override default CSS style of search suffix | __ |
searchByKey | string | Set a custom key to search in options | 'label' |
searchProps | HTMLInputElement | Set'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";