shadcn.io is not affiliated with official shadcn/ui
Shadcn Tags Input for React and Tailwind
Allows users to add tags to an input field.
React
Solid
Vue
Svelte
Installation
bunx --bun shadcn@latest add https://kit.dev/r/tags-input.jsonpnpm dlx shadcn@latest add https://kit.dev/r/tags-input.jsonnpx shadcn@latest add https://kit.dev/r/tags-input.jsonyarn shadcn@latest add https://kit.dev/r/tags-input.jsonThis component depends on Input Group. Install it first if you haven't already.
Install the following dependencies:
bun add @ark-ui/react lucide-reactpnpm add @ark-ui/react lucide-reactnpm install @ark-ui/react lucide-reactyarn add @ark-ui/react lucide-reactCopy and paste the following code into your project.
"use client";
import {
TagsInput as ArkTagsInput,
useTagsInput as useArkTagsInput,
useTagsInputContext as useArkTagsInputContext,
} from "@ark-ui/react/tags-input";
import { XIcon } from "lucide-react";
import type React from "react";
import { cn } from "@/lib/utils";
import {
InputGroup,
InputGroupButton,
InputGroupInput,
type InputGroupProps,
} from "@/components/ui/input-group";
export const useTagsInput = useArkTagsInput;
export const useTagsInputContext = useArkTagsInputContext;
export const TagsInputContext = ArkTagsInput.Context;
interface TagsInputProps
extends React.ComponentProps<typeof ArkTagsInput.Root>,
Pick<InputGroupProps, "size"> {
/**
* Whether to show the clear button.
*
* @default true
*/
showClear?: boolean;
}
export const TagsInput = (props: TagsInputProps) => {
const {
size = "md",
showClear,
editable = false,
tabIndex,
className,
children,
...rest
} = props;
return (
<ArkTagsInput.Root
className={cn(
"group/tags-input",
"flex w-full flex-col gap-2",
className
)}
data-size={size}
data-slot="tags-input"
editable={editable}
{...rest}
>
<TagsInputControl showClear={showClear}>
{children}
<TagsInputInput placeholder="Add framework" />
</TagsInputControl>
<ArkTagsInput.HiddenInput tabIndex={tabIndex} />
</ArkTagsInput.Root>
);
};
interface TagsInputControlProps
extends React.ComponentProps<typeof ArkTagsInput.Control>,
Pick<InputGroupProps, "size"> {
/**
* Whether to show the clear button.
*
* @default true
*/
showClear?: boolean;
}
export const TagsInputControl = (props: TagsInputControlProps) => {
const { size, showClear = true, className, children, ...rest } = props;
const api = useTagsInputContext();
return (
<ArkTagsInput.Control asChild data-slot="tags-input-control">
<InputGroup
className={cn(
"h-auto in-data-[size=lg]:min-h-9 in-data-[size=sm]:min-h-7 min-h-8",
"p-1",
"flex-wrap content-start items-center gap-1",
"data-disabled:pointer-events-none data-disabled:opacity-64",
className
)}
size={size}
{...rest}
>
{children}
{showClear && api.value.length > 0 && (
<TagsInputClearTrigger aria-label="Clear all tags" />
)}
</InputGroup>
</ArkTagsInput.Control>
);
};
interface TagsInputItemProps
extends React.ComponentProps<typeof ArkTagsInput.Item>,
Pick<InputGroupProps, "size"> {
/**
* Whether to show the clear trigger.
*
* @default true
*/
showDelete?: boolean;
}
export const TagsInputItem = (props: TagsInputItemProps) => {
const { showDelete = true, className, children, ...rest } = props;
return (
<ArkTagsInput.Item
className={cn(
"h-6 in-data-[size=lg]:h-7 in-data-[size=sm]:h-5 max-w-full",
"pr-0.5 in-data-[size=lg]:pl-2 in-data-[size=sm]:pl-1 pl-1.5",
"inline-flex shrink-0 items-center gap-1",
"bg-secondary",
"in-data-[size=lg]:text-sm text-secondary-foreground text-xs",
"rounded-md border outline-none",
"data-highlighted:border-primary/30 data-highlighted:bg-primary/10",
className
)}
data-slot="tags-input-item"
{...rest}
>
<TagsInputItemPreview>
<TagsInputItemText>{children}</TagsInputItemText>
{showDelete && <TagsInputItemDeleteTrigger />}
</TagsInputItemPreview>
<TagsInputItemInput />
</ArkTagsInput.Item>
);
};
export const TagsInputItemPreview = (
props: React.ComponentProps<typeof ArkTagsInput.ItemPreview>
) => {
const { className, ...rest } = props;
return (
<ArkTagsInput.ItemPreview
className={cn("inline-flex max-w-full items-center gap-1", className)}
data-slot="tags-input-item-preview"
{...rest}
/>
);
};
export const TagsInputItemText = (
props: React.ComponentProps<typeof ArkTagsInput.ItemText>
) => {
const { className, ...rest } = props;
return (
<ArkTagsInput.ItemText
className={cn("truncate", className)}
data-slot="tags-input-item-text"
{...rest}
/>
);
};
export const TagsInputItemDeleteTrigger = (
props: React.ComponentProps<typeof ArkTagsInput.ItemDeleteTrigger>
) => {
const { className, children, ...rest } = props;
return (
<ArkTagsInput.ItemDeleteTrigger
asChild
data-slot="tags-input-item-delete-trigger"
{...rest}
>
<InputGroupButton
className={cn(
"in-data-[size=lg]:size-6 in-data-[size=sm]:size-4 size-5",
"shrink-0",
"text-muted-foreground",
"rounded-[calc(var(--radius)-5px)]",
"[&_svg:not([class*='size-'])]:size-3",
"hover:text-foreground",
className
)}
size="icon-xs"
variant="ghost"
>
{children ?? <XIcon aria-hidden />}
</InputGroupButton>
</ArkTagsInput.ItemDeleteTrigger>
);
};
export const TagsInputItemInput = (
props: React.ComponentProps<typeof ArkTagsInput.ItemInput>
) => (
<ArkTagsInput.ItemInput asChild data-slot="tags-input-item-input" {...props}>
<InputGroupInput
className={cn(
"px-1 text-xs",
"h-6 in-data-[size=lg]:h-7 in-data-[size=sm]:h-5"
)}
/>
</ArkTagsInput.ItemInput>
);
export const TagsInputInput = (
props: React.ComponentProps<typeof ArkTagsInput.Input>
) => (
<ArkTagsInput.Input asChild data-slot="tags-input-input" {...props}>
<InputGroupInput
className={cn(
"w-auto min-w-18 max-w-full flex-auto shrink basis-auto",
"h-7 in-data-[size=lg]:h-8 in-data-[size=sm]:h-6"
)}
/>
</ArkTagsInput.Input>
);
export const TagsInputClearTrigger = (
props: React.ComponentProps<typeof ArkTagsInput.ClearTrigger>
) => {
const { className, children, ...rest } = props;
return (
<ArkTagsInput.ClearTrigger
asChild
data-slot="tags-input-clear-trigger"
{...rest}
>
<InputGroupButton
className={cn(
"ms-auto shrink-0 self-center text-muted-foreground hover:text-foreground",
className
)}
size="icon-xs"
variant="ghost"
>
{children ?? <XIcon aria-hidden />}
</InputGroupButton>
</ArkTagsInput.ClearTrigger>
);
};
interface TagsInputRootProviderProps
extends React.ComponentProps<typeof ArkTagsInput.RootProvider>,
Pick<InputGroupProps, "size"> {
/**
* Whether to show the clear button.
*
* @default true
*/
showClear?: boolean;
}
export const TagsInputRootProvider = (props: TagsInputRootProviderProps) => {
const { size = "md", showClear, className, children, ...rest } = props;
return (
<ArkTagsInput.RootProvider
className={cn(
"group/tags-input",
"flex w-full flex-col gap-2",
className
)}
data-size={size}
data-slot="tags-input-root-provider"
{...rest}
>
<TagsInputControl showClear={showClear}>{children}</TagsInputControl>
<ArkTagsInput.HiddenInput />
</ArkTagsInput.RootProvider>
);
};Update the import paths to match your project setup.
Anatomy
TagsInputContext
├── TagsInputLabel
├── TagsInput
├── TagsInputInput
├── TagsInputClearTrigger
└── TagsInputItem
├── TagsInputItemPreview
├── TagsInputItemInput
├── TagsInputItemText
└── TagsInputItemDeleteTriggerUsage
import { Field, FieldLabel } from "@/components/ui/field";
import {
TagsInput,
TagsInputContext,
TagsInputItem,
} from "@/components/ui/tags-input";const defaultValue = ["React", "Solid", "Vue", "Svelte"];
const TagsInputDemo = () => (
<Field className="w-full max-w-sm">
<FieldLabel>Frameworks</FieldLabel>
<TagsInput className="w-full" defaultValue={defaultValue}>
<TagsInputContext>
{({ value }) =>
value.map((value, index) => (
<TagsInputItem index={index} key={value} value={value}>
{value}
</TagsInputItem>
))
}
</TagsInputContext>
</TagsInput>
</Field>
);Accessibility
| Key | Description |
|---|---|
ArrowDown | Move down. |
ArrowLeft | Move left. |
ArrowRight | Move right. |
Escape | Dismiss. |
Backspace | Delete backward. |
Delete | Delete. |
Enter | Activate the focused item. |
Space | Activate the focused control. |
Demos
Blur Behavior
Combobox
Controlled
Controlled Input Value
Custom Delimiter
Disable Editing
Disabled
Field
Invalid
Max Length
Max Tags
Max With Overflow
Paste Behavior
Sanitize Value
Size Lg
Size Md
Size Sm
Validation
API Reference
TagsInputContext
div
PropType
TagsInput
div
PropType
AttributeType
TagsInputInput
input
PropType
AttributeType
TagsInputClearTrigger
button
PropType
AttributeType
TagsInputItem
div
PropType
AttributeType
TagsInputItemPreview
div
PropType
AttributeType
TagsInputItemInput
input
PropType
AttributeType
TagsInputItemText
span
PropType
AttributeType
TagsInputItemDeleteTrigger
button
PropType
AttributeType
CSS variableType
TagsInputControl
div
PropType
AttributeType
TagsInputRootProvider
div
PropType
AttributeType
useTagsInput
PropType
TagsInputContext
PropType