Skip to content
Dethink Components

ComponentsTagInput

TagInput

Let users add, edit, and remove text tags.

On this page

Use this component from the local workspace package. Follow the setup guide first. A public npm package and registry are not available yet.

View registry files

Import the component into your page or component file.

Usage
import { TagInput } from "@dethink/components";

Type a value and press Enter, comma, or Tab. Backspace removes the previous chip when the input is empty.

Basic

Free-form labels with helper copy and native form serialization.

Labels

Press Enter, comma, or Tab to add a label.

Show sourceexamples/tag-input/basic.tsx
examples/tag-input/basic.tsx
"use client";

import { TagInput } from "@dethink/components";

export function TagInputBasic() {
  return (
    <div className="mx-auto max-w-sm">
      <TagInput
        description="Press Enter, comma, or Tab to add a label."
        label="Labels"
        name="labels"
        placeholder="Add label"
      />
    </div>
  );
}

Controlled

Apps can own the tag array and render derived submission state.

Notification tags
finance

Serialized tags: finance

Show sourceexamples/tag-input/controlled.tsx
examples/tag-input/controlled.tsx
"use client";

import { useState } from "react";
import { FieldDescription, Stack, TagInput } from "@dethink/components";

export function TagInputControlled() {
  const [tags, setTags] = useState(["finance"]);

  return (
    <div className="mx-auto max-w-sm">
      <Stack gap="3">
        <TagInput
          label="Notification tags"
          name="notificationTags"
          onValueChange={setTags}
          value={tags}
        />
        <FieldDescription>
          Serialized tags: {tags.join(", ") || "none"}
        </FieldDescription>
      </Stack>
    </div>
  );
}

Validation

Duplicate prevention is built in; custom validation blocks tags with a visible message.

Campaign tags
#finance

Tags must start with # and stay short.

Show sourceexamples/tag-input/validation.tsx
examples/tag-input/validation.tsx
"use client";

import { TagInput } from "@dethink/components";

export function TagInputValidation() {
  return (
    <div className="mx-auto max-w-sm">
      <TagInput
        defaultValue={["#finance"]}
        description="Tags must start with # and stay short."
        label="Campaign tags"
        maxTagLength={16}
        name="campaignTags"
        validateTag={(value) =>
          value.startsWith("#") ? null : "Tags must start with #."
        }
      />
    </div>
  );
}

Form states

Invalid, required, read-only, and disabled states use the same field styling contract as other inputs.

Recipient tags
Inherited labels
finance
renewal
Locked labels
locked
Show sourceexamples/tag-input/states.tsx
examples/tag-input/states.tsx
"use client";

import { Stack, TagInput } from "@dethink/components";

export function TagInputStates() {
  return (
    <div className="mx-auto max-w-sm">
      <Stack gap="4">
        <TagInput
          errorMessage="Add at least one recipient tag."
          invalid
          label="Recipient tags"
          required
        />
        <TagInput
          readOnly
          defaultValue={["finance", "renewal"]}
          label="Inherited labels"
        />
        <TagInput disabled defaultValue={["locked"]} label="Locked labels" />
      </Stack>
    </div>
  );
}

Theme, RTL, and wrapping

Nested provider tokens, compact density, RTL direction, and narrow chip wrapping.

وسوم الحساب
finance
renewal
executive-review
customer-success
Show sourceexamples/tag-input/theme-and-wrapping.tsx
examples/tag-input/theme-and-wrapping.tsx
"use client";

import { DethinkProvider, TagInput } from "@dethink/components";

export function TagInputThemeAndWrapping() {
  return (
    <DethinkProvider theme="dark" density="compact" dir="rtl">
      <div className="border-border bg-background mx-auto max-w-72 rounded-lg border p-4">
        <TagInput
          defaultValue={[
            "finance",
            "renewal",
            "executive-review",
            "customer-success",
          ]}
          label="وسوم الحساب"
          name="accountTags"
        />
      </div>
    </DethinkProvider>
  );
}

Examples that combine components for common tasks.

Case label editor

A compact form with a capped label list and a second TagInput validating email recipients.

Case labels
finance
renewal
priority

Paste comma-separated values to add several labels at once.

Notification recipients
ops@example.com
Show sourceexamples/tag-input/recipe-label-editor.tsx
examples/tag-input/recipe-label-editor.tsx
"use client";

import { Form, Stack, TagInput } from "@dethink/components";

export function TagInputRecipeLabelEditor() {
  return (
    <Form action="/cases" method="post" className="mx-auto max-w-md">
      <div className="border-border bg-muted/20 rounded-lg border p-4">
        <Stack gap="4">
          <TagInput
            defaultValue={["finance", "renewal", "priority"]}
            description="Paste comma-separated values to add several labels at once."
            label="Case labels"
            name="caseLabels"
            maxTags={8}
            maxTagLength={24}
          />
          <TagInput
            defaultValue={["ops@example.com"]}
            label="Notification recipients"
            name="recipients"
            validateTag={(value) =>
              value.includes("@") ? null : "Use an email address."
            }
          />
        </Stack>
      </div>
    </Form>
  );
}

TagInput owns its label, chip list, entry input, validation, and repeated form fields.

TagInput props
PropWhat it doesDefault
value / defaultValue / onValueChangestring[] / string[] / (value) => voidControlled or uncontrolled tags.[]
inputValue / defaultInputValue / onInputValueChangestring / string / (text) => voidControlled or uncontrolled text for the entry field.""
delimitersstring[]Characters that commit the current text as a tag.[,]
normalizeTag(value) => stringNormalizes typed and initial tag values before validation.trim + collapse spaces
validateTag(value, existing) => ReactNode | nullReturns a message to block a tag, or null to accept it.Not set
maxTags / maxTagLengthnumberLimits total tag count and individual tag length.Not set
label / description / errorMessageReactNodeThe field label, help text, and error message.Not set
placeholder / removeLabelstringEntry placeholder and remove-button action text. React Aria appends the tag label."Add tag" / "Remove"
controlSize"sm" | "md" | "lg"Control height and typography scale."md"
required / disabled / readOnly / invalidbooleanMakes the field required, disabled, read-only, or invalid.false
namestringRepeated hidden input name for native form submission.Not set