Skip to content

TagsInput

Multi-select combobox that renders selected values as removable tags.

Ada Lovelace
<TagsInput
htmlName="team"
label="Team"
onChange={() => {}}
placeholder="Search people"
/>
function EmptyTagsInputStory(
args: React.ComponentProps<typeof TagsInput>,
): React.JSX.Element {
const [value, setValue] = useState<SearchableItem[]>([]);
const source = useMemo(() => createStaticSearchSource(people), []);
return (
<TagsInput
{...args}
debounceMs={0}
hasEntriesOnFocus
onChange={(items, change) => {
setValue(items);
args.onChange(items, change);
}}
searchSource={source}
startIcon={Users}
value={value}
/>
);
}
<EmptyTagsInputStory {...args} />
Ada Lovelace
<TagsInput
htmlName="team"
label="Team"
onChange={() => {}}
placeholder="Search people"
hasCreate
/>
Ada Lovelace
<TagsInput
htmlName="team"
label="Team"
onChange={() => {}}
placeholder="Type a name, then click away"
createItemOnBlur={rawValue => ({id: rawValue, label: rawValue})}
/>
Ada Lovelace
<TagsInput
htmlName="team"
label="Team"
onChange={() => {}}
placeholder="Search people"
hasClear
/>
function AsyncSearchStory(
args: React.ComponentProps<typeof TagsInput>,
): React.JSX.Element {
const [value, setValue] = useState<SearchableItem[]>([]);
const source = useMemo(() => createAsyncSource(people), []);
return (
<TagsInput
{...args}
debounceMs={0}
onChange={(items, change) => {
setValue(items);
args.onChange(items, change);
}}
searchSource={source}
startIcon={Users}
value={value}
/>
);
}
<AsyncSearchStory {...args} />
Ada Lovelace
<TagsInput
htmlName="team"
label="Team"
onChange={() => {}}
placeholder="Search people"
maxEntries={2}
/>
Ada Lovelace
<TagsInput
htmlName="team"
label="Team"
onChange={() => {}}
placeholder="Search people"
isDisabled
/>
Ada Lovelace
<TagsInput
htmlName="team"
label="Team"
onChange={() => {}}
placeholder="Search people"
hasClear
isReadOnly
/>
Ada Lovelace
<TagsInput
htmlName="team"
label="Team"
onChange={() => {}}
placeholder="Search people"
size="sm"
/>
Ada Lovelace
<TagsInput
htmlName="team"
label="Team"
onChange={() => {}}
placeholder="Search people"
size="lg"
/>
Ada Lovelace
<TagsInput
htmlName="team"
label="Team"
onChange={() => {}}
placeholder="Search people"
isRequired
/>
Ada Lovelace
<TagsInput
htmlName="team"
label="Team"
onChange={() => {}}
placeholder="Search people"
status={{message: 'At least one team member is required.', type: 'error'}}
/>
Ada Lovelace
This team has limited capacity.
<TagsInput
htmlName="team"
label="Team"
onChange={() => {}}
placeholder="Search people"
status={{message: 'This team has limited capacity.', type: 'warning'}}
/>
Ada LovelaceGrace HopperKatherine JohnsonMargaret Hamilton
function OverflowInlineStory(
args: React.ComponentProps<typeof TagsInput>,
): React.JSX.Element {
const [val, setVal] = useState<SearchableItem[]>(people.slice(0, 4));
const source = useMemo(() => createStaticSearchSource(people), []);
return (
<div style={{maxWidth: 400}}>
<TagsInput
{...args}
debounceMs={0}
hasEntriesOnFocus
onChange={(items, change) => {
setVal(items);
args.onChange(items, change);
}}
searchSource={source}
tagOverflowBehavior="unfocusedInline"
value={val}
/>
</div>
);
}
<OverflowInlineStory {...args} />
Ada LovelaceGrace HopperKatherine JohnsonMargaret HamiltonHedy Lamarr
Ada LovelaceGrace HopperKatherine JohnsonMargaret HamiltonHedy Lamarr
Ada LovelaceGrace HopperKatherine JohnsonMargaret HamiltonHedy Lamarr
function OverflowBehaviorsStory(
args: React.ComponentProps<typeof TagsInput>,
): React.JSX.Element {
const [inlineValue, setInlineValue] = useState<SearchableItem[]>(
people.slice(0, 5),
);
const [popoverValue, setPopoverValue] = useState<SearchableItem[]>(
people.slice(0, 5),
);
const source = useMemo(() => createStaticSearchSource(people), []);
return (
<div
style={{
display: 'grid',
gap: 24,
gridTemplateColumns: 'repeat(auto-fit, minmax(260px, 1fr))',
maxWidth: 720,
}}>
<TagsInput
{...args}
debounceMs={0}
hasEntriesOnFocus
label="Inline overflow"
onChange={(items, change) => {
setInlineValue(items);
args.onChange(items, change);
}}
searchSource={source}
startIcon={Users}
tagOverflowBehavior="unfocusedInline"
value={inlineValue}
/>
<TagsInput
{...args}
debounceMs={0}
hasEntriesOnFocus
label="Popover overflow"
onChange={(items, change) => {
setPopoverValue(items);
args.onChange(items, change);
}}
searchSource={source}
startIcon={Users}
tagOverflowBehavior="unfocusedLayer"
value={popoverValue}
/>
</div>
);
}
<OverflowBehaviorsStory {...args} />
Focus to show in-memory suggestions immediately.
Ada Lovelace
Focus to load suggestions after a one-second delay.
Ada Lovelace
function InitialSuggestionsComparisonStory(args: TagsInputProps) {
const [syncValue, setSyncValue] = useState<SearchableItem[]>([people[0]]);
const [asyncValue, setAsyncValue] = useState<SearchableItem[]>([people[0]]);
const syncSource = useMemo(() => createStaticSearchSource(people), []);
const asyncSource = useMemo(() => {
const source = createAsyncSource(people, 1000);
return {...source, bootstrap: async () => source.search('')};
}, []);
return (
<VStack gap={4}>
<TagsInput
{...args}
description="Focus to show in-memory suggestions immediately."
hasEntriesOnFocus
label="Synchronous initial suggestions"
onChange={setSyncValue}
searchSource={syncSource}
value={syncValue}
/>
<TagsInput
{...args}
description="Focus to load suggestions after a one-second delay."
hasEntriesOnFocus
label="Asynchronous initial suggestions"
onChange={setAsyncValue}
searchSource={asyncSource}
value={asyncValue}
/>
</VStack>
);
}
<InitialSuggestionsComparisonStory {...args} />

Multi-select combobox that renders selected values as removable tags.

When isOptional: false, isRequired: false

PropTypeDefaultDescription
classNamestring—Additional CSS class names applied to the input wrapper.
data-testidstring—Test ID applied to the input wrapper.
createItemOnBlur(rawValue: string) => T | null—Builds a tag from text still typed when focus leaves the tags-input, so an entry the user forgot to commit is not silently dropped. Return null to leave the text in place, for example when it is not a valid entry. When an item is returned it is added (unless already selected) and the typed text is cleared. Independent of hasCreate.
debounceMsnumber150Debounce delay in milliseconds before search runs.
descriptionReactNode—Supporting text rendered below the label.
emptySearchResultsTextstring'No results found'Empty state text.
endContentReactNode—Content rendered at the end of the input row.
htmlNamestring—HTML name attribute for native form submission. Each selected item ID is submitted as a separate entry.
handleRefRef<TagsInputHandle>—Imperative focus/blur handle.
hasAutoFocusbooleanfalseWhether to focus the input on mount.
hasClearbooleanfalseWhether to show a clear button that removes all tags.
hasEntriesOnFocusbooleanfalseWhether to show bootstrap results on focus before typing.
isDisabledbooleanfalseWhether the input is disabled.
isLabelHiddenbooleanfalseWhether to visually hide the label.
isReadOnlybooleanfalseWhether the input is read-only. Prevents typing and hides the clear button without applying disabled opacity.
label*string—Field label.
labelEndReactNode—Content shown at the end of the label row, such as a link. It renders outside the label, so it does not focus the control or change its accessible name. It stays visible when isLabelHidden is true.
labelIconIconComponent—Icon shown before the label.
labelTooltipReactNode—Tooltip content shown next to the label.
maxEntriesnumber—Maximum number of selected tags.
maxMenuItemsnumber10Maximum number of menu items.
onBlur(event: FocusEvent<HTMLDivElement>) => void—Called when focus leaves the tags-input.
onChange*(items: T[], change: TagsInputChange<T>) => void—Called when selected tags change.
onFocus(event: FocusEvent<HTMLDivElement>) => void—Called when focus enters the tags-input.
onQueryChange(query: string) => void—Called when the query changes.
placeholderstring—Placeholder text.
refRef<HTMLDivElement>—Ref forwarded to the field root.
renderItem(item: T) => ReactNode—Custom result renderer.
renderTag( item: T, onRemove: () => void, state: TagsInputTagState, ) => ReactNode—Custom tag renderer.
searchSource*SearchSource<T>—Provides results for the menu. Use createStaticSearchSource for in-memory data, or implement SearchSource for async/remote search.
size"sm" | "md" | "lg"'md'Visual size.
startIconIconComponent—Icon shown before the input.
statusInputStatus—Validation status displayed below the tags-input.
styleCSSProperties—Inline styles applied to the input wrapper.
tagOverflowBehavior"none" | "unfocusedInline" | "unfocusedLayer"'none'Controls how tags overflow when the container is too narrow. - 'none': Tags wrap to multiple lines (default). - 'unfocusedInline': Single line with "+ N more" when unfocused; expands inline on focus. - 'unfocusedLayer': Single line with "+ N more" when unfocused; expands as overlay on focus.
value*T[]—Selected items.
isOptionalfalse—
isRequiredfalse—
hasCreatebooleanfalseWhether users can create a tag from free text.
createItem(rawValue: string) => T—Builds the committed item from the typed text. Optional for this item type because the default builder — rawValue => ({id: rawValue, label: rawValue}) — already produces a valid item. Builds the committed item from the typed text. Required for this item type because it has fields the default {id, label} builder cannot supply.

When isOptional: true, isRequired: false

PropTypeDefaultDescription
classNamestring—Additional CSS class names applied to the input wrapper.
data-testidstring—Test ID applied to the input wrapper.
createItemOnBlur(rawValue: string) => T | null—Builds a tag from text still typed when focus leaves the tags-input, so an entry the user forgot to commit is not silently dropped. Return null to leave the text in place, for example when it is not a valid entry. When an item is returned it is added (unless already selected) and the typed text is cleared. Independent of hasCreate.
debounceMsnumber150Debounce delay in milliseconds before search runs.
descriptionReactNode—Supporting text rendered below the label.
emptySearchResultsTextstring'No results found'Empty state text.
endContentReactNode—Content rendered at the end of the input row.
htmlNamestring—HTML name attribute for native form submission. Each selected item ID is submitted as a separate entry.
handleRefRef<TagsInputHandle>—Imperative focus/blur handle.
hasAutoFocusbooleanfalseWhether to focus the input on mount.
hasClearbooleanfalseWhether to show a clear button that removes all tags.
hasEntriesOnFocusbooleanfalseWhether to show bootstrap results on focus before typing.
isDisabledbooleanfalseWhether the input is disabled.
isLabelHiddenbooleanfalseWhether to visually hide the label.
isReadOnlybooleanfalseWhether the input is read-only. Prevents typing and hides the clear button without applying disabled opacity.
label*string—Field label.
labelEndReactNode—Content shown at the end of the label row, such as a link. It renders outside the label, so it does not focus the control or change its accessible name. It stays visible when isLabelHidden is true.
labelIconIconComponent—Icon shown before the label.
labelTooltipReactNode—Tooltip content shown next to the label.
maxEntriesnumber—Maximum number of selected tags.
maxMenuItemsnumber10Maximum number of menu items.
onBlur(event: FocusEvent<HTMLDivElement>) => void—Called when focus leaves the tags-input.
onChange*(items: T[], change: TagsInputChange<T>) => void—Called when selected tags change.
onFocus(event: FocusEvent<HTMLDivElement>) => void—Called when focus enters the tags-input.
onQueryChange(query: string) => void—Called when the query changes.
placeholderstring—Placeholder text.
refRef<HTMLDivElement>—Ref forwarded to the field root.
renderItem(item: T) => ReactNode—Custom result renderer.
renderTag( item: T, onRemove: () => void, state: TagsInputTagState, ) => ReactNode—Custom tag renderer.
searchSource*SearchSource<T>—Provides results for the menu. Use createStaticSearchSource for in-memory data, or implement SearchSource for async/remote search.
size"sm" | "md" | "lg"'md'Visual size.
startIconIconComponent—Icon shown before the input.
statusInputStatus—Validation status displayed below the tags-input.
styleCSSProperties—Inline styles applied to the input wrapper.
tagOverflowBehavior"none" | "unfocusedInline" | "unfocusedLayer"'none'Controls how tags overflow when the container is too narrow. - 'none': Tags wrap to multiple lines (default). - 'unfocusedInline': Single line with "+ N more" when unfocused; expands inline on focus. - 'unfocusedLayer': Single line with "+ N more" when unfocused; expands as overlay on focus.
value*T[]—Selected items.
isOptional*true—
isRequiredfalse—
hasCreatebooleanfalseWhether users can create a tag from free text.
createItem(rawValue: string) => T—Builds the committed item from the typed text. Optional for this item type because the default builder — rawValue => ({id: rawValue, label: rawValue}) — already produces a valid item. Builds the committed item from the typed text. Required for this item type because it has fields the default {id, label} builder cannot supply.

When isOptional: false, isRequired: true

PropTypeDefaultDescription
classNamestring—Additional CSS class names applied to the input wrapper.
data-testidstring—Test ID applied to the input wrapper.
createItemOnBlur(rawValue: string) => T | null—Builds a tag from text still typed when focus leaves the tags-input, so an entry the user forgot to commit is not silently dropped. Return null to leave the text in place, for example when it is not a valid entry. When an item is returned it is added (unless already selected) and the typed text is cleared. Independent of hasCreate.
debounceMsnumber150Debounce delay in milliseconds before search runs.
descriptionReactNode—Supporting text rendered below the label.
emptySearchResultsTextstring'No results found'Empty state text.
endContentReactNode—Content rendered at the end of the input row.
htmlNamestring—HTML name attribute for native form submission. Each selected item ID is submitted as a separate entry.
handleRefRef<TagsInputHandle>—Imperative focus/blur handle.
hasAutoFocusbooleanfalseWhether to focus the input on mount.
hasClearbooleanfalseWhether to show a clear button that removes all tags.
hasEntriesOnFocusbooleanfalseWhether to show bootstrap results on focus before typing.
isDisabledbooleanfalseWhether the input is disabled.
isLabelHiddenbooleanfalseWhether to visually hide the label.
isReadOnlybooleanfalseWhether the input is read-only. Prevents typing and hides the clear button without applying disabled opacity.
label*string—Field label.
labelEndReactNode—Content shown at the end of the label row, such as a link. It renders outside the label, so it does not focus the control or change its accessible name. It stays visible when isLabelHidden is true.
labelIconIconComponent—Icon shown before the label.
labelTooltipReactNode—Tooltip content shown next to the label.
maxEntriesnumber—Maximum number of selected tags.
maxMenuItemsnumber10Maximum number of menu items.
onBlur(event: FocusEvent<HTMLDivElement>) => void—Called when focus leaves the tags-input.
onChange*(items: T[], change: TagsInputChange<T>) => void—Called when selected tags change.
onFocus(event: FocusEvent<HTMLDivElement>) => void—Called when focus enters the tags-input.
onQueryChange(query: string) => void—Called when the query changes.
placeholderstring—Placeholder text.
refRef<HTMLDivElement>—Ref forwarded to the field root.
renderItem(item: T) => ReactNode—Custom result renderer.
renderTag( item: T, onRemove: () => void, state: TagsInputTagState, ) => ReactNode—Custom tag renderer.
searchSource*SearchSource<T>—Provides results for the menu. Use createStaticSearchSource for in-memory data, or implement SearchSource for async/remote search.
size"sm" | "md" | "lg"'md'Visual size.
startIconIconComponent—Icon shown before the input.
statusInputStatus—Validation status displayed below the tags-input.
styleCSSProperties—Inline styles applied to the input wrapper.
tagOverflowBehavior"none" | "unfocusedInline" | "unfocusedLayer"'none'Controls how tags overflow when the container is too narrow. - 'none': Tags wrap to multiple lines (default). - 'unfocusedInline': Single line with "+ N more" when unfocused; expands inline on focus. - 'unfocusedLayer': Single line with "+ N more" when unfocused; expands as overlay on focus.
value*T[]—Selected items.
isOptionalfalse—
isRequired*true—
hasCreatebooleanfalseWhether users can create a tag from free text.
createItem(rawValue: string) => T—Builds the committed item from the typed text. Optional for this item type because the default builder — rawValue => ({id: rawValue, label: rawValue}) — already produces a valid item. Builds the committed item from the typed text. Required for this item type because it has fields the default {id, label} builder cannot supply.