Inputs

Tag Input

A field that turns typed text into removable tags. Type a value and press Enter.

Installation

pnpm dlx @mielui/svelte add tag-input

Usage

Bind tags on Root to the list. Inside Root, List renders the tags and Input takes new ones. Enter or a comma adds the typed text, and Backspace in an empty input removes the last tag.

<script lang="ts">
    import * as TagInput from '@mielui/svelte/components/tag-input';

    let tags = $state(['svelte']);
</script>

<TagInput.Root bind:tags label="Topics">
    <TagInput.List />
    <TagInput.Input placeholder="Add a topicโ€ฆ" />
</TagInput.Root>

Set name to submit each tag as a hidden form value. A disabled field submits nothing. Bind element on Input to reach the text input.

Examples

Saving topics

A tag field with a count, a clear button, and a save button that reads the current tags.

Adding tags

Enter adds the typed text, and so does any character in delimiters, a comma by default. addOnBlur adds what is left in the field when it loses focus, and addOnPaste splits pasted text on the same delimiters. Both are on by default. normalize cleans each tag before it is added. A tag that already exists is refused unless allowDuplicates is set. Bind query to read or set the text still being typed.

Responding to changes

onAdd and onRemove report single tags. onTagsChange reports the whole list.

Validation

Return false or a message from validate to refuse a tag. onReject receives the refused value, a code, and a readable reason.

Maximum

max caps the list. Extra tags are refused with the max-tags code.

Required

required blocks native form submission until there is at least one tag. labels.required sets the message the browser shows.

Labels

Built-in text is English. Pass a labels object to Root to translate the remove button's name, the refusal reasons, the announcements after a tag is added or removed, and the required message. TagInputLabels types it. Every key is optional, and omitted keys keep their English default.

<TagInput.Root
    bind:tags
    label="Sujets"
    labels={{
        remove: (tag) => `Supprimer ${tag}`,
        duplicate: (tag) => `${tag} est dรฉjร  dans la liste.`,
        maxReached: (max) => `${max} sujets au maximum.`,
        added: (tag, count) => `${tag} ajoutรฉ, ${count} au total.`,
        removed: (tag, count) => `${tag} retirรฉ, ${count} au total.`,
        required: 'Ajoutez au moins un sujet.'
    }}
>
    <TagInput.List />
    <TagInput.Input placeholder="Ajouter un sujetโ€ฆ" />
</TagInput.Root>

API reference

TagInput.Root

Owns tags and draft entry state.

  • labels
    TagInputLabels

    Overrides the built-in text and accessible names. Every key is optional and English is the fallback.

  • tags Bindable
    string[]

    Current tags. Bindable.

    Default []

  • query Bindable
    string

    Text in the input. Bindable.

    Default ''

  • max
    number

    Largest number of tags.

  • allowDuplicates
    boolean

    Allows the same tag more than once.

    Default false

  • disabled
    boolean

    Prevents interaction and dims the control.

    Default false

  • label
    string

    Visible label linked to the control.

  • description
    string

    Supporting text under the label, announced with the control.

  • invalid
    boolean

    Marks the field invalid. It defaults to true when there are issues.

  • issues
    readonly Readonly<{ message: string; }>[]

    Validation issues shown under the control in place of the description.

    Default []

  • name
    string

    Field name submitted with the form.

  • required
    boolean

    Requires at least one tag for native form submission. Carried by a dedicated validation anchor, since hidden inputs are barred from constraint validation.

    Default false

  • variant
    TagInputVariant

    outline has a border. secondary has a filled background.

    Default 'outline'

  • validate
    ((tag: string) => string | boolean)

    Returns true to accept a tag, or a message that explains why it is refused.

  • normalize
    ((tag: string) => string)

    Cleans a tag before it is added, such as trimming or lowercasing it.

  • delimiters
    string[]

    Characters that finish a tag as you type.

    Default [',']

  • addOnBlur
    boolean

    Adds the typed text as a tag when the field loses focus.

    Default true

  • addOnPaste
    boolean

    Splits pasted text into tags.

    Default true

  • onTagsChange
    ((tags: string[]) => void)

    Called with the new list when tags change.

  • onAdd
    ((tag: string) => void)

    Called with each tag that is added.

  • onRemove
    ((tag: string) => void)

    Called with each tag that is removed.

  • onReject
    ((rejection: TagInputRejection) => void)

    Called when a tag is refused, with the reason.

  • id
    string

    Id of the element.

  • class
    string

    Classes added to the element.

  • children
    Snippet<[]>

    Content rendered inside.

TagInput.Input

Accepts new tag text.

  • placeholder
    string

    Text shown while there is no value.

    Default 'Add a tagโ€ฆ'

  • element Bindable
    HTMLInputElement

    Bindable reference to the DOM element.

    Default undefined

  • class
    string

    Classes added to the element.

TagInput.List

Groups the entered tags.

  • label
    string

    Accessible name of the tag list.

    Default 'Tags'

  • class
    string

    Classes added to the element.

  • children
    Snippet<[]>

    Content rendered inside.

TagInput.Tag

Renders one tag with its remove control.

  • value Required
    string

    Text of the tag.

  • index
    number

    Position of the tag in the list.

  • removable
    boolean

    Shows the remove button.

    Default true

  • onRemove
    ((tag: string) => void)

    Called with each tag that is removed.

  • class
    string

    Classes added to the element.

  • children
    Snippet<[]>

    Content rendered inside.