ComboboxMultiSelect

ComboboxMultiSelect allows users to choose multiple options from a predefined list. When the input is focused, a popover appears below the input, which displays a list of all available options. Users can filter the options based on string match, by typing into the input element. Each selected option is presented as a Tag, which includes a dismiss button to remove that selection. selected.

The options must be known as the components are rendered, which means this component is not suitable for server-side searches.

Available from eds-core/1.20.0

Quick Start

Installation
npm install @adaptavant/eds-core
Import
import { ComboboxMultiSelect } from '@adaptavant/eds-core';

Open Menu

By default, the menu opens when the user types in the trigger input. Also, using menuTrigger="focus" prop allows you to open the menu when the input is focused.

const initialOptions = [
	{
		id: "opiasri",
		name: "Oscar Piastri",
	},
	{
		id: "dricciardo",
		name: "Daniel Ricciardo",
	},
	{
		id: "kchandhok",
		name: "Karun Chandhok",
	},
	{
		id: "jdaruvala",
		name: "Jehan Daruvala",
	},
	{
		id: "rkubia",
		name: "Robert Kubica",
	},
];

const selectionReducer = (state, action) => {
	switch (action.type) {
		case "ADD":
			return [...state, action.payload];
		case "REMOVE":
			return state.filter((item) => item.id !== action.payload.id);
		case 'DROP_LAST':
			if(state.length === 0) return state;
			return state.slice(0, -1);
		default:
			return state;
		}
};

const [selectedOptions, dispatch] = React.useReducer(selectionReducer, []);
const [searchTerm, setSearchTerm] = React.useState("");

const filteredOptions = React.useMemo(() => {
	if (searchTerm === "") return initialOptions;
	return initialOptions.filter((item) =>
		item.name.toLowerCase().includes(searchTerm.toLowerCase())
	);
}, [initialOptions, searchTerm]);

const handleSelectionChange = (item) => {
	if(!item) return;
	const isSelected = selectedOptions.some(
		(selected) => selected.id === item.id
	);
	dispatch({
		type: isSelected ? "REMOVE" : "ADD",
		payload: item,
	});
	setSearchTerm("");
};

return (
	<Field label="Select Items">
		<ComboboxMultiSelect
			inputValue={searchTerm}
			menuTrigger="focus"
			onClearInput={() => setSearchTerm("")}
			onRemoveLast={() => dispatch({ type: 'DROP_LAST' })}
			onInputChange={setSearchTerm}
			onSelectionChange={handleSelectionChange}
			options={filteredOptions}
			selectedKey="id"
			selectedOptions={selectedOptions}
		>
			<ComboboxMultiSelectTextInput
				placeholder="Search..."
				renderItem={({ name }) => name}
			/>
			<ComboboxMultiSelectPopover>
				<ComboboxMultiSelectListbox
					noResultsFallback={
						<Text className="text-secondary text-center text-body-12 py-4">
							No matching results
						</Text>
					}
					options={filteredOptions}
				>
					{(item) => (
						<ComboboxMultiSelectItem option={item}>
							{item.name}
						</ComboboxMultiSelectItem>
					)}
				</ComboboxMultiSelectListbox>
			</ComboboxMultiSelectPopover>
		</ComboboxMultiSelect>
	</Field>
);

Style API

Our design system components include style props that allow you to easily customize different parts of each component to match your design needs.

Please refer to the Style API documentation for more insights.

ComboboxMultiSelect parts

const initialOptions = [
	{
		id: "opiasri",
		name: "Oscar Piastri",
	},
	{
		id: "dricciardo",
		name: "Daniel Ricciardo",
	},
	{
		id: "kchandhok",
		name: "Karun Chandhok",
	},
	{
		id: "jdaruvala",
		name: "Jehan Daruvala",
	},
	{
		id: "rkubia",
		name: "Robert Kubica",
	},
];

const selectionReducer = (state, action) => {
	switch (action.type) {
		case "ADD":
			return [...state, action.payload];
		case "REMOVE":
			return state.filter((item) => item.id !== action.payload.id);
		case 'DROP_LAST':
			if(state.length === 0) return state;
			return state.slice(0, -1);
		default:
			return state;
		}
};

const [selectedOptions, dispatch] = React.useReducer(selectionReducer, []);
const [searchTerm, setSearchTerm] = React.useState("");

const filteredOptions = React.useMemo(() => {
	if (searchTerm === "") return initialOptions;
	return initialOptions.filter((item) =>
		item.name.toLowerCase().includes(searchTerm.toLowerCase())
	);
}, [initialOptions, searchTerm]);

const handleSelectionChange = (item) => {
	if(!item) return;
	const isSelected = selectedOptions.some(
		(selected) => selected.id === item.id
	);
	dispatch({
		type: isSelected ? "REMOVE" : "ADD",
		payload: item,
	});
	setSearchTerm("");
};

return (
	<Field label="Select Items">
		<ComboboxMultiSelect
			inputValue={searchTerm}
			menuTrigger="focus"
			onClearInput={() => setSearchTerm("")}
			onInputChange={setSearchTerm}
			onRemoveLast={() => dispatch({ type: 'DROP_LAST' })}
			onSelectionChange={handleSelectionChange}
			options={filteredOptions}
			selectedKey="id"
			selectedOptions={selectedOptions}
		>
			<ComboboxMultiSelectTextInput
				className="bg-palette-violet-background"
				classNames={{
					focusIndicator: 'border-palette-violet-border',
					tag: 'bg-palette-blue-background',
					tagLabel: 'text-palette-blue-text',
					tagCloseButtonIcon: 'fill-palette-blue-border',
				}}
				placeholder="Search..."
				renderItem={({ name }) => name}
			/>
			<ComboboxMultiSelectPopover className="bg-palette-violet-background">
				<ComboboxMultiSelectListbox
					noResultsFallback={
						<Text className="text-secondary text-center text-body-12 py-4">
							No matching results
						</Text>
					}
					options={filteredOptions}
				>
					{(item) => (
						<ComboboxMultiSelectItem
							option={item}
							className={`
								bg-palette-violet-background
								active:bg-palette-violet-background-active
								active:data-[highlighted]:bg-palette-violet-background-active
								data-[highlighted]:bg-palette-violet-background-active
							`}
							classNames={{
								checkboxControl:
									'peer-checked:bg-palette-violet-background-active',
							}}
						>
							{item.name}
						</ComboboxMultiSelectItem>
					)}
				</ComboboxMultiSelectListbox>
			</ComboboxMultiSelectPopover>
		</ComboboxMultiSelect>
	</Field>
);

No parts available. Only root.

ComboboxMultiSelectItem parts

Part

Description

center

The main area of the 'track'

checkbox

The checkbox root

checkboxControl

The interactive area of the checkbox

checkboxIcon

The icon within the checkbox

rail

Common style applied to both railStart and railEnd via styles.rail. Used for shared customization

railEnd

The right fixed element. For icons, buttons, or trailing content.

railStart

The left fixed element. For icons, buttons, or any content to appear before the main area .

ComboboxMultiSelectTextInput parts

Part

Description

adornmentEnd

Container for the clear (remove all) icon that appears at the end.

focusIndicator

A hidden span or visual indicator to show when the input is focused.

inner

The flex element which contains the input and the tags

input

Styles applied to the input field.

root

Wrapper around the whole input, tags, and adornments.

tag

The element which represents a selected item

tagCloseButton

The button within a Tag which dismisses the selection

tagCloseButtonIcon

The icon element within the TagCloseButton

tagLabel

The text element within the tag