vera logoVera UI
ComponentsForm Components

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-ui
npm install @helgadigitals/vera-ui
yarn add @helgadigitals/vera-ui

Usage

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