InputTags

InputTags groups a collection of contents in items.

React
basic-demo

Usage#

import { InputTags } from '@primereact/ui/inputtags';
<InputTags.Root>
    {(instance) => (
        <>
            {instance?.state.value.map((value, index) => (
                <InputTags.Item key={`${value}_${index}`} index={index} />
            ))}
            <InputTags.Input />
        </>
    )}
</InputTags.Root>

Examples#

Delimiter#

A new tag is added when enter key is pressed, delimiter property allows defining an additional key. Currently only valid value is , to create a new item when comma key is pressed.

delimiter-demo
'use client';
import { InputTagsRootInstance } from '@primereact/types/shared/inputtags';
import { InputTags } from '@primereact/ui/inputtags';

export default function DelimiterDemo() {
    return (
        <InputTags.Root delimiter=",">
            {(instance: InputTagsRootInstance) => {
                return (
                    <>
                        {instance?.state.value.map((value, index) => (
                            <InputTags.Item key={`${value}_${index}`} index={index} />
                        ))}
                        <InputTags.Input />
                    </>
                );
            }}
        </InputTags.Root>
    );
}

Max#

The max property limits the number of tags that can be added. Once the limit is reached, no more tags can be entered.

React
max-demo
'use client';
import { InputTagsRootInstance, InputTagsRootValueChangeEvent } from '@primereact/types/shared/inputtags';
import { InputTags } from '@primereact/ui/inputtags';
import * as React from 'react';

export default function MaxDemo() {
    const [tags, setTags] = React.useState<string[]>(['React']);

    return (
        <InputTags.Root value={tags} onValueChange={(e: InputTagsRootValueChangeEvent) => setTags(e.value as string[])} max={5}>
            {(instance: InputTagsRootInstance) => {
                return (
                    <>
                        {instance?.state.value.map((value, index) => (
                            <InputTags.Item key={`${value}_${index}`} index={index} />
                        ))}
                        <InputTags.Input />
                    </>
                );
            }}
        </InputTags.Root>
    );
}

Item#

The InputTags.Root component accepts a render function that provides access to the component instance, allowing full customization of tags, input field, and additional UI elements. The remove actions can be implemented with onItemRemoveClick and onRemoveAllItems, or components can be wrapped with additional functionality.

JavaScriptTypeScript
item-demo
'use client';
import { Times, TimesCircle } from '@primeicons/react';
import { InputTagsRootInstance, InputTagsRootValueChangeEvent } from '@primereact/types/shared/inputtags';
import { IconField } from '@primereact/ui/iconfield';
import { InputTags } from '@primereact/ui/inputtags';
import { Tag } from '@primereact/ui/tag';
import * as React from 'react';

export default function ItemDemo() {
    const [tags, setTags] = React.useState<string[]>(['JavaScript', 'TypeScript']);

    return (
        <InputTags.Root value={tags} onValueChange={(e: InputTagsRootValueChangeEvent) => setTags(e.value as string[])}>
            {(instance: InputTagsRootInstance) => {
                return (
                    <>
                        {instance?.state.value.map((value, index) => (
                            <Tag key={`${value}_${index}`}>
                                {value}
                                <Times size={12} className="cursor-pointer ml-2" onClick={() => instance?.onItemRemoveClick(index)} />
                            </Tag>
                        ))}
                        <IconField.Root className="static">
                            <InputTags.Input />
                            <IconField.Icon>
                                <TimesCircle size={16} className="cursor-pointer" onClick={() => instance?.onRemoveAllItems()} />
                            </IconField.Icon>
                        </IconField.Root>
                    </>
                );
            }}
        </InputTags.Root>
    );
}

Typeahead#

InputTags can be combined with typeahead functionality to provide suggestions as users type. Define an options array along with optionLabel to specify the display field. For grouped suggestions, use optionGroupLabel and optionGroupChildren properties. The onComplete callback is triggered on input change, enabling dynamic filtering and updating of suggestions.

typeahead-demo
'use client';
import type { InputTagsRootInstance, InputTagsRootValueChangeEvent, useInputTagsCompleteEvent } from '@primereact/types/shared/inputtags';
import { InputTags } from '@primereact/ui/inputtags';
import * as React from 'react';

interface Technology {
    label: string;
    value: string;
}

interface TechCategory {
    label: string;
    code: string;
    items: Technology[];
}

const techStack: TechCategory[] = [
    {
        label: 'Frontend',
        code: 'FE',
        items: [
            { label: 'React', value: 'react' },
            { label: 'Vue', value: 'vue' },
            { label: 'Angular', value: 'angular' },
            { label: 'Svelte', value: 'svelte' },
            { label: 'Next.js', value: 'nextjs' },
            { label: 'Nuxt', value: 'nuxt' }
        ]
    },
    {
        label: 'Backend',
        code: 'BE',
        items: [
            { label: 'Node.js', value: 'nodejs' },
            { label: 'Python', value: 'python' },
            { label: 'Java', value: 'java' },
            { label: 'Go', value: 'go' },
            { label: 'Rust', value: 'rust' }
        ]
    },
    {
        label: 'Database',
        code: 'DB',
        items: [
            { label: 'PostgreSQL', value: 'postgresql' },
            { label: 'MongoDB', value: 'mongodb' },
            { label: 'Redis', value: 'redis' },
            { label: 'MySQL', value: 'mysql' },
            { label: 'SQLite', value: 'sqlite' }
        ]
    }
];

const filterItems = (items: Technology[], query: string, selectedLabels: string[]): Technology[] => {
    const normalizedQuery = query.toLowerCase();

    return items.filter((item) => item.label.toLowerCase().includes(normalizedQuery) && !selectedLabels.includes(item.label));
};

export default function TypeaheadDemo() {
    const [skills, setSkills] = React.useState<string[]>([]);
    const [query, setQuery] = React.useState('');

    const filteredTech = React.useMemo(() => {
        const _filteredTech: TechCategory[] = [];

        for (const category of techStack) {
            const filteredItems = filterItems(category.items, query, skills);

            if (filteredItems.length) {
                _filteredTech.push({ ...category, items: filteredItems });
            }
        }

        return _filteredTech;
    }, [query, skills]);

    return (
        <InputTags.Root
            value={skills}
            options={filteredTech}
            optionLabel="label"
            optionGroupLabel="label"
            optionGroupChildren="items"
            onValueChange={(e: InputTagsRootValueChangeEvent) => setSkills(e.value ?? [])}
            onComplete={(e: useInputTagsCompleteEvent) => setQuery(e.query)}
        >
            {(instance: InputTagsRootInstance) => (
                <>
                    {instance?.state.value.map((skill, index) => (
                        <InputTags.Item key={`${skill}_${index}`} index={index} />
                    ))}
                    <InputTags.Input placeholder="Search technologies..." />

                    <InputTags.Portal>
                        <InputTags.List>
                            <InputTags.Options style={{ maxHeight: '14rem' }} />
                        </InputTags.List>
                    </InputTags.Portal>
                </>
            )}
        </InputTags.Root>
    );
}

Events#

The onAdd and onRemove callbacks are triggered when tags are added or removed, providing the tag value and index for custom handling like logging, analytics or validation.

events-demo
'use client';
import { Tag } from '@primeicons/react';
import type {
    InputTagsRootInstance,
    InputTagsRootValueChangeEvent,
    useInputTagsAddEvent,
    useInputTagsRemoveEvent
} from '@primereact/types/shared/inputtags';
import { InputTags } from '@primereact/ui/inputtags';
import * as React from 'react';

export default function EventsDemo() {
    const [tags, setTags] = React.useState<string[]>([]);
    const [log, setLog] = React.useState<string[]>([]);

    const onAdd = (e: useInputTagsAddEvent) => {
        setLog((prev) => [`Added: "${e.value}"`, ...prev].slice(0, 5));
    };

    const onRemove = (e: useInputTagsRemoveEvent) => {
        setLog((prev) => [`Removed: "${e.value}" at index ${e.index}`, ...prev].slice(0, 5));
    };

    return (
        <div className="flex flex-col gap-4">
            <InputTags.Root
                value={tags}
                onValueChange={(e: InputTagsRootValueChangeEvent) => setTags(e.value as string[])}
                onAdd={onAdd}
                onRemove={onRemove}
            >
                {(instance: InputTagsRootInstance) => {
                    return (
                        <>
                            {instance?.state.value.map((value, index) => (
                                <InputTags.Item key={`${value}_${index}`} index={index} />
                            ))}
                            <InputTags.Input placeholder="Add tags and watch the event log..." />
                        </>
                    );
                }}
            </InputTags.Root>
            {log.length > 0 && (
                <div className="flex flex-col gap-2">
                    <span className="text-sm font-medium text-surface-500 dark:text-surface-400 flex items-center gap-2">
                        <Tag size={14} />
                        Event Log
                    </span>
                    <ul className="list-none p-0 m-0 flex flex-col gap-1">
                        {log.map((entry, i) => (
                            <li key={`${entry}_${i}`} className="text-sm text-surface-600 dark:text-surface-300 font-mono">
                                {entry}
                            </li>
                        ))}
                    </ul>
                </div>
            )}
        </div>
    );
}

Filled#

Specify the variant property as filled to display the component with a higher visual emphasis than the default outlined style.

React
filled-demo
'use client';
import { InputTagsRootInstance, InputTagsRootValueChangeEvent } from '@primereact/types/shared/inputtags';
import { InputTags } from '@primereact/ui/inputtags';
import * as React from 'react';

export default function FilledDemo() {
    const [tags, setTags] = React.useState<string[]>(['React']);

    return (
        <InputTags.Root value={tags} onValueChange={(e: InputTagsRootValueChangeEvent) => setTags(e.value as string[])} variant="filled">
            {(instance: InputTagsRootInstance) => {
                return (
                    <>
                        {instance?.state.value.map((value, index) => (
                            <InputTags.Item key={`${value}_${index}`} index={index} />
                        ))}
                        <InputTags.Input />
                    </>
                );
            }}
        </InputTags.Root>
    );
}

Float Label#

A floating label is displayed when the input is focused or filled.

float-label-demo
'use client';
import { InputTagsRootInstance, InputTagsRootValueChangeEvent } from '@primereact/types/shared/inputtags';
import { FloatLabel } from '@primereact/ui/floatlabel';
import { InputTags } from '@primereact/ui/inputtags';
import { Label } from '@primereact/ui/label';
import * as React from 'react';

export default function FloatLabelDemo() {
    const [tags, setTags] = React.useState<string[]>([]);

    return (
        <FloatLabel>
            <InputTags.Root value={tags} onValueChange={(e: InputTagsRootValueChangeEvent) => setTags(e.value as string[])}>
                {(instance: InputTagsRootInstance) => {
                    return (
                        <>
                            {instance?.state.value.map((value, index) => (
                                <InputTags.Item key={`${value}_${index}`} index={index} />
                            ))}
                            <InputTags.Input />
                        </>
                    );
                }}
            </InputTags.Root>
            <Label htmlFor="over_label">Technologies</Label>
        </FloatLabel>
    );
}

Invalid#

Invalid state is displayed using the invalid prop to indicate a failed validation. This style is useful when integrating with form validation libraries.

invalid-demo
'use client';
import { InputTagsRootInstance, InputTagsRootValueChangeEvent } from '@primereact/types/shared/inputtags';
import { InputTags } from '@primereact/ui/inputtags';
import * as React from 'react';

export default function InvalidDemo() {
    const [tags, setTags] = React.useState<string[]>([]);

    return (
        <InputTags.Root value={tags} invalid={tags.length === 0} onValueChange={(e: InputTagsRootValueChangeEvent) => setTags(e.value as string[])}>
            {(instance: InputTagsRootInstance) => {
                return (
                    <>
                        {instance?.state.value.map((value, index) => (
                            <InputTags.Item key={`${value}_${index}`} index={index} />
                        ))}
                        <InputTags.Input />
                    </>
                );
            }}
        </InputTags.Root>
    );
}

Disabled#

When disabled is present, the element cannot be edited and focused.

React
disabled-demo
'use client';
import { InputTagsRootInstance } from '@primereact/types/shared/inputtags';
import { InputTags } from '@primereact/ui/inputtags';

export default function DisabledDemo() {
    return (
        <InputTags.Root defaultValue={['React']} disabled>
            {(instance: InputTagsRootInstance) => {
                return (
                    <>
                        {instance?.state.value.map((value, index) => (
                            <InputTags.Item key={`${value}_${index}`} index={index} />
                        ))}
                        <InputTags.Input />
                    </>
                );
            }}
        </InputTags.Root>
    );
}

Accessibility#

The input element has textbox role in addition to aria-describedby attribute. The tag list uses listbox role with aria-orientation set to horizontal.

When typeahead is enabled, the input element has combobox role in addition to aria-autocomplete, aria-haspopup and aria-expanded attributes. The relation between the input and the popup is created with aria-controls and aria-activedescendant attribute is used to instruct screen reader which option to read during keyboard navigation within the popup list.

Keyboard Support#

KeyFunction
tabMoves focus to the input element.
enterAdds a new tag with the current input value.
backspaceRemoves the last tag when input is empty.
left arrowMoves focus to the previous tag when input is empty.
right arrowMoves focus to the next tag when focused on a tag.

Tag Keyboard Support#

KeyFunction
backspaceRemoves the focused tag.
deleteRemoves the focused tag.
KeyFunction
tabSelects the focused option and closes the popup, then moves focus to next element in page.
enterSelects the focused option and closes the popup.
escapeCloses the popup.
down arrowMoves focus to the next option.
up arrowMoves focus to the previous option.
homeMoves focus to the first option.
endMoves focus to the last option.