Multi Select
rivelle/multi-selectFreeComponent
A searchable multiple-value picker with limits, select-all and compact value summaries.
Live preview
live · rendered by the registry
Rendered by rivelle on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://rivelle.dev/r/multi-select.jsonSource
1"use client";23import * as React from "react";4import { Check, ChevronsUpDown, X } from "lucide-react";56import { cn } from "@/lib/utils";7import { Button } from "@/components/ui/button";8import {9 Command,10 CommandEmpty,11 CommandGroup,12 CommandInput,13 CommandItem,14 CommandList,15 CommandSeparator,16} from "@/components/ui/command";17import {18 Popover,19 PopoverContent,20 PopoverTrigger,21} from "@/components/ui/popover";2223type MultiSelectOption = {24 value: string;25 label: string;26 disabled?: boolean;27};2829type MultiSelectProps = {30 options: MultiSelectOption[];31 value?: string[];32 defaultValue?: string[];33 onValueChange?: (value: string[]) => void;34 placeholder?: string;35 searchPlaceholder?: string;36 emptyText?: string;37 maxSelected?: number;38 maxCount?: number;39 selectAllText?: string;40 clearText?: string;41 closeOnSelect?: boolean;42 disabled?: boolean;43 className?: string;44 contentClassName?: string;45};4647function MultiSelect({48 options,49 value,50 defaultValue = [],51 onValueChange,52 placeholder = "Select options",53 searchPlaceholder = "Search options...",54 emptyText = "No results found.",55 maxSelected,56 maxCount = 2,57 selectAllText = "Select all",58 clearText = "Clear selection",59 closeOnSelect = false,60 disabled,61 className,62 contentClassName,63}: MultiSelectProps) {64 const [open, setOpen] = React.useState(false);65 const [internalValue, setInternalValue] = React.useState(defaultValue);66 const selectedValues = value ?? internalValue;67 const selectedSet = React.useMemo(68 () => new Set(selectedValues),69 [selectedValues],70 );71 const selectedOptions = options.filter((option) =>72 selectedSet.has(option.value),73 );74 const enabledOptions = options.filter((option) => !option.disabled);75 const allSelected =76 enabledOptions.length > 0 &&77 enabledOptions.every((option) => selectedSet.has(option.value));78 const canSelectAll =79 maxSelected === undefined || maxSelected >= enabledOptions.length;8081 const updateValue = (next: string[]) => {82 if (value === undefined) setInternalValue(next);83 onValueChange?.(next);84 };8586 const toggle = (option: MultiSelectOption) => {87 if (option.disabled) return;8889 if (selectedSet.has(option.value)) {90 updateValue(selectedValues.filter((item) => item !== option.value));91 } else if (92 maxSelected === undefined ||93 selectedValues.length < maxSelected94 ) {95 updateValue([...selectedValues, option.value]);96 }9798// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from rivelle
All 83 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Accordionaccordion | rivelle | Components | Free | 2 deps | |
| Alertalert | rivelle | Components | Free | 1 dep | |
| Alert Dialogalert-dialog | rivelle | Components | Free | 1 dep | |
| Aspect Ratioaspect-ratio | rivelle | Components | Free | 1 dep | |
| Avataravatar | rivelle | Components | Free | 1 dep | |
| Badgebadge | rivelle | Components | Free | 2 deps | |
| Breadcrumbbreadcrumb | rivelle | Components | Free | 2 deps | |
| Buttonbutton | rivelle | Components | Free | 2 deps |
