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
SelectorAutocompleteSelect -
Set
searchable={false}for short, fixed option lists (under 8 items) - Use groups to organize options into logical categories
-
Provide a clear
placeholderthat describes what can be selected -
Wrap in
FormFieldfor label, description, and error support