Multi select
MultiSelect picks several items from a list. The chosen items show as tags in a Tag input, which also filters the list.
Usage#
import { MultiSelect } from "@clawscale/react/select";MultiSelect takes the shared item props, plus selectedItems and tagRenderer.
Examples#
Tags#
MultiSelect is controlled. Keep selectedItems in state, toggle an item in onItemSelect and drop it in onRemove. tagRenderer sets the tag content. In the item renderer, selected ticks chosen items and shouldDismissPopover={false} keeps the list open between picks.
import { MenuItem } from "@clawscale/react";
import { type ItemRenderer, MultiSelect } from "@clawscale/react/select";
import { useState } from "react";
import { DATASETS, type Dataset, filterDataset } from "../_data";
export default function MultiSelectTags() {
const [selected, setSelected] = useState(() => DATASETS.slice(6, 8));
const toggle = (dataset: Dataset) =>
setSelected((current) =>
current.includes(dataset) ? current.filter((item) => item !== dataset) : [...current, dataset],
);
const renderDataset: ItemRenderer<Dataset> = (dataset, { handleClick, handleFocus, id, modifiers, ref }) => {
if (!modifiers.matchesPredicate) return null;
return (
<MenuItem
key={dataset.id}
ref={ref}
id={id}
active={modifiers.active}
label={dataset.owner}
onClick={handleClick}
onFocus={handleFocus}
roleStructure="listoption"
selected={selected.includes(dataset)}
shouldDismissPopover={false}
text={dataset.name}
/>
);
};
return (
<MultiSelect<Dataset>
items={DATASETS}
itemPredicate={filterDataset}
itemRenderer={renderDataset}
onItemSelect={toggle}
onRemove={toggle}
tagInputProps={{ inputProps: { "aria-label": "Datasets" } }}
placeholder="Add datasets"
selectedItems={selected}
tagRenderer={(dataset) => dataset.name}
/>
);
}Clear button#
onClear adds a button that removes every tag at once. It shows while at least one item is selected.
import { MultiSelect } from "@clawscale/react/select";
import { useState } from "react";
import { DATASETS, type Dataset, filterDataset } from "../_data";
import { renderCheckedDataset } from "../_renderers";
export default function MultiSelectClear() {
const [selected, setSelected] = useState(() => DATASETS.slice(8, 11));
const toggle = (dataset: Dataset) =>
setSelected((current) =>
current.includes(dataset) ? current.filter((item) => item !== dataset) : [...current, dataset],
);
return (
<MultiSelect<Dataset>
items={DATASETS}
itemPredicate={filterDataset}
itemRenderer={renderCheckedDataset(selected)}
onClear={() => setSelected([])}
onItemSelect={toggle}
onRemove={toggle}
tagInputProps={{ inputProps: { "aria-label": "Datasets" } }}
placeholder="Add datasets"
selectedItems={selected}
tagRenderer={(dataset) => dataset.name}
/>
);
}Tag props#
tagInputProps.tagProps styles the tags. As a function, it receives the rendered tag and its index, and the index points into selectedItems. Here datasets over one billion rows get a warning tag, since scans on them are slow.
import { MultiSelect } from "@clawscale/react/select";
import { useState } from "react";
import { DATASETS, type Dataset, filterDataset } from "../_data";
import { renderCheckedDataset } from "../_renderers";
const BILLION = 1_000_000_000;
export default function MultiSelectTagProps() {
const [selected, setSelected] = useState(() => DATASETS.slice(0, 2));
const toggle = (dataset: Dataset) =>
setSelected((current) =>
current.includes(dataset) ? current.filter((item) => item !== dataset) : [...current, dataset],
);
return (
<MultiSelect<Dataset>
items={DATASETS}
itemPredicate={filterDataset}
itemRenderer={renderCheckedDataset(selected)}
onItemSelect={toggle}
onRemove={toggle}
placeholder="Add datasets"
selectedItems={selected}
tagInputProps={{
inputProps: { "aria-label": "Datasets" },
tagProps: (_value, index) => {
const large = (selected[index]?.rows ?? 0) > BILLION;
return { icon: large ? "warning-sign" : undefined, intent: large ? "warning" : "none", minimal: true };
},
}}
tagRenderer={(dataset) => dataset.name}
/>
);
}Props interface#
interface MultiSelectProps extends ListItemsProps<T>, SelectPopoverProps@clawscale/react/select| Props | Description |
|---|---|
activeItem | T | CreateNewItem | nullThe currently focused item for keyboard interactions, or Inherited from null to indicate that no item is active. If omitted or undefined, this prop will be uncontrolled (managed by the component's state). Use onActiveItemChange to listen for updates.ListItemsProps |
className | stringA space-delimited list of class names to pass along to a child element. Inherited from Props |
createNewItemFromQuery | (query: string) => T | T[]If provided, allows new items to be created using the current query string. This is invoked when user interaction causes one or many items to be created, either by pressing the Inherited from Enter key or by clicking on the "Create Item" option. It transforms a query string into one or many items type.ListItemsProps |
createNewItemPosition | "first" | "last" = 'last'Determines the position of the Inherited from createNewItem within the list: first or last. Only relevant when createNewItemRenderer is defined.ListItemsProps |
createNewItemRenderer | (query: string, active: boolean, handleClick: React.MouseEventHandler<HTMLElement>) => React.JSX.Element | undefinedCustom renderer to transform the current query string into a selectable "Create Item" option. If this function is provided, a "Create Item" option will be rendered at the end of the list of items. If this function is not provided, a "Create Item" option will not be displayed. Inherited from ListItemsProps |
customTarget | (selectedItems: T[], isOpen: boolean) => React.ReactNodeElement which triggers the multiselect popover. Providing this prop will replace the default TagInput target thats rendered and move the search functionality to within the Popover. |
disabled | boolean = falseWhether the component is non-interactive. If true, the list's item renderer will not be called. |
fill | booleanWhether the component should take up the full width of its container. |
initialContent | React.ReactNode | nullReact content to render when query is empty. If omitted, all items will be rendered (or result of Inherited from itemListPredicate with empty query). If explicit null, nothing will be rendered when query is empty.ListItemsProps |
itemDisabled | keyof T | ((item: T, index: number) => boolean)Determine if the given item is disabled. Provide a callback function, or simply provide the name of a boolean property on the item that exposes its disabled state. Inherited from ListItemsProps |
itemListPredicate | ItemListPredicate<T>Customize querying of entire Inherited from items array. Return new list of items. This method can reorder, add, or remove items at will. (Supports filter algorithms that operate on the entire set, rather than individual items.)ListItemsProps |
itemListRenderer | ItemListRenderer<T>Custom renderer for the contents of the dropdown. Inherited from ListItemsProps |
itemPredicate | ItemPredicate<T>Customize querying of individual items. Inherited from ListItemsProps |
itemRendererrequired | ItemRenderer<T>Custom renderer for an item in the dropdown list. Receives a boolean indicating whether this item is active (selected by keyboard arrows) and an Inherited from onClick event handler that should be attached to the returned element.ListItemsProps |
itemsrequired | T[]Array of items in the list. Inherited from ListItemsProps |
itemsEqual | ItemsEqualProp<T>Specifies how to test if two items are equal. By default, simple strict equality ( Inherited from ===) is used to compare two items.ListItemsProps |
menuProps | React.HTMLAttributes<HTMLUListElement>Props to spread to the Menu listbox containing the selectable options. |
noResults | React.ReactNodeReact content to render when filtering items returns zero results. If omitted, nothing will be rendered in this case. Inherited from ListItemsProps |
onActiveItemChange | (activeItem: T | null, isCreateNewItem: boolean) => voidInvoked when user interaction should change the active item: arrow keys move it up/down in the list, selecting an item makes it active, and changing the query may reset it to the first item in the list if it no longer matches the filter. Inherited from ListItemsProps |
onClear | () => voidIf provided, this component will render a "clear" button inside its TagInput. Clicking that button will invoke this callback to clear all items from the current selection. |
onItemSelectrequired | (item: T, event?: React.SyntheticEvent<HTMLElement>) => voidCallback invoked when an item from the list is selected, typically by clicking or pressing Inherited from enter key.ListItemsProps |
onItemsPaste | (items: T[]) => voidCallback invoked when multiple items are selected at once via pasting. Inherited from ListItemsProps |
onQueryChange | (query: string, event?: React.ChangeEvent<HTMLInputElement>) => voidCallback invoked when the query string changes. Inherited from ListItemsProps |
onRemove | (value: T, index: number) => voidCallback invoked when an item is removed from the selection by removing its tag in the TagInput. This is generally more useful than tagInputProps.onRemove because it receives the removed value instead of the value's rendered ReactNode tag. |
openOnKeyDown | boolean = falseIf true, the component waits until a keydown event in the TagInput before opening its popover. |
placeholder | string = "Search..."Input placeholder text. Shorthand for tagInputProps.placeholder. |
popoverContentProps | React.HTMLAttributes<HTMLDivElement>HTML attributes to spread to the popover content container element. Inherited from SelectPopoverProps |
popoverProps | Partial<Omit<PopoverProps, "content" | "defaultIsOpen" | "fill" | "renderTarget">>Props to spread to the popover. Inherited from SelectPopoverProps |
popoverRef | React.RefObject<PopoverNextRef>Optional ref to the popover. Inherited from popoverRef.current?.reposition() re-runs position calculation, useful when the target moves in a way the popover doesn't observe automatically (e.g. a parent layout change that doesn't trigger a resize).SelectPopoverProps |
popoverTargetProps | React.HTMLAttributes<HTMLElement>HTML attributes to add to the popover target element. Inherited from SelectPopoverProps |
query | stringQuery string passed to Inherited from itemListPredicate or itemPredicate to filter items. This value is controlled: its state must be managed externally by attaching an onChange handler to the relevant element in your renderer implementation.ListItemsProps |
resetOnQuery | boolean = trueWhether the active item should be reset to the first matching item _every time the query changes_ (via prop or by user input). Inherited from ListItemsProps |
resetOnSelect | boolean = falseWhether the active item should be reset to the first matching item _when an item is selected_. The query will also be reset to the empty string. Inherited from ListItemsProps |
scrollToActiveItem | boolean = trueWhen Inherited from activeItem is controlled, whether the active item should _always_ be scrolled into view when the prop changes. If false, only changes that result from built-in interactions (clicking, querying, or using arrow keys) will scroll the active item into view. Ignored if the activeItem prop is omitted (uncontrolled behavior).ListItemsProps |
selectedItemsrequired | T[]Controlled selected values. |
tagInputProps | Partial<Omit<TagInputProps, "inputValue" | "onInputChange">>Props to pass to the TagInput component. |
tagRendererrequired | (item: T) => React.ReactNodeCustom renderer to transform an item into tag content. |