The MultiSelect component allows users to choose multiple items from a list, displaying selected items as removable tags.
React component for selecting multiple items with tag-based display.
/**
* MultiSelect component for choosing multiple items with tag display
* @template T - Type of items in the list
*/
class MultiSelect<T> extends React.Component<MultiSelectProps<T>, MultiSelectState> {
/** Generic factory method for type inference */
static ofType<U>(): new (props: MultiSelectProps<U>) => MultiSelect<U>;
/** Reference to the tag input element */
input: HTMLInputElement | null;
/** Reference to the internal QueryList component */
queryList: QueryList<T> | null;
}
interface MultiSelectProps<T> extends ListItemsProps<T>, SelectPopoverProps {
/** Array of currently selected items */
selectedItems: T[];
/** Function to render each selected item as a tag */
tagRenderer: (item: T) => React.ReactNode;
/** Custom render function for the trigger element. Providing this moves search functionality to the popover */
customTarget?: (selectedItems: T[], isOpen: boolean) => React.ReactNode;
/** Whether the multi-select is disabled */
disabled?: boolean;
/** Whether the multi-select should fill its container */
fill?: boolean;
/** Props for the dropdown menu container */
menuProps?: React.HTMLAttributes<HTMLUListElement>;
/** Callback when all items are cleared */
onClear?: () => void;
/** Callback when an item tag is removed */
onRemove?: (value: T, index: number) => void;
/** Whether to open dropdown on keydown in the input (ignored if customTarget is provided) */
openOnKeyDown?: boolean;
/** Placeholder text for the input */
placeholder?: string;
/** Props passed to the internal TagInput component with some limitations */
tagInputProps?: Partial<Omit<TagInputProps, "inputValue" | "onInputChange">>;
}
interface MultiSelectState {
/** Whether the dropdown is currently open */
isOpen: boolean;
}Usage Examples:
import React, { useState } from "react";
import { MultiSelect, ItemRenderer } from "@blueprintjs/select";
import { MenuItem, Tag } from "@blueprintjs/core";
interface Technology {
name: string;
category: string;
color: string;
}
const technologies: Technology[] = [
{ name: "React", category: "Frontend", color: "#61DAFB" },
{ name: "TypeScript", category: "Language", color: "#3178C6" },
{ name: "Node.js", category: "Backend", color: "#339933" },
{ name: "Python", category: "Language", color: "#3776AB" },
{ name: "PostgreSQL", category: "Database", color: "#336791" },
];
const renderTechnology: ItemRenderer<Technology> = (tech, { handleClick, modifiers }) => (
<MenuItem
key={tech.name}
text={tech.name}
label={tech.category}
onClick={handleClick}
active={modifiers.active}
disabled={modifiers.disabled}
/>
);
const renderTechTag = (tech: Technology) => (
<Tag style={{ backgroundColor: tech.color, color: "white" }}>
{tech.name}
</Tag>
);
const TechnologyMultiSelect = () => {
const [selectedTechs, setSelectedTechs] = useState<Technology[]>([]);
const handleItemSelect = (tech: Technology) => {
if (!selectedTechs.find(t => t.name === tech.name)) {
setSelectedTechs([...selectedTechs, tech]);
}
};
const handleRemove = (techToRemove: Technology, index: number) => {
setSelectedTechs(selectedTechs.filter((_, i) => i !== index));
};
return (
<MultiSelect<Technology>
items={technologies}
selectedItems={selectedTechs}
itemRenderer={renderTechnology}
tagRenderer={renderTechTag}
onItemSelect={handleItemSelect}
onRemove={handleRemove}
onClear={() => setSelectedTechs([])}
placeholder="Select technologies..."
noResults={<MenuItem disabled text="No technologies found." />}
resetOnSelect
/>
);
};
// With custom filtering to exclude selected items
const getFilteredTechnologies = (query: string, selectedTechs: Technology[]) => {
const selectedNames = new Set(selectedTechs.map(t => t.name));
return technologies
.filter(tech => !selectedNames.has(tech.name))
.filter(tech =>
tech.name.toLowerCase().includes(query.toLowerCase()) ||
tech.category.toLowerCase().includes(query.toLowerCase())
);
};
const FilteredTechnologyMultiSelect = () => {
const [selectedTechs, setSelectedTechs] = useState<Technology[]>([]);
return (
<MultiSelect<Technology>
items={technologies}
selectedItems={selectedTechs}
itemRenderer={renderTechnology}
tagRenderer={renderTechTag}
itemListPredicate={(query) => getFilteredTechnologies(query, selectedTechs)}
onItemSelect={(tech) => setSelectedTechs([...selectedTechs, tech])}
onRemove={(_, index) => setSelectedTechs(selectedTechs.filter((_, i) => i !== index))}
placeholder="Select technologies..."
/>
);
};MultiSelect provides extensive customization options for tag display and behavior.
// Tag renderer function type
type TagRenderer<T> = (item: T) => React.ReactNode;
// TagInput props (subset available to MultiSelect)
interface TagInputProps {
/** Whether tags can be added by typing */
addOnBlur?: boolean;
/** Whether tags can be added by pasting */
addOnPaste?: boolean;
/** Whether the input should fill its container */
fill?: boolean;
/** Props for the input element */
inputProps?: React.HTMLAttributes<HTMLInputElement>;
/** Whether the input is disabled */
disabled?: boolean;
/** Large size variant */
large?: boolean;
/** Callback when input loses focus */
onInputChange?: React.ChangeEventHandler<HTMLInputElement>;
/** Callback when a tag is removed */
onRemove?: (value: string, index: number) => void;
/** Placeholder text */
placeholder?: string;
/** Whether tags should have a remove button */
tagProps?: TagProps;
/** Array of tag values */
values: React.ReactNode[];
}Usage Examples:
import { MultiSelect } from "@blueprintjs/select";
import { Tag, Intent } from "@blueprintjs/core";
// Custom tag renderer with different intents
const renderPriorityTag = (item: { name: string; priority: "high" | "medium" | "low" }) => {
const intent = {
high: Intent.DANGER,
medium: Intent.WARNING,
low: Intent.SUCCESS,
}[item.priority];
return (
<Tag intent={intent} minimal>
{item.name}
</Tag>
);
};
// With custom TagInput props
const CustomTagInputMultiSelect = () => (
<MultiSelect<string>
items={["Apple", "Banana", "Cherry", "Date"]}
selectedItems={[]}
itemRenderer={(item, { handleClick, modifiers }) => (
<div onClick={handleClick}>{item}</div>
)}
tagRenderer={(item) => <Tag>{item}</Tag>}
onItemSelect={() => {}}
tagInputProps={{
large: true,
fill: true,
placeholder: "Type to search...",
tagProps: {
minimal: true,
intent: Intent.PRIMARY,
},
}}
/>
);MultiSelect supports creating new items from user input.
// From ListItemsProps
interface ListItemsProps<T> {
/** Function to create new items from query string */
createNewItemFromQuery?: (query: string) => T | T[];
/** Function to render the "create new item" option */
createNewItemRenderer?: (
query: string,
active: boolean,
handleClick: React.MouseEventHandler<HTMLElement>,
) => React.JSX.Element | undefined;
/** Where to position the create new item option */
createNewItemPosition?: "first" | "last";
}Usage Examples:
const CreatableMultiSelect = () => {
const [tags, setTags] = useState<string[]>(["existing-tag"]);
const [selectedTags, setSelectedTags] = useState<string[]>([]);
const createNewTag = (query: string): string => {
return query.trim();
};
const renderCreateTagOption = (query: string, active: boolean, handleClick: React.MouseEventHandler) => (
<MenuItem
icon="add"
text={`Create "${query}"`}
active={active}
onClick={handleClick}
shouldDismissPopover={false}
/>
);
return (
<MultiSelect<string>
items={tags}
selectedItems={selectedTags}
itemRenderer={(tag, { handleClick, modifiers }) => (
<MenuItem
text={tag}
onClick={handleClick}
active={modifiers.active}
/>
)}
tagRenderer={(tag) => <Tag>{tag}</Tag>}
onItemSelect={(tag) => {
if (!selectedTags.includes(tag)) {
setSelectedTags([...selectedTags, tag]);
}
}}
createNewItemFromQuery={createNewTag}
createNewItemRenderer={renderCreateTagOption}
createNewItemPosition="first"
noResults={<MenuItem disabled text="Type to create a new tag..." />}
/>
);
};MultiSelect allows complete customization of the input area display.
interface MultiSelectProps<T> {
/** Custom render function for the entire input area */
customTarget?: (selectedItems: T[], isOpen: boolean) => React.ReactNode;
}Usage Examples:
import { MultiSelect } from "@blueprintjs/select";
import { Button, Tag } from "@blueprintjs/core";
const CustomTargetMultiSelect = () => {
const [selectedItems, setSelectedItems] = useState<string[]>([]);
const customTarget = (items: string[], isOpen: boolean) => (
<Button
text={items.length > 0 ? `${items.length} items selected` : "Select items..."}
rightIcon={isOpen ? "caret-up" : "caret-down"}
active={isOpen}
style={{ minWidth: "200px", justifyContent: "space-between" }}
/>
);
return (
<MultiSelect<string>
items={["Option 1", "Option 2", "Option 3", "Option 4"]}
selectedItems={selectedItems}
itemRenderer={(item, { handleClick, modifiers }) => (
<div onClick={handleClick} style={{ padding: "8px" }}>
{item}
</div>
)}
tagRenderer={(item) => <Tag>{item}</Tag>}
onItemSelect={(item) => setSelectedItems([...selectedItems, item])}
customTarget={customTarget}
/>
);
};