<script setup>
import { TagInput } from "@dicehub/kappa/components/tag-input";
</script>
<template>
<TagInput.Root :default-value="['solver', 'mesh', 'review']" placeholder="Add a tag…">
<TagInput.Label>Project tags</TagInput.Label>
<TagInput.Control>
<TagInput.Context v-slot="{ value }">
<TagInput.Item
v-for="(tag, index) in value"
:key="`${tag}-${index}`"
:index="index"
:value="tag"
>
<TagInput.ItemPreview>
<TagInput.ItemText>{{ tag }}</TagInput.ItemText>
<TagInput.ItemDeleteTrigger />
</TagInput.ItemPreview>
<TagInput.ItemInput />
</TagInput.Item>
</TagInput.Context>
<TagInput.Input aria-label="Add project tag" />
<TagInput.ClearTrigger />
</TagInput.Control>
<TagInput.HiddenInput name="project-tags" />
</TagInput.Root>
</template>Installation
Tag Input is part of the main Kappa package. Kappa wraps the Ark UI Vue tags-input primitive and adds the public namespace, dense sizes, default action icons, and semantic-token styles.
Barrel
import { TagInput } from "@dicehub/kappa";Granular
import { TagInput } from "@dicehub/kappa/components/tag-input";Usage
Render the current values from TagInput.Context. Each Item needs its value and stable index. Include ItemInput to support tag editing andHiddenInput for native form submission.
<script setup>
import { TagInput } from "@dicehub/kappa/components/tag-input";
</script>
<template>
<TagInput.Root :default-value="['geometry', 'ready']" placeholder="Type and press Enter…">
<TagInput.Label>Labels</TagInput.Label>
<TagInput.Control>
<TagInput.Context v-slot="{ value }">
<TagInput.Item
v-for="(tag, index) in value"
:key="`${tag}-${index}`"
:index="index"
:value="tag"
>
<TagInput.ItemPreview>
<TagInput.ItemText>{{ tag }}</TagInput.ItemText>
<TagInput.ItemDeleteTrigger />
</TagInput.ItemPreview>
<TagInput.ItemInput />
</TagInput.Item>
</TagInput.Context>
<TagInput.Input aria-label="Add label" />
<TagInput.ClearTrigger />
</TagInput.Control>
<TagInput.HiddenInput name="labels" />
</TagInput.Root>
</template>Composition
TagInput.Root
├── TagInput.Label
├── TagInput.Control
│ ├── TagInput.Context
│ │ └── TagInput.Item [value, index]
│ │ ├── TagInput.ItemPreview
│ │ │ ├── TagInput.ItemText
│ │ │ └── TagInput.ItemDeleteTrigger
│ │ └── TagInput.ItemInput
│ ├── TagInput.Input
│ └── TagInput.ClearTrigger (optional)
├── TagInput.HiddenInput (optional)
└── TagInput.ItemContext (optional)Ark UI owns tag creation, removal, editing, focus movement, paste parsing, validation, announcements, and controlled or uncontrolled state. Kappa keeps these parts open for custom content.
Rootowns state.RootProvideraccepts a machine fromuseTagsInput.Controlwraps all visible values, the entry input, and the optional clear action.ItemPreviewcontains display text and its delete action.ItemInputappears in edit mode.Contextexposes the values and root API.ItemContextexposes item state.
<script setup>
import { TagInput, useTagsInput } from "@dicehub/kappa/components/tag-input";
const tagsInput = useTagsInput({ defaultValue: ["provider-owned"] });
</script>
<template>
<TagInput.RootProvider :value="tagsInput">
<TagInput.Label>Provider tags</TagInput.Label>
<TagInput.Control>
<!-- Render TagInput.Item parts from TagInput.Context. -->
<TagInput.Input aria-label="Add provider tag" />
</TagInput.Control>
</TagInput.RootProvider>
</template>Examples
Controlled Value
Keep the tag array in application state with v-model.
<script setup>
import { ref } from "vue";
import { TagInput } from "@dicehub/kappa/components/tag-input";
const tags = ref(["mesh", "review"]);
</script>
<template>
<TagInput.Root v-model="tags" placeholder="Add filter…">
<TagInput.Label>Active filters</TagInput.Label>
<TagInput.Control>
<TagInput.Context v-slot="{ value }">
<TagInput.Item
v-for="(tag, index) in value"
:key="`${tag}-${index}`"
:index="index"
:value="tag"
>
<TagInput.ItemPreview>
<TagInput.ItemText>{{ tag }}</TagInput.ItemText>
<TagInput.ItemDeleteTrigger />
</TagInput.ItemPreview>
<TagInput.ItemInput />
</TagInput.Item>
</TagInput.Context>
<TagInput.Input aria-label="Add active filter" />
<TagInput.ClearTrigger />
</TagInput.Control>
</TagInput.Root>
<output>{{ tags.join(" · ") }}</output>
</template>Paste, Validation, and Limit
Split pasted text, normalize each value, validate its format, and enforce a maximum count.
<script setup lang="ts">
import { ref } from "vue";
import { TagInput, type TagInputProps } from "@dicehub/kappa/components/tag-input";
const status = ref("Use 2–16 letters, numbers, or hyphens. Maximum four tags.");
const validate: NonNullable<TagInputProps["validate"]> = ({ inputValue }) =>
inputValue
.split(/[,\n]/)
.filter(Boolean)
.every((value) => /^[a-z0-9-]{2,16}$/i.test(value.trim()));
</script>
<template>
<TagInput.Root
add-on-paste
:delimiter="/[,\n]/"
:max="4"
:sanitize-value="(value) => value.trim().toLowerCase()"
:validate="validate"
@value-invalid="status = $event.reason"
>
<TagInput.Label>Build targets</TagInput.Label>
<TagInput.Control>
<TagInput.Context v-slot="{ value }">
<TagInput.Item
v-for="(tag, index) in value"
:key="`${tag}-${index}`"
:index="index"
:value="tag"
>
<TagInput.ItemPreview>
<TagInput.ItemText>{{ tag }}</TagInput.ItemText>
<TagInput.ItemDeleteTrigger />
</TagInput.ItemPreview>
<TagInput.ItemInput />
</TagInput.Item>
</TagInput.Context>
<TagInput.Input aria-label="Add build target" placeholder="Paste or enter tags…" />
<TagInput.ClearTrigger />
</TagInput.Control>
</TagInput.Root>
<output role="status">{{ status }}</output>
</template>Sizes
Match tag density to compact tools, forms, and larger application surfaces.
<script setup>
import { TagInput } from "@dicehub/kappa/components/tag-input";
const sizes = ["xs", "sm", "base", "lg"];
</script>
<template>
<TagInput.Root
v-for="size in sizes"
:key="size"
:default-value="[size]"
:size="size"
:aria-label="`${size} tag input`"
>
<TagInput.Control>
<TagInput.Context v-slot="{ value }">
<TagInput.Item
v-for="(tag, index) in value"
:key="`${tag}-${index}`"
:index="index"
:value="tag"
>
<TagInput.ItemPreview>
<TagInput.ItemText>{{ tag }}</TagInput.ItemText>
<TagInput.ItemDeleteTrigger />
</TagInput.ItemPreview>
<TagInput.ItemInput />
</TagInput.Item>
</TagInput.Context>
<TagInput.Input :aria-label="`Add ${size} tag`" placeholder="Add tag…" />
</TagInput.Control>
</TagInput.Root>
</template>Invalid, Read-only, and Disabled
Pass state through the root so every machine part stays synchronized.
<script setup>
import { TagInput } from "@dicehub/kappa/components/tag-input";
</script>
<template>
<TagInput.Root :default-value="['invalid']" invalid>
<!-- Label, Control, tag items, and Input -->
</TagInput.Root>
<TagInput.Root :default-value="['locked', 'stable']" read-only>
<!-- Label, Control, tag items, and Input -->
</TagInput.Root>
<TagInput.Root :default-value="['unavailable']" disabled>
<!-- Label, Control, tag items, and Input -->
</TagInput.Root>
</template>Accessibility
- Use a visible
Label. Add a distinct accessible name to the entryInputwhen needed. - Enter or the delimiter adds a tag. Backspace selects and then removes the previous tag.
- Arrow keys move the tag highlight. Delete removes the highlighted tag.
- Enter or double-click edits a tag when
editableis enabled. Escape cancels the edit. - Delete and clear actions receive localized names from Ark UI. Override
translationsfor another language. - Invalid, disabled, read-only, focus, and highlighted states remain visible in both themes and forced colors.
See the official Ark UI Tags Input documentationfor the underlying machine behavior and accessibility contract.
API Reference
TagInput.Root
The root accepts Ark UI tags-input props plus Kappa's visual size.
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue / v-model | string[] | — | Controlled tag values. |
defaultValue | string[] | [] | Initial values for uncontrolled use. |
inputValue / v-model:inputValue | string | — | Controlled text in the entry input. |
delimiter | string | RegExp | "," | Adds or splits tags when this delimiter is entered or pasted. |
addOnPaste | boolean | false | Splits pasted text into tags with delimiter. |
blurBehavior | "add" | "clear" | — | Adds or clears unfinished input when focus leaves. |
editable | boolean | true | Lets a user edit a tag with Enter or double-click. |
max / maxLength | number | ∞ / — | Limits the tag count and entry length. |
validate | (details) => boolean | — | Accepts or rejects a proposed tag. |
sanitizeValue | (value) => string | trim | Normalizes each tag before it is added. |
allowDuplicates / allowOverflow | boolean | false | Changes duplicate and maximum-value behavior. |
disabled / readOnly / required / invalid | boolean | false | Native and validation states passed to Ark UI. |
name / form | string | — | Connects HiddenInput to native form submission. |
size | "xs" | "sm" | "base" | "lg" | "base" | Kappa control and tag density. |
Parts
| Part | Element | Description |
|---|---|---|
TagInput.Root | div | Owns the Ark tags-input machine and state. |
TagInput.RootProvider | div | Uses an external useTagsInput machine. |
TagInput.Label | label | Names and focuses the entry input. |
TagInput.Control | div | Wraps tag items, entry input, and clear action. |
TagInput.Item | div | Provides one value and index to its item parts. |
TagInput.ItemPreview | div | Shows a tag when it is not in edit mode. |
TagInput.ItemText | span | Displays consumer-provided tag text. |
TagInput.ItemDeleteTrigger | button | Removes its tag. A default close icon is included. |
TagInput.ItemInput | input | Edits a tag after Enter or double-click. |
TagInput.Input | input | Accepts new tag text and keyboard commands. |
TagInput.ClearTrigger | button | Clears all tags. A default close icon is included. |
TagInput.HiddenInput | input | Provides the serialized value for native forms. |
TagInput.Context / ItemContext | slot | Exposes root or item machine state to scoped slots. |
Events
| Event | Payload | Description |
|---|---|---|
update:modelValue | string[] | Emitted when controlled tag values change. |
update:inputValue | string | Emitted when controlled entry text changes. |
valueChange | { value: string[] } | Emitted after a tag is added, edited, or removed. |
valueInvalid | { reason: "rangeOverflow" | "invalidTag" } | Emitted when max or validate rejects a tag. |
highlightChange | { highlightedValue } | Emitted when keyboard or pointer highlight changes. |
inputValueChange | { inputValue } | Emitted when entry text changes. |
focusOutside / interactOutside / pointerDownOutside | outside event | Ark UI outside-interaction events. |
Data Attributes
| Attribute | Value | Description |
|---|---|---|
data-slot | "tag-input" / "tag-input-control" / … | Stable Kappa selectors for named parts. |
data-size | "xs" | "sm" | "base" | "lg" | Resolved Kappa density on the root. |
data-focus / data-empty | "" | Root and control focus or empty state from Ark UI. |
data-highlighted | "" | The tag selected by pointer or keyboard navigation. |
data-invalid / data-disabled / data-readonly | "" | Validation and availability states from Ark UI. |
Exports
| Export | Description |
|---|---|
TagInput | Compound namespace and root component. |
TagInput.Root / RootProvider | Machine-owned and provider-owned roots. |
TagInput.Label / Control / Item* / Input / ClearTrigger / HiddenInput | Named compound parts. |
TagInput.Context / ItemContext | Scoped access to current root and item state. |
TagInputProps / TagInputEmits | Kappa root props and Vue event contracts. |
TagInputApi / TagInputContextValue / TagInputItemContextValue | Provider and context API types. |
useTagsInput / useTagsInputContext / useTagsInputItemContext / tagsInputAnatomy | Ark UI hooks and anatomy re-exports. |
TAG_INPUT_SIZES / TAG_INPUT_DEFAULT_SIZE | Kappa density values and default. |