Skip to content
Kappa
Tag Input
@dicehub/kappav0.4.2

Tag Input

Collects removable text values with accessible keyboard, paste, edit, and form behavior.

solver
mesh
review
<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.

geometry
ready
<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

Typical part hierarchy
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.

  • Root owns state. RootProvider accepts a machine from useTagsInput.
  • Control wraps all visible values, the entry input, and the optional clear action.
  • ItemPreview contains display text and its delete action. ItemInput appears in edit mode.
  • Context exposes the values and root API. ItemContext exposes 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.

mesh
review
mesh · review
<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.

Use 2–16 letters, numbers, or hyphens. Maximum four tags.
<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.

xs
sm
base
lg
<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.

invalid
locked
stable
unavailable
<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 entry Input when 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 editable is enabled. Escape cancels the edit.
  • Delete and clear actions receive localized names from Ark UI. Override translations for 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.

PropTypeDefaultDescription
modelValue / v-modelstring[]—Controlled tag values.
defaultValuestring[][]Initial values for uncontrolled use.
inputValue / v-model:inputValuestring—Controlled text in the entry input.
delimiterstring | RegExp","Adds or splits tags when this delimiter is entered or pasted.
addOnPastebooleanfalseSplits pasted text into tags with delimiter.
blurBehavior"add" | "clear"—Adds or clears unfinished input when focus leaves.
editablebooleantrueLets a user edit a tag with Enter or double-click.
max / maxLengthnumber∞ / —Limits the tag count and entry length.
validate(details) => boolean—Accepts or rejects a proposed tag.
sanitizeValue(value) => stringtrimNormalizes each tag before it is added.
allowDuplicates / allowOverflowbooleanfalseChanges duplicate and maximum-value behavior.
disabled / readOnly / required / invalidbooleanfalseNative and validation states passed to Ark UI.
name / formstring—Connects HiddenInput to native form submission.
size"xs" | "sm" | "base" | "lg""base"Kappa control and tag density.

Parts

PartElementDescription
TagInput.RootdivOwns the Ark tags-input machine and state.
TagInput.RootProviderdivUses an external useTagsInput machine.
TagInput.LabellabelNames and focuses the entry input.
TagInput.ControldivWraps tag items, entry input, and clear action.
TagInput.ItemdivProvides one value and index to its item parts.
TagInput.ItemPreviewdivShows a tag when it is not in edit mode.
TagInput.ItemTextspanDisplays consumer-provided tag text.
TagInput.ItemDeleteTriggerbuttonRemoves its tag. A default close icon is included.
TagInput.ItemInputinputEdits a tag after Enter or double-click.
TagInput.InputinputAccepts new tag text and keyboard commands.
TagInput.ClearTriggerbuttonClears all tags. A default close icon is included.
TagInput.HiddenInputinputProvides the serialized value for native forms.
TagInput.Context / ItemContextslotExposes root or item machine state to scoped slots.

Events

EventPayloadDescription
update:modelValuestring[]Emitted when controlled tag values change.
update:inputValuestringEmitted 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 / pointerDownOutsideoutside eventArk UI outside-interaction events.

Data Attributes

AttributeValueDescription
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

ExportDescription
TagInputCompound namespace and root component.
TagInput.Root / RootProviderMachine-owned and provider-owned roots.
TagInput.Label / Control / Item* / Input / ClearTrigger / HiddenInputNamed compound parts.
TagInput.Context / ItemContextScoped access to current root and item state.
TagInputProps / TagInputEmitsKappa root props and Vue event contracts.
TagInputApi / TagInputContextValue / TagInputItemContextValueProvider and context API types.
useTagsInput / useTagsInputContext / useTagsInputItemContext / tagsInputAnatomyArk UI hooks and anatomy re-exports.
TAG_INPUT_SIZES / TAG_INPUT_DEFAULT_SIZEKappa density values and default.