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-inputUsage
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
"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
/* 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.
| Prop | Type | Default |
|---|---|---|
value | string[] | |
defaultValue | string[] | [] |
onValueChange | (value: string[]) => void | |
disabled | boolean | false |
css | SystemStyleObject |
<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.
| Prop | Type | Default |
|---|---|---|
css | SystemStyleObject |
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.
| Prop | Type | Default |
|---|---|---|
children | (value: string[]) => React.ReactNode | |
css | SystemStyleObject |
<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.
| Prop | Type | Default |
|---|---|---|
index | number | |
disabled | boolean | |
css | SystemStyleObject |
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.
| Prop | Type | Default |
|---|---|---|
css | SystemStyleObject |
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.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
disabled | boolean | |
css | SystemStyleObject |
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.
| Prop | Type | Default |
|---|---|---|
placeholder | string | "Add tags..." |
disabled | boolean | |
css | SystemStyleObject |
All other props are passed through to the underlying <input> element.
<TagsInputInput placeholder="Enter tag..." />
TagsInputClearTrigger
A button to clear all tags at once.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
css | SystemStyleObject |
All other props are passed through to the underlying <button> element.
<TagsInputClearTrigger>Clear all</TagsInputClearTrigger>