molecule

MultiSelectDropdown

Multi-select dropdown with checkboxes, grouping support, async/static options, and search filtering.

Overview

The MultiSelectDropdown component provides a dropdown for selecting multiple items from a list. It supports both static and async option sources, grouped options, search filtering, and displays selected count in the trigger. Composes Popover internally.

import { MultiSelectDropdown } from '@enara-health/ui-react';

<MultiSelectDropdown
  values={selectedValues}
  onChange={setSelectedValues}
  options={[
    { key: 'cardio', label: 'Cardiology' },
    { key: 'neuro', label: 'Neurology' },
    { key: 'ortho', label: 'Orthopedics' },
    { key: 'peds', label: 'Pediatrics' },
  ]}
  placeholder="Select specialties..."
/>

Props

Prop Type Default Description
values string[] - Required. Array of selected option keys
onChange (values: string[]) => void - Required. Callback when selection changes
options MultiSelectOption[] - Static array of options (use this or fetchOptions)
fetchOptions (searchTerm: string) => Promise<MultiSelectOption[]> - Async function to fetch options (use this or options)
placeholder string 'Select...' Placeholder text when nothing is selected
searchable boolean true Whether to show the search input
matchStrategy 'contains' | 'starts-with' | 'fuzzy' | (label, search) => boolean 'contains' How to filter static options against search input. Pass a custom function for advanced matching.
emptyMessage string 'No options found' Message shown when no options match the search
className string - Additional CSS class names

MultiSelectOption

Property Type Description
key string Unique identifier for the option
label string Display text for the option
group string Optional group name for categorizing options

Variants

Static Options

Pass a fixed list of options via the options prop.

<MultiSelectDropdown
  values={selectedValues}
  onChange={setSelectedValues}
  options={[
    { key: 'a', label: 'Option A' },
    { key: 'b', label: 'Option B' },
    { key: 'c', label: 'Option C' },
  ]}
/>

Grouped Options

Add a group field to options to organize them under section headers.

<MultiSelectDropdown
  values={selectedValues}
  onChange={setSelectedValues}
  options={[
    { key: 'cardio', label: 'Cardiology', group: 'Medical' },
    { key: 'neuro', label: 'Neurology', group: 'Medical' },
    { key: 'cbt', label: 'CBT', group: 'Behavioral' },
    { key: 'dbt', label: 'DBT', group: 'Behavioral' },
  ]}
  placeholder="Select specialties..."
/>

Async Options

Use fetchOptions for server-side search and dynamic option loading.

const fetchTags = async (search: string) => {
  const res = await fetch(`/api/tags?q=${search}`);
  const data = await res.json();
  return data.map((t) => ({ key: t.id, label: t.name }));
};

<MultiSelectDropdown
  values={selectedTags}
  onChange={setSelectedTags}
  fetchOptions={fetchTags}
  placeholder="Select tags..."
/>

Match Strategy

Control how static options are filtered with the matchStrategy prop. Defaults to 'contains'. You can also pass a custom function.

Contains (default)

Match anywhere — try "an"

Starts with

Prefix only — try "b"

Fuzzy

Characters in order — try "hnd"

// Built-in strategies
<MultiSelectDropdown matchStrategy="contains" ... />
<MultiSelectDropdown matchStrategy="starts-with" ... />
<MultiSelectDropdown matchStrategy="fuzzy" ... />

// Custom function
<MultiSelectDropdown
  matchStrategy={(label, search) => label.startsWith(search)}
  ...
/>

Non-Searchable

Set searchable={false} to hide the search input for small, fixed lists.

<MultiSelectDropdown
  values={selectedValues}
  onChange={setSelectedValues}
  options={statusOptions}
  searchable={false}
  placeholder="Filter by status..."
/>

Examples

Filter Bar Integration

Use in a filter bar to filter table data by multiple categories.

<FilterBar>
  <MultiSelectDropdown
    values={statusFilter}
    onChange={setStatusFilter}
    options={[
      { key: 'active', label: 'Active' },
      { key: 'pending', label: 'Pending' },
      { key: 'inactive', label: 'Inactive' },
    ]}
    searchable={false}
    placeholder="Status"
  />
  <MultiSelectDropdown
    values={roleFilter}
    onChange={setRoleFilter}
    options={roleOptions}
    placeholder="Roles"
  />
</FilterBar>

Role Assignment

Assign multiple roles to a user within a form.

<FormField label="Roles">
  <MultiSelectDropdown
    values={user.roles}
    onChange={(roles) => setUser(prev => ({ ...prev, roles }))}
    options={[
      { key: 'admin', label: 'Administrator', group: 'System' },
      { key: 'editor', label: 'Editor', group: 'Content' },
      { key: 'viewer', label: 'Viewer', group: 'Content' },
      { key: 'billing', label: 'Billing Manager', group: 'Finance' },
    ]}
    placeholder="Assign roles..."
  />
</FormField>

Accessibility

  • Pattern: Implements a multi-select listbox with checkbox items.
  • Keyboard Support: Tab through options, Space to toggle selection, search input filters options as you type.
  • Screen Reader: Trigger announces the selected count (e.g., "3 selected") or the single selected label.
  • Focus Management: Focus moves to the search input when the dropdown opens.
  • WCAG Compliance: Meets AA standards for multi-select form controls.

Best Practices

  • Use for selecting multiple items — for single selection, use Select or AutocompleteSelect
  • Set searchable={false} for short, fixed option lists (under 8 items)
  • Use groups to organize options into logical categories
  • Provide a clear placeholder that describes what can be selected
  • Wrap in FormField for label, description, and error support