Introducing PrimeReact v11-alpha 🎉Discover Now

useInputTags

Hook that manages tag input state and keyboard navigation between chips.

basic-demo

Usage#

import { useInputTags } from '@primereact/headless/inputtags';
const { rootProps, controlProps, getItemProps, removeItem, state } = useInputTags({
    value: tags,
    onValueChange: (e) => setTags(e.value)
});
 
<div {...rootProps}>
    {state.value.map((tag, index) => (
        <span key={index} {...getItemProps(index)}>
            {tag}
            <button onClick={() => removeItem(index)}>×</button>
        </span>
    ))}
    <input {...controlProps} />
</div>;

useInputTags is a pure tag-state hook — it manages the tag array, the text-entry value, focus, keyboard navigation between chips, delimiter splitting, and paste handling. It deliberately has no popover/listbox composition built in. For typeahead-driven entry, compose the hook's controlProps with an <AutoComplete.Root> tree in the Primitive API.

Returned values#

FieldDescription
state.valueCurrent tag list.
state.inputValueCurrent text-entry value.
state.focusedWhether the text-entry control is focused.
state.focusedItemIndexIndex of the keyboard-focused tag, or -1 when none.
inputRefRef attached to the text-entry control.
rootPropsSpread on the container element (sets role="listbox" and aria-orientation). Attach your own callback ref via mergeProps when you need the root DOM node — e.g. to anchor a popup positioner against the full field.
controlPropsSpread on the text-entry <input>. Wires value/onChange, focus/blur tracking, keyboard handlers, and paste-to-tags.
hiddenInputPropsOptional hidden <input type="hidden"> props for form submission with the joined tag values.
getItemProps(index) => props — spread on each rendered chip for ARIA roles (role="option", aria-selected, aria-setsize, aria-posinset) and the data-selected focus marker.
addItem(tag: string | string[]) => string[] — adds one or more tags (subject to max, allowDuplicate, trimming).
removeItem(index) => void — removes the tag at the given index.
removeLastRemoves the last tag.
removeAllClears every tag.

Working with callbacks#

Controlled tags#

Pass value and onValueChange to own the tag list — needed when tags are persisted, validated, or shared across components.

const [tags, setTags] = React.useState<string[]>(['React']);
 
useInputTags({
    value: tags,
    onValueChange: (e) => setTags(e.value)
});

Controlled input query#

inputValue/onInputValueChange control only the typed text, independent of the committed tag list. Useful for custom filtering, clearing the input programmatically, or syncing the query with external state.

const [query, setQuery] = React.useState('');
 
useInputTags({
    inputValue: query,
    onInputValueChange: (e) => setQuery(e.query)
});

Custom add triggers#

Combine delimiter, addOnBlur, addOnTab, and addOnPaste when users expect to paste comma-separated lists or commit on Tab.

useInputTags({
    delimiter: ',',
    addOnBlur: true,
    addOnTab: true,
    addOnPaste: true
});

Per-tag add/remove events#

Use onAdd and onRemove when side effects should run for each individual change (analytics, toast, server sync).

useInputTags({
    onAdd: (e) => console.log('Added:', e.value),
    onRemove: (e) => console.log('Removed:', e.value)
});

Tracking the root element#

useInputTags doesn't expose a root-element ref by design. When you need the container DOM node (e.g. as anchor for a popup positioner so the suggestions match the full field width), attach a callback ref via mergeProps and back it with useState so consumers re-render once it mounts.

import { mergeProps } from '@primeuix/utils';
 
const [rootEl, setRootEl] = React.useState<HTMLElement | null>(null);
 
const inputtags = useInputTags({ value: tags, onValueChange: (e) => setTags(e.value) });
const rootProps = mergeProps(inputtags.rootProps, { ref: setRootEl });
 
usePositioner({ anchor: rootEl, content: positionerEl, /* … */ });
 
<div {...rootProps}>…</div>;

Styling with data attributes#

The hook exposes state through data-* attributes on each part. Use them as CSS selectors — no className juggling.

ScopePartStates
inputtagsroot:focus-within
inputtagscontrol(none — standard input states apply)

Each chip rendered with {...getItemProps(index)} receives:

AttributeMeaning
role"option"
aria-selectedtrue when the chip has keyboard focus, else false
data-selectedEmpty string when focused (matchable via [data-selected]), undefined otherwise
[data-scope='inputtags'][data-part='root']:focus-within {
    border-color: var(--p-primary-color);
    box-shadow: 0 0 0 1px var(--p-primary-color);
}
 
[data-scope='inputtags'][data-part='root'] [role='option'][data-selected] {
    background: var(--p-surface-200);
}

API#

useInputTags#

NameTypeDefault
valuestring[]—
Controlled list of tag values.
defaultValuestring[]—
Initial tag values for uncontrolled usage.
inputValuestring—
Controlled value of the text-entry control.
defaultInputValuestring—
Initial value of the text-entry control for uncontrolled usage.
namestring—
Name used by the hidden form input.
disabledboolean—
Whether the component is disabled.
maxnumber—
Maximum number of tags that can be added.
delimiterstring | RegExp—
Delimiter used to split typed or pasted text into tags.
allowDuplicateboolean—
Whether duplicate tag values are allowed.
addOnBlurboolean—
Whether the current input value should be added when the control loses focus.
addOnPasteboolean—
Whether pasted text should be added as tags.
addOnTabboolean—
Whether the current input value should be added when tabbing away.
invalidboolean—
Whether the component is in an invalid state.
variant"outlined" | "filled"—
Visual variant of the field.
fluidboolean—
Whether the component should fill the width of its container.
onValueChange(event: useInputTagsValueChangeEvent) => void—
Callback fired when the tag value list changes.
onAdd(event: useInputTagsAddEvent) => void—
Callback fired after a tag is added.
onRemove(event: useInputTagsRemoveEvent) => void—
Callback fired after a tag is removed.
onInputValueChange(event: useInputTagsInputValueChangeEvent) => void—
Callback fired when the text-entry value changes.

Accessibility#

Enter adds a tag, Backspace on empty input removes the last tag, and Arrow keys navigate existing tags for deletion. See Primitive for full WAI-ARIA compliance details.