Multi Select
A powerful multi-select component that allows users to choose multiple values from a dropdown list. Features include search functionality, select all/clear options, badge display for selected items, and customizable animations.
Installation
pnpm add @helgadigitals/vera-uinpm install @helgadigitals/vera-uiyarn add @helgadigitals/vera-uiUsage
import { useState } from "react"import { MultiSelect } from "@helgadigitals/vera-ui"const options = [{ value: "react", label: "React" },{ value: "vue", label: "Vue" },{ value: "angular", label: "Angular" },{ value: "svelte", label: "Svelte" },]export default function Example() {const [selectedValues, setSelectedValues] = useState([])return ( <MultiSelect options={options} onValueChange={setSelectedValues} defaultValue={selectedValues} placeholder="Select frameworks" maxCount={3} />)}Examples
Basic Multi Select
Select fruits to see them listed here
import { useState } from "react"import { MultiSelect } from "@helgadigitals/vera-ui"const fruits = [{ value: "apple", label: "Apple" },{ value: "banana", label: "Banana" },{ value: "orange", label: "Orange" },{ value: "grape", label: "Grape" },{ value: "strawberry", label: "Strawberry" },]export default function BasicExample() {const [selectedFruits, setSelectedFruits] = useState([])return ( <div className="space-y-4"> <div className="space-y-2"> <label className="text-sm font-medium">Choose your favorite fruits</label> <MultiSelect options={fruits} onValueChange={setSelectedFruits} defaultValue={selectedFruits} placeholder="Select fruits" className="w-full max-w-md" /> </div> {selectedFruits.length > 0 && ( <div> <p className="text-sm text-muted-foreground"> Selected: {selectedFruits.join(", ")} </p> </div> )} </div>)}Examples
With Icons
import { useState } from "react"import { Code, Database, Globe, Smartphone } from "lucide-react"import { MultiSelect } from "@helgadigitals/vera-ui"const technologies = [{ value: "frontend", label: "Frontend", icon: Globe },{ value: "backend", label: "Backend", icon: Database },{ value: "mobile", label: "Mobile", icon: Smartphone },{ value: "devops", label: "DevOps", icon: Code },]export default function WithIconsExample() {const [selectedTech, setSelectedTech] = useState([])return ( <div className="space-y-2"> <label className="text-sm font-medium">Technology areas</label> <MultiSelect options={technologies} onValueChange={setSelectedTech} defaultValue={selectedTech} placeholder="Select technology areas" className="w-full max-w-md" /> </div>)}Different Variants
import { useState } from "react"import { MultiSelect } from "@helgadigitals/vera-ui"const colors = [{ value: "red", label: "Red" },{ value: "blue", label: "Blue" },{ value: "green", label: "Green" },{ value: "yellow", label: "Yellow" },]export default function VariantsExample() {const [defaultColors, setDefaultColors] = useState([])const [secondaryColors, setSecondaryColors] = useState([])const [destructiveColors, setDestructiveColors] = useState([])return ( <div className="space-y-6"> <div className="space-y-2"> <label className="text-sm font-medium">Default Variant</label> <MultiSelect options={colors} onValueChange={setDefaultColors} defaultValue={defaultColors} placeholder="Select colors" variant="default" className="w-full max-w-md" /> </div> <div className="space-y-2"> <label className="text-sm font-medium">Secondary Variant</label> <MultiSelect options={colors} onValueChange={setSecondaryColors} defaultValue={secondaryColors} placeholder="Select colors" variant="secondary" className="w-full max-w-md" /> </div> <div className="space-y-2"> <label className="text-sm font-medium">Destructive Variant</label> <MultiSelect options={colors} onValueChange={setDestructiveColors} defaultValue={destructiveColors} placeholder="Select colors" variant="destructive" className="w-full max-w-md" /> </div> </div>)}With Animation
Click the sparkle icon to animate selected badges
import { useState } from "react"import { MultiSelect } from "@helgadigitals/vera-ui"const countries = [{ value: "us", label: "United States" },{ value: "ca", label: "Canada" },{ value: "uk", label: "United Kingdom" },{ value: "de", label: "Germany" },{ value: "fr", label: "France" },]export default function AnimationExample() {const [selectedCountries, setSelectedCountries] = useState([])return ( <div className="space-y-2"> <label className="text-sm font-medium">Countries (with animation)</label> <MultiSelect options={countries} onValueChange={setSelectedCountries} defaultValue={selectedCountries} placeholder="Select countries" animation={0.5} className="w-full max-w-md" /> <p className="text-xs text-muted-foreground"> Click the sparkle icon to animate selected badges </p> </div>)}Custom Max Count
import { useState } from "react"import { MultiSelect } from "@helgadigitals/vera-ui"const skills = [{ value: "javascript", label: "JavaScript" },{ value: "typescript", label: "TypeScript" },{ value: "react", label: "React" },{ value: "vue", label: "Vue" },{ value: "angular", label: "Angular" },{ value: "nodejs", label: "Node.js" },{ value: "python", label: "Python" },{ value: "java", label: "Java" },]export default function MaxCountExample() {const [selectedSkills, setSelectedSkills] = useState([])return ( <div className="space-y-4"> {/* Show only 2 badges */} <div className="space-y-2"> <label className="text-sm font-medium">Skills (max 2 visible)</label> <MultiSelect options={skills} onValueChange={setSelectedSkills} defaultValue={selectedSkills} placeholder="Select skills" maxCount={2} className="w-full max-w-md" /> </div> {/* Show 5 badges */} <div className="space-y-2"> <label className="text-sm font-medium">Skills (max 5 visible)</label> <MultiSelect options={skills} onValueChange={setSelectedSkills} defaultValue={selectedSkills} placeholder="Select skills" maxCount={5} className="w-full max-w-md" /> </div> </div>)}Form Integration
import { useForm, Controller } from "react-hook-form"import { MultiSelect, Button } from "@helgadigitals/vera-ui"const interests = [{ value: "technology", label: "Technology" },{ value: "sports", label: "Sports" },{ value: "music", label: "Music" },{ value: "art", label: "Art" },{ value: "travel", label: "Travel" },{ value: "food", label: "Food" },]export default function FormExample() {const { control, handleSubmit, formState: { errors } } = useForm()const onSubmit = (data) => { console.log(data)}return ( <form onSubmit={handleSubmit(onSubmit)} className="space-y-6"> <div className="space-y-2"> <label className="text-sm font-medium">Interests *</label> <Controller name="interests" control={control} rules={{ required: "Please select at least one interest", validate: (value) => value?.length >= 2 || "Please select at least 2 interests" }} render={({ field }) => ( <MultiSelect options={interests} onValueChange={field.onChange} formValue={field.value || []} placeholder="Select your interests" className="w-full" /> )} /> {errors.interests && ( <p className="text-sm text-destructive"> {errors.interests.message} </p> )} </div> <div className="space-y-2"> <label className="text-sm font-medium">Skills (Optional)</label> <Controller name="skills" control={control} render={({ field }) => ( <MultiSelect options={[ { value: "coding", label: "Coding" }, { value: "design", label: "Design" }, { value: "marketing", label: "Marketing" }, { value: "management", label: "Management" }, ]} onValueChange={field.onChange} formValue={field.value || []} placeholder="Select your skills" maxCount={2} className="w-full" /> )} /> </div> <Button type="submit">Submit</Button> </form>)}Modal Popover
Modal popover blocks interaction with other elements
import { useState } from "react"import { MultiSelect } from "@helgadigitals/vera-ui"const languages = [{ value: "en", label: "English" },{ value: "es", label: "Spanish" },{ value: "fr", label: "French" },{ value: "de", label: "German" },{ value: "ja", label: "Japanese" },{ value: "zh", label: "Chinese" },]export default function ModalExample() {const [selectedLanguages, setSelectedLanguages] = useState([])return ( <div className="space-y-2"> <label className="text-sm font-medium">Languages (modal)</label> <MultiSelect options={languages} onValueChange={setSelectedLanguages} defaultValue={selectedLanguages} placeholder="Select languages" modalPopover={true} className="w-full max-w-md" /> <p className="text-xs text-muted-foreground"> Modal popover blocks interaction with other elements </p> </div>)}Large Dataset
import { useState } from "react"import { MultiSelect } from "@helgadigitals/vera-ui"// Generate a large list of optionsconst generateOptions = (count: number) => {return Array.from({ length: count }, (_, i) => ({ value: `option-${i + 1}`, label: `Option ${i + 1}`,}))}export default function LargeDatasetExample() {const [selectedOptions, setSelectedOptions] = useState([])const options = generateOptions(100)return ( <div className="space-y-2"> <label className="text-sm font-medium"> Large dataset (100 options) - use search to filter </label> <MultiSelect options={options} onValueChange={setSelectedOptions} defaultValue={selectedOptions} placeholder="Search and select options" maxCount={5} className="w-full max-w-md" /> </div>)}API Reference
Props
Prop
Type
Option Object
Prop
Type
Features
Selection Management
- Multiple Selection: Select/deselect multiple options
- Select All: Toggle all options at once
- Clear All: Remove all selections
- Individual Removal: Remove individual selections via badge close buttons
Search and Filtering
- Real-time Search: Type to filter options instantly
- Keyboard Navigation: Navigate with arrow keys and select with Enter
- No Results: Shows "No results found" when search yields no matches
Visual Feedback
- Badge Display: Selected items shown as removable badges
- Count Overflow: Shows "+X more" when exceeding maxCount
- Animation Support: Optional bounce animation for selected badges
- Variants: Different visual styles for badges
Accessibility
- Keyboard Support: Full keyboard navigation and selection
- Screen Reader: Proper ARIA attributes and announcements
- Focus Management: Proper focus handling throughout interaction
Accessibility
The MultiSelect component follows WAI-ARIA guidelines:
- Keyboard Navigation:
- Space or Enter opens the dropdown
- Arrow keys navigate between options
- Type to search for options
- Escape closes the dropdown
- Backspace removes last selected item when search is empty
- Screen Readers: Proper ARIA attributes and selection announcements
- Focus Management: Focus is properly managed between trigger and content
Search Functionality: Users can type while the dropdown is open to filter options. The search is case-insensitive and matches option labels.
Badge Variants
The selected items are displayed as badges with different visual styles:
- Default: Standard badge appearance
- Secondary: Muted badge appearance
- Destructive: Red badge appearance for warnings/errors
- Inverted: Inverted color scheme
Best Practices
Provide Clear Labels
Always include descriptive labels for your multi-select components:
// Good: Clear label
<div className="space-y-2">
<label className="text-sm font-medium">
Programming Languages
</label>
<MultiSelect
options={languages}
onValueChange={setSelectedLanguages}
placeholder="Select programming languages"
/>
</div>Set Appropriate Max Count
Consider the available space when setting maxCount:
// For narrow containers
<MultiSelect maxCount={2} />
// For wider containers
<MultiSelect maxCount={5} />Use Icons for Better UX
Icons help users quickly identify options:
const options = [
{ value: "email", label: "Email", icon: Mail },
{ value: "phone", label: "Phone", icon: Phone },
{ value: "sms", label: "SMS", icon: MessageSquare },
]Styling
The MultiSelect component supports custom styling through CSS classes:
<MultiSelect
className="w-full border-blue-300 focus-within:border-blue-500"
variant="secondary"
// ... other props
/>The badges inherit the variant styling but can also be customized through the component's CSS variables.
Label
A label component built on Radix UI's Label primitive that provides accessible labeling for form controls. It automatically associates with form elements and supports proper focus management.
Radio Group
A radio group is a set of checkable buttons where only one can be checked at a time. Built on Radix UI's RadioGroup primitive, it provides accessible single-choice selection with keyboard navigation.