Command Palette

Search for a command...

Tags Input

A tags input component for adding and managing multiple text values as removable badges.

"use client";

import { Field, FieldLabel } from "@/components/ui/field";
import {
  TagsInput,
  TagsInputContainer,
  TagsInputInput,
  TagsInputItem,
  TagsInputItemDeleteTrigger,
  TagsInputItemText,
  TagsInputList,
} from "@/components/ui/tags-input";

export default function TagsInputDemo() {
  return (
    <Field>
      <FieldLabel>Tags</FieldLabel>
      <TagsInput>
        <TagsInputContainer>
          <TagsInputList>
            {(value) =>
              value.map((tag, index) => (
                <TagsInputItem key={index} index={index}>
                  <TagsInputItemText>{tag}</TagsInputItemText>
                  <TagsInputItemDeleteTrigger />
                </TagsInputItem>
              ))
            }
          </TagsInputList>
          <TagsInputInput />
        </TagsInputContainer>
      </TagsInput>
    </Field>
  );
}

Installation

npx nore-ui-cli@latest add tags-input

Usage

import {
  TagsInput,
  TagsInputClearTrigger,
  TagsInputContainer,
  TagsInputInput,
  TagsInputItem,
  TagsInputItemDeleteTrigger,
  TagsInputItemText,
  TagsInputList,
} from "./tags-input";
<TagsInput>
  <TagsInputContainer>
    <TagsInputList>
      {(value) =>
        value.map((tag, index) => (
          <TagsInputItem key={index} index={index}>
            <TagsInputItemText>{tag}</TagsInputItemText>
            <TagsInputItemDeleteTrigger />
          </TagsInputItem>
        ))
      }
    </TagsInputList>
    <TagsInputInput />
  </TagsInputContainer>
</TagsInput>

Examples

Clear Button

React
Vue
Svelte
"use client";

import { Button } from "@/components/ui/button";
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field";
import {
  TagsInput,
  TagsInputClearTrigger,
  TagsInputContainer,
  TagsInputInput,
  TagsInputItem,
  TagsInputItemDeleteTrigger,
  TagsInputItemText,
  TagsInputList,
} from "@/components/ui/tags-input";

export default function TagsInputClear() {
  return (
    <TagsInput defaultValue={["React", "Vue", "Svelte"]}>
      <FieldGroup css={{ gap: "4" }}>
        <Field>
          <FieldLabel>Tags</FieldLabel>
          <TagsInputContainer>
            <TagsInputList>
              {(value) =>
                value.map((tag, index) => (
                  <TagsInputItem key={index} index={index}>
                    <TagsInputItemText>{tag}</TagsInputItemText>
                    <TagsInputItemDeleteTrigger />
                  </TagsInputItem>
                ))
              }
            </TagsInputList>
            <TagsInputInput />
          </TagsInputContainer>
        </Field>
        <Field>
          <TagsInputClearTrigger asChild>
            <Button variant="outline">Clear Tags</Button>
          </TagsInputClearTrigger>
        </Field>
      </FieldGroup>
    </TagsInput>
  );
}

Form

Add topics to help people discover your content. You can add up to 5 tags.

/* eslint-disable react/no-children-prop */
"use client";

import { useForm } from "@tanstack/react-form";
import { toast } from "sonner";
import { styled } from "styled-system/jsx";
import * as z from "zod";
import { Button } from "@/components/ui/button";
import { Field, FieldDescription, FieldError, FieldLabel } from "@/components/ui/field";
import {
  TagsInput,
  TagsInputContainer,
  TagsInputInput,
  TagsInputItem,
  TagsInputItemDeleteTrigger,
  TagsInputItemText,
  TagsInputList,
} from "@/components/ui/tags-input";

const formSchema = z.object({
  tags: z
    .array(z.string())
    .min(1, "Please add at least one tag.")
    .max(5, "You can only add up to 5 tags."),
});

export default function TagsInputForm() {
  const form = useForm({
    defaultValues: {
      tags: [] as string[],
    },
    validators: {
      onSubmit: formSchema,
    },
    onSubmit: async ({ value }) => {
      toast("You submitted the following values:", {
        description: (
          <styled.pre
            css={{
              mt: "2",
              w: "320px",
              overflowX: "auto",
              rounded: "md",
              bg: "slate.950",
              p: "4",
              borderWidth: "1px",
            }}
          >
            <styled.code css={{ color: "white" }}>{JSON.stringify(value, null, 2)}</styled.code>
          </styled.pre>
        ),
      });
    },
  });

  return (
    <styled.form
      onSubmit={(e) => {
        e.preventDefault();
        form.handleSubmit();
      }}
      css={{ display: "grid", gap: "6" }}
    >
      <form.Field
        name="tags"
        children={(field) => {
          const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid;
          return (
            <Field data-invalid={isInvalid} css={{ display: "flex", flexDir: "column" }}>
              <FieldLabel>Topics</FieldLabel>
              <TagsInput
                value={field.state.value}
                onValueChange={(value) => field.handleChange(value)}
              >
                <TagsInputContainer>
                  <TagsInputList>
                    {(value) =>
                      value.map((tag, index) => (
                        <TagsInputItem key={index} index={index}>
                          <TagsInputItemText>{tag}</TagsInputItemText>
                          <TagsInputItemDeleteTrigger />
                        </TagsInputItem>
                      ))
                    }
                  </TagsInputList>
                  <TagsInputInput placeholder="Add a topic..." />
                </TagsInputContainer>
              </TagsInput>
              <FieldDescription>
                Add topics to help people discover your content. You can add up to 5 tags.
              </FieldDescription>
              {isInvalid && <FieldError errors={field.state.meta.errors} />}
            </Field>
          );
        }}
      />
      <Button type="submit">Submit</Button>
    </styled.form>
  );
}

API Reference

TagsInput

The main component that wraps all tags input parts and manages the state.

PropTypeDefault
valuestring[]
defaultValuestring[][]
onValueChange(value: string[]) => void
disabledbooleanfalse
cssSystemStyleObject
<TagsInput defaultValue={["React", "TypeScript"]} onValueChange={(value) => console.log(value)}>
  {/* children */}
</TagsInput>

TagsInputContainer

The container that holds the tags list and input field. Clicking on it focuses the input.

PropTypeDefault
cssSystemStyleObject

All other props are passed through to the underlying <div> element.

<TagsInputContainer>
  <TagsInputList>{/* ... */}</TagsInputList>
  <TagsInputInput />
</TagsInputContainer>

TagsInputList

Renders the list of tags. Uses a render prop pattern to map over the current values.

PropTypeDefault
children(value: string[]) => React.ReactNode
cssSystemStyleObject
<TagsInputList>
  {(value) =>
    value.map((tag, index) => (
      <TagsInputItem key={index} index={index}>
        <TagsInputItemText>{tag}</TagsInputItemText>
        <TagsInputItemDeleteTrigger />
      </TagsInputItem>
    ))
  }
</TagsInputList>

TagsInputItem

Represents an individual tag in the list.

PropTypeDefault
indexnumber
disabledboolean
cssSystemStyleObject

All other props are passed through to the underlying <div> element.

<TagsInputItem index={0}>
  <TagsInputItemText>React</TagsInputItemText>
  <TagsInputItemDeleteTrigger />
</TagsInputItem>

TagsInputItemText

Displays the text content of a tag.

PropTypeDefault
cssSystemStyleObject

All other props are passed through to the underlying <span> element.

<TagsInputItemText>{tag}</TagsInputItemText>

TagsInputItemDeleteTrigger

A button to remove a tag from the list. Displays an X icon by default.

PropTypeDefault
asChildbooleanfalse
disabledboolean
cssSystemStyleObject

All other props are passed through to the underlying <button> element.

<TagsInputItemDeleteTrigger />

With custom content:

<TagsInputItemDeleteTrigger>
  <CustomIcon />
</TagsInputItemDeleteTrigger>

TagsInputInput

The input field for adding new tags. Press Enter to add a tag, Backspace to remove the last tag when empty.

PropTypeDefault
placeholderstring"Add tags..."
disabledboolean
cssSystemStyleObject

All other props are passed through to the underlying <input> element.

<TagsInputInput placeholder="Enter tag..." />

TagsInputClearTrigger

A button to clear all tags at once.

PropTypeDefault
asChildbooleanfalse
cssSystemStyleObject

All other props are passed through to the underlying <button> element.

<TagsInputClearTrigger>Clear all</TagsInputClearTrigger>