<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>
Usage
import { TagsInput } from '@hina-ui/vue'
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>
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.
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>
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>
Clearable
clearable shows a clear button at the end that removes every tag at once and fires clear.
<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>
Sizes
size has three steps, sm, md and lg. With no tags the height equals an input of the same step.
<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>
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.
<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>
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.
<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>
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
maxor duplicates are not added and fireinvalid.
Accessibility
- Name the text input with
aria-labeloraria-labelledby; undeclared attributes land on it. - Every tag’s remove button carries a name from the locale.
invalidalso setsaria-invalidon 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 |