# @stackline/react-multiselect-dropdown
> Maintained React 19 multiselect dropdown with accessibility-focused and keyboard/ARIA tested support, controlled state, slots, headless/state hooks, Stackline skins, body overlays, live docs, search, grouping, lazy loading, and custom renderers.
[](https://www.npmjs.com/package/@stackline/react-multiselect-dropdown)
[](https://github.com/alexandroit/react-multiselect-dropdown)
[](https://github.com/alexandroit/react-multiselect-dropdown)
[](https://alexandro.net/docs/react/multiselect/)
[](https://www.reddit.com/r/Stackline/)
**[Documentation](https://alexandro.net/docs/react/multiselect/)** | **[npm](https://www.npmjs.com/package/@stackline/react-multiselect-dropdown)** | **[Issues](https://github.com/alexandroit/react-multiselect-dropdown/issues)** | **[Repository](https://github.com/alexandroit/react-multiselect-dropdown)**
**Current package version:** `19.1.8`
---
## Why this package?
`@stackline/react-multiselect-dropdown` provides a maintained React multiselect component for applications that need predictable selection state, search, grouping, skins, keyboard support, and live tested examples.
The package is built around a controlled React API: pass `data`, bind `selectedItems`, receive updates through `onChange`, and customize behavior through a `settings` object. It also supports a Slots API for replacing component structure without losing ARIA/focus behavior, a headless `useMultiSelectDropdown` hook, a lower-level `useMultiSelectState` hook, custom React render functions for option rows and selected badges, lazy loading callbacks, imperative `ref` methods, and body-overlay positioning for dialogs or clipped containers.
This README documents React 19 package `19.1.6`. It keeps the guided structural slots, headless and state hooks, type-safe factory, and combobox contract while refreshing the tested React runtime and the security-sensitive browser test toolchain without changing the public API.
## Compatibility
| Item | Value |
| --- | --- |
| Package | `@stackline/react-multiselect-dropdown@19.1.8` |
| Peer: `react` | `>=19.0.0 <20.0.0` |
| Peer: `react-dom` | `>=19.0.0 <20.0.0` |
| Runtime dependencies | None; framework peers are supplied by the application |
| Package format | ESM, CommonJS, and TypeScript declarations |
### React Version Compatibility
Each package family installs on its matching React family. Keep the package family aligned with the React major used by your application.
| Package family | React family | Peer range | Tested release window | Demo link |
| :---: | :---: | :---: | :---: | :--- |
| **17.x** | **React 17 only** | **`>=17.0.0 <18.0.0`** | **17.0.2 -> 17.0.2** | [React 17 family docs](https://alexandro.net/docs/react/multiselect/react-17/) |
| **18.x** | **React 18 only** | **`>=18.0.0 <19.0.0`** | **18.0.2 -> 18.3.1** | [React 18 family docs](https://alexandro.net/docs/react/multiselect/react-18/) |
| **19.x** | **React 19 only** | **`>=19.0.0 <20.0.0`** | **19.1.5 -> 19.2.8** | [React 19 family docs](https://alexandro.net/docs/react/multiselect/react-19/) |
## Installation
```bash
npm install @stackline/react-multiselect-dropdown@19.1.8 --save-exact
```
Install `19.1.6` for React 19.x applications. The styled component includes its component styles and injects them at runtime. The headless hook does not inject CSS and lets your application own the markup and styling. This maintenance release is API-compatible with the 19.1 line and is tested against React 19.2.8.
## Usage
### Setup
#### 1. Import the component
```tsx
import { MultiSelectDropdown } from '@stackline/react-multiselect-dropdown';
import type {
DropdownSettings,
MultiSelectDropdownHandle
} from '@stackline/react-multiselect-dropdown';
```
#### 2. Keep selection in React state
```tsx
const [selectedCountries, setSelectedCountries] = useState([]);
```
#### 3. Pass a stable settings object
```tsx
const settings = useMemo>(
() => ({
text: 'Select countries',
enableSearchFilter: true,
primaryKey: 'id',
labelKey: 'itemName',
badgeShowLimit: 3,
skin: 'classic'
}),
[]
);
```
### Basic Usage
```tsx
import { useState } from 'react';
import {
MultiSelectDropdown,
type DropdownSettings
} from '@stackline/react-multiselect-dropdown';
type Country = {
id: number;
itemName: string;
capital: string;
region: string;
};
const countries: Country[] = [
{ id: 1, itemName: 'Brazil', capital: 'Brasilia', region: 'South America' },
{ id: 2, itemName: 'Canada', capital: 'Ottawa', region: 'North America' },
{ id: 3, itemName: 'Portugal', capital: 'Lisbon', region: 'Europe' },
{ id: 4, itemName: 'United States', capital: 'Washington, DC', region: 'North America' },
{ id: 5, itemName: 'Argentina', capital: 'Buenos Aires', region: 'South America' },
{ id: 6, itemName: 'Mexico', capital: 'Mexico City', region: 'North America' }
];
export function CountrySelector() {
const [selectedCountries, setSelectedCountries] = useState([
countries[1]
]);
const settings = useMemo>(
() => ({
singleSelection: false,
text: 'Select countries',
selectAllText: 'Select all',
unSelectAllText: 'Clear all',
enableSearchFilter: true,
searchPlaceholderText: 'Search',
primaryKey: 'id',
labelKey: 'itemName',
badgeShowLimit: 4,
maxHeight: 260,
showCheckbox: true,
noDataLabel: 'No data',
skin: 'classic',
appendToBody: false
}),
[]
);
return (
console.log('selected', item)}
onDeSelect={(item) => console.log('removed', item)}
onSelectAll={(items) => console.log('selected all', items)}
onDeSelectAll={(items) => console.log('cleared all', items)}
/>
);
}
```
## Features
| Feature | Supported |
| :--- | :---: |
| React 19 tested release line | Yes |
| Multi-select and single-select modes | Yes |
| Controlled and uncontrolled selection | Yes |
| Guided Slots API for custom HTML structure | Yes |
| Headless `useMultiSelectDropdown` hook | Yes |
| State-only `useMultiSelectState` hook | Yes |
| Search and filter | Yes |
| Group by field | Yes |
| Custom item render functions | Yes |
| Custom badge render functions | Yes |
| Lazy loading hooks | Yes |
| Add-new-item from search text | Yes |
| Ref methods for open, close, focus, select all, and clear | Yes |
| Built-in `classic`, `material`, `dark`, `custom`, and `brand` skins | Yes |
| Accessibility-focused and keyboard/ARIA tested navigation, focus states, and ARIA labels | Yes |
| Multiselect options expose both `aria-selected` and `aria-checked` | Yes |
| Backspace/Escape combobox contract | Yes |
| Async add-item stale response protection | Yes |
| Selected object preservation across async data refreshes | Yes |
| Dialog and overflow-container support through `appendToBody` / `tagToBody` | Yes |
| Left-aligned, vertically centered placeholder and single-value text | Yes |
| Versioned docs builds per React line | Yes |
## Security
See the [security policy](https://github.com/alexandroit/react-multiselect-dropdown/blob/main/SECURITY.md) for supported release lines and private vulnerability reporting.
Keep framework peers and application dependencies patched. Dependency audits cover known advisories; they do not establish that an application is secure.
Option identifier normalization trims adversarial runs in linear time while preserving the existing identifier and selection contract.
## API Surface
### Styling and Skins
Use `settings.skin` to switch the visual mode:
```ts
setSettings((current) => ({
...current,
skin: 'material'
}));
```
Built-in skins:
| Skin | Usage |
| :--- | :--- |
| `classic` | Compact classic dropdown styling. |
| `material` | Material-style rounded controls and chips. |
| `dark` | Dark UI surfaces. |
| `custom` | CSS-variable starter skin for custom projects. |
| `brand` | Stackline brand skin. |
`settings.theme` is accepted as a legacy alias, but new React usage should configure only `settings.skin`.
### Customization Paths
Use the API layer that matches the amount of control your team needs:
| Layer | Best for | What you own |
| :--- | :--- | :--- |
| `` | Fast forms, filters, dashboards, and admin screens. | Data, selected state, settings, events, and optional render functions. |
| `createMultiSelectDropdown()` | Teams that want to bind the item type once for a feature or design-system wrapper. | A typed component family, typed settings, typed slots, and typed hooks. |
| `slots` | Custom HTML around the proven component behavior. | Specific DOM pieces such as trigger, badges, menu, search, groups, options, and footer. |
| `useMultiSelectDropdown` | Fully custom interfaces and design systems. | All markup and CSS, while Stackline provides state, ARIA prop getters, keyboard flow, grouping, and callbacks. |
| `useMultiSelectState` | Advanced state engines or existing combobox shells. | Every element, every event binding, and all visual behavior. |
For most teams, start with the component. Use `slots` when the component works but your layout needs a different shell. Use the headless hooks when the application must own the whole combobox contract.
### Type-Safe Factory
Use `createMultiSelectDropdown()` when a feature, package, or design-system wrapper should bind the item type once and reuse it across the component, settings, slots, and hooks.
This is optional. The normal `` API remains the fastest path for most screens.
```tsx
import { useMemo, useState } from 'react';
import {
createMultiSelectDropdown
} from '@stackline/react-multiselect-dropdown';
type Country = {
id: number;
itemName: string;
capital: string;
region: string;
};
const CountryMultiselect = createMultiSelectDropdown();
const countrySettings = CountryMultiselect.defineSettings({
text: 'Choose countries',
primaryKey: 'id',
labelKey: 'itemName',
searchBy: ['itemName', 'capital'],
groupBy: 'region',
enableSearchFilter: true,
badgeShowLimit: 2
});
const countrySlots = CountryMultiselect.defineSlots({
Option: ({ props, option, checkbox }) => (