Vitral 0.2
Form

InputTag

A field whose value is a list of short strings, each shown as a removable tag, added with Enter, a separator or a paste.

Import

main.ts
import { InputTag } from '@vitral/vue';

Basic

Type and press Enter, or type a comma. Backspace in an empty box removes the last tag.

  • vue
  • design tokens
2 selected: vue · design tokens
<script setup lang="ts">import { InputTag, Label, StackPanel } from '@vitral/vue';import { ref } from 'vue'; const topics = ref(['vue', 'design tokens']);</script> <template>    <StackPanel spacing="0.375rem" style="min-width: 16rem">        <Label for="tag-topics">Topics</Label>        <InputTag id="tag-topics" v-model="topics" placeholder="Add a topic" fluid />        <small style="color: var(--vt-text-muted-color)">{{ topics.length }} selected: {{ topics.join(' · ') || 'none' }}</small>    </StackPanel></template>

Checked as they are added

`validate` refuses a tag and leaves the text in the box to be fixed; `reject` says what happened, so the field can explain itself. Several separators are allowed: this one takes a comma, a semicolon or a space.

    <script setup lang="ts">import { InputTag, Label, Message, StackPanel } from '@vitral/vue';import { ref } from 'vue'; const emails = ref<string[]>([]);const rejected = ref(''); const isEmail = (tag: string) => /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(tag); function onReject(event: { value: string; reason: string }) {    rejected.value = event.reason === 'duplicate' ? `“${event.value}” is already there.` : `“${event.value}” is not an address.`;}</script> <template>    <StackPanel spacing="0.375rem" style="min-width: 16rem">        <Label for="tag-emails">Invite by email</Label>        <InputTag            id="tag-emails"            v-model="emails"            :separator="[',', ';', ' ']"            :validate="isEmail"            placeholder="name@example.com"            :invalid="!!rejected"            fluid            @add="rejected = ''"            @reject="onReject"        />        <Message v-if="rejected" severity="danger" size="small" variant="simple">{{ rejected }}</Message>    </StackPanel></template>

    A maximum

    `max` stops the list where it should stop; what is refused is reported rather than dropped quietly.

    • red
    • green
    <script setup lang="ts">import { InputTag, Label, Message, StackPanel } from '@vitral/vue';import { ref } from 'vue'; const colours = ref(['red', 'green']);const rejected = ref(''); function onReject(event: { value: string; reason: string }) {    rejected.value = event.reason === 'duplicate' ? `“${event.value}” is already there.` : 'That is as many as it takes.';}</script> <template>    <StackPanel spacing="0.375rem" style="min-width: 16rem">        <Label for="tag-max">Up to three colours</Label>        <InputTag id="tag-max" v-model="colours" :max="3" placeholder="Add a colour" fluid @add="rejected = ''" @remove="rejected = ''" @reject="onReject" />        <Message v-if="rejected" severity="danger" size="small" variant="simple">{{ rejected }}</Message>    </StackPanel></template>

    Sizes and states

    The field chrome is the shared one: the same sizes, variants, invalid and disabled states as every other input.

    • accessible
    • accessible
    • accessible
    • accessible
    • accessible
    • accessible
    • accessible
    <script setup lang="ts">import { InputTag, StackPanel } from '@vitral/vue';import { ref } from 'vue'; const keywords = ref(['accessible']);</script> <template>    <StackPanel spacing="1rem" style="width: 100%; max-width: 30rem">        <InputTag v-model="keywords" size="small" aria-label="Small" fluid />        <InputTag v-model="keywords" aria-label="Default" fluid />        <InputTag v-model="keywords" size="large" aria-label="Large" fluid />        <InputTag v-model="keywords" variant="filled" aria-label="Filled" fluid />        <InputTag v-model="keywords" invalid aria-label="Invalid" fluid />        <InputTag v-model="keywords" disabled aria-label="Disabled" fluid />        <InputTag v-model="keywords" readonly aria-label="Read-only" fluid />    </StackPanel></template>

    API

    Read from packages/vue/src/components/InputTag/types.ts, so it says what the component actually accepts.

    Props

    NameTypeDescription
    separatorstring | readonly string[]What ends a tag as it is typed, besides Enter. Defaults to a comma.
    maxnumberHow many tags the field takes.
    allowDuplicatebooleanKeep a tag the list already has. Off by default.
    addOnBlurbooleanAdd what is in the box when focus leaves the field. On by default.
    validate(tag: string) => booleanRefuse a tag: the text stays in the box for the reader to fix.
    placeholderstring—
    removeIconIconProp—
    sizeSize—
    variantInputVariantDefaults to the plugin's `inputVariant`.
    invalidboolean—
    disabledboolean—
    readonlyboolean—
    fluidboolean—
    namestringThe name the field is submitted under; the tags are sent one by one.

    Plus pt, dt and unstyled from BaseProps, see pass-through and unstyled mode.

    Emits

    EventPayloadDescription
    addevent: { originalEvent: Event; value: string; tags: string[] }A tag was added, by Enter, by a separator, by a paste or on blur.
    removeevent: { originalEvent: Event; value: string; index: number; tags: string[] }A tag was taken out, by its button or by a key.
    rejectevent: { originalEvent: Event; value: string; reason: 'duplicate' | 'max' | 'invalid' }Something was refused: the list was full, the tag was already there, or `validate` said no.
    focusevent: FocusEvent—
    blurevent: FocusEvent—

    Slots

    NameSlot propsDescription
    tag(props: { value: string; index: number; remove: (event: Event) => void })Replaces a tag's content, inside the tag's own box.
    removeicon(props: { value: string; index: number })—