TagsInput

Enters several values as removable tags.

GalgameLight novel
<script setup lang="ts">
  import { ref } from 'vue'
  import { TagsInput } from '@hina-ui/vue'

  const tags = ref(['Galgame', 'Light novel'])
</script>

<template>
  <TagsInput v-model="tags" placeholder="Add a tag" aria-label="Tags" class="max-w-sm" />
</template>
vue

Usage

import { TagsInput } from '@hina-ui/vue'
ts

A tags input keeps several values in one field, each shown as a removable tag. v-model binds an array of strings. Typing text and pressing Enter, or typing the delimiter, turns the text into a tag; when the tags no longer fit on one line the field grows downward. Attributes it does not declare land on the inner text input, so name it with aria-label or aria-labelledby.

No tags yet

<script setup lang="ts">
  import { ref } from 'vue'
  import { Stack, TagsInput, Text } from '@hina-ui/vue'

  const tags = ref<string[]>([])
</script>

<template>
  <Stack gap="sm" class="w-full max-w-sm">
    <TagsInput v-model="tags" placeholder="Type and press Enter" aria-label="Keywords" />
    <Text tone="muted" size="sm">{{ tags.length ? tags.join(', ') : 'No tags yet' }}</Text>
  </Stack>
</template>
vue

It differs from MultiSelect in where the values come from: a multi select picks from given options, a tags input accepts any text.

Examples

Limit and duplicates

max caps the number of tags and duplicate decides whether repeated values are allowed. Rejected input fires invalid with the text.

Slice of lifeRomance

Up to three, no duplicates

<script setup lang="ts">
  import { ref } from 'vue'
  import { Stack, TagsInput, Text } from '@hina-ui/vue'

  const tags = ref(['Slice of life', 'Romance'])
  const rejected = ref('')
</script>

<template>
  <Stack gap="sm" class="w-full max-w-sm">
    <TagsInput
      v-model="tags"
      :max="3"
      placeholder="Up to three"
      aria-label="Genres"
      @invalid="rejected = $event"
    />
    <Text tone="muted" size="sm">
      {{ rejected ? `"${rejected}" was not added` : 'Up to three, no duplicates' }}
    </Text>
  </Stack>
</template>
vue

Delimiter and paste

delimiter defaults to a comma and accepts another character or a regular expression. addOnPaste is on by default and splits pasted text by the delimiter; addOnBlur also turns leftover text into a tag when the input loses focus.

<script setup lang="ts">
  import { ref } from 'vue'
  import { TagsInput } from '@hina-ui/vue'

  const tags = ref<string[]>([])
</script>

<template>
  <TagsInput
    v-model="tags"
    delimiter=" "
    add-on-blur
    placeholder="Space separated, paste a whole list"
    aria-label="Aliases"
    class="max-w-sm"
  />
</template>
vue

Clearable

clearable shows a clear button at the end that removes every tag at once and fires clear.

KeyType-MoonNitroplus
<script setup lang="ts">
  import { ref } from 'vue'
  import { TagsInput } from '@hina-ui/vue'

  const tags = ref(['Key', 'Type-Moon', 'Nitroplus'])
</script>

<template>
  <TagsInput
    v-model="tags"
    clearable
    placeholder="Add a studio"
    aria-label="Studios"
    class="max-w-sm"
  />
</template>
vue

Sizes

size has three steps, sm, md and lg. With no tags the height equals an input of the same step.

Small
Medium
Large
<script setup lang="ts">
  import { Stack, TagsInput } from '@hina-ui/vue'
</script>

<template>
  <Stack gap="sm" class="w-full max-w-sm">
    <TagsInput size="sm" :model-value="['Small']" aria-label="Small" />
    <TagsInput size="md" :model-value="['Medium']" aria-label="Medium" />
    <TagsInput size="lg" :model-value="['Large']" aria-label="Large" />
  </Stack>
</template>
vue

States

invalid gives the field a warning color, disabled disables the group and its tags cannot be removed. variant="secondary" is the flat form used inside a surface.

Invalid
Disabled
Secondary
<script setup lang="ts">
  import { Stack, TagsInput } from '@hina-ui/vue'
</script>

<template>
  <Stack gap="sm" class="w-full max-w-sm">
    <TagsInput :model-value="['Invalid']" invalid aria-label="Invalid" />
    <TagsInput :model-value="['Disabled']" disabled aria-label="Disabled" />
    <TagsInput :model-value="['Secondary']" variant="secondary" aria-label="Secondary" />
  </Stack>
</template>
vue

In a form

Inside a FormField the label points at the input, and the description and error message are rendered by the field; validation rules and submission belong to the Form. The value is an array of strings, so count limits go on the array and length limits on the items.

Press Enter to add, up to five

<script setup lang="ts">
  import { reactive, ref } from 'vue'
  import * as v from 'valibot'
  import { Button, Form, FormField, TagsInput, Text } from '@hina-ui/vue'

  const schema = v.object({
    aliases: v.pipe(
      v.array(v.pipe(v.string(), v.maxLength(20, 'An alias is at most 20 characters'))),
      v.minLength(1, 'Add at least one alias'),
      v.maxLength(5, 'At most five aliases'),
    ),
  })

  const values = reactive({ aliases: [] as string[] })
  const saved = ref('')

  async function save(data: unknown) {
    await new Promise(resolve => setTimeout(resolve, 600))
    saved.value = JSON.stringify(data)
  }
</script>

<template>
  <Form v-slot="{ submitting }" :values="values" :rules="schema" class="w-80" @submit="save">
    <FormField name="aliases" label="Aliases" description="Press Enter to add, up to five" required>
      <TagsInput v-model="values.aliases" />
    </FormField>
    <Button type="submit" :loading="submitting" class="self-start">Save</Button>
    <Text v-if="saved" tone="muted" size="sm">Saved: {{ saved }}</Text>
  </Form>
</template>
vue

Behavior

  • Enter or the delimiter turns the current text into a tag and clears the input.
  • With an empty input, Backspace removes the last tag.
  • Clicking a tag or the blank area of the field focuses the input.
  • Every tag ends with a remove button.
  • Values beyond max or duplicates are not added and fire invalid.

Accessibility

  • Name the text input with aria-label or aria-labelledby; undeclared attributes land on it.
  • Every tag’s remove button carries a name from the locale.
  • invalid also sets aria-invalid on the text input.

API

Props

Prop
Type
Default
Description
modelValue
string[]
[]
The tags
placeholder
string
—
Placeholder of the text input
max
number
0
Maximum number of tags, 0 for no limit
duplicate
boolean
false
Whether repeated tags are allowed
delimiter
string | RegExp
','
Delimiter
addOnPaste
boolean
true
Whether pasted text is split into tags
addOnBlur
boolean
false
Whether leftover text becomes a tag on blur
clearable
boolean
false
Whether to show the clear button
name
string
—
Form field name
variant
'primary' | 'secondary'
'primary'
Variant
size
'sm' | 'md' | 'lg'
'md'
Size
disabled
boolean
false
Whether the group is disabled
invalid
boolean
false
Whether the group failed validation
class
string
—
Classes appended to the root element

Events

Event
Payload
Description
update:modelValue
value: string[]
The tags changed
invalid
value: string
Input was rejected
clear
—
The clear button emptied the tags