2 TagsInput
L4stIdi0t edited this page 2026-10-09 22:01:06 +02:00
This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

TagsInput

A field that collects several short values as tags: type, press Enter (or the delimiter), and what you typed becomes a removable chip.

Given options, it is also a multi-select: the input becomes an APG combobox over a multiselectable listbox, filtered as you type, with DOM focus staying in the field and the active option tracked through aria-activedescendant. Typed text that matches no option is offered as a new tag, first in the list, so Enter adds what you typed; ↓ reaches the suggestions. create={false} limits it to the list. The tags themselves are one tab stop with the input: ← at the start of the field, or Backspace in an empty one, moves focus onto the last tag's remove button, and ←/→ walk between them. Backspace there removes it — two presses, so a stray Backspace never deletes a tag.

Forms · two-way: bind:values · submits in a form with name · portalled to <body> · keyboard contract

<script lang="ts">
	import { TagsInput } from '@l4st/driftwood';
</script>
Light Dark
tags-input tags-input in dark mode

Props

Prop Type Default What it does
values bind: string[] [] The tags, in the order they were added.
options TagsInputOption[] — Suggestions. Given these, the field opens a list and works as a multi-select.
create boolean true Accept values that are not in options. Without options this is the only way in.
delimiter string ',' A key that adds the typed text like Enter does. Pasted text is split on it too. '' turns it off.
max number — At most this many tags. Adding stops there; removing still works.
addOnBlur boolean false Add the typed text when focus leaves the field, instead of dropping it.
placeholder string 'Add…' Shown while there are no tags and nothing typed.
disabled boolean false Blocks typing and removing.
name string — Submits one field per tag, like a multiple <select>.
id string — Points a <Label for=…> at the input.
class string — Merged with the component's own classes, last — so yours win.
aria-label string — Accessible name, when no visible <Label for=…> points at this.
aria-labelledby string — Id of the element that names this, instead of aria-label.
filter (option: TagsInputOption, query: string) => boolean (option, query) => option.label.toLowerCase().includes(query.toLowerCase()) Match an option against what was typed. Default: case-insensitive substring.
emptyLabel string 'No matches' Shown when the filter matches nothing and nothing can be created. Localise here.
createLabel (text: string) => string (text: string) => `Add "${text}"` The list item that adds typed text as a new tag. Localise here.
removeLabel (label: string) => string (label: string) => `Remove ${label}` Accessible name of a tag's × button. Localise here.
announce (label: string, change: 'added' | 'removed') => string (label: string, change: 'added' | 'removed') => `${label} ${change}` What a screen reader hears when a tag comes or goes. Localise here.
onValuesChange (values: string[]) => void — Every change to the tags.
onCreate (value: string) => void — A tag that is not one of options was added — every tag, without options.

Everything not listed above is spread onto the underlying element (HTMLInputAttributes): aria-*, data-*, event handlers, and the rest of the native attributes.

Types

export type TagsInputOption = {
	value: string;
	label: string;
	disabled?: boolean;
};

Keyboard

Key Does
Enter / , add the typed text as a tag — with options, toggle the active one
↑ / ↓ move the active option, when there are options
Home / End first / last option, while the list is open
← / Backspace from the start of the field, move onto the last tag
← / → move between tags; → past the last returns to the field
Backspace / Delete remove the focused tag
Escape close the list, then clear the typed text

Example

The markup behind the screenshot above, lifted from src/routes/shots/ — so it compiles, and it is exactly what produced the image.

<TagsInput
	options={[
		{ value: 'media', label: 'media' },
		{ value: 'backups', label: 'backups' },
		{ value: 'code', label: 'code' }
	]}
	values={['media', 'backups', 'nightly']}
	placeholder="Add a tag"
	aria-label="Tags"
/>

Forms · Field · Input · FileInput · FileList · Textarea · CodeEditor · Combobox · RadioGroup · NumberInput · InlineEdit · Label · Select · MultiSelect · DatePicker · Checkbox · Switch · Slider


← All components · Getting started · Theming · source