Shadcn Tag Input is a tag input component implementation of Shadcn’s input component. It’s customizable, but styled by default (Shadcn’s default styling).
Setup
Run the shadcn-ui init command to setup your project:
npx shadcn-ui@latest init
Run the shadcn-ui add command to add the tag input component to your project:
npx shadcn-ui@latest add input
Copy and paste the folowing code into a new file:
import React from 'react'; import { Input } from './ui/input'; import { Button } from './ui/button'; import { X } from 'lucide-react'; import { cn } from '@/lib/utils'; interface TagInputProps extends React.InputHTMLAttributes<HTMLInputElement> { placeholder?: string; tags: string[]; setTags: React.Dispatch<React.SetStateAction<string[]>>; } const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>((props, ref) => { const { placeholder, tags, setTags, className } = props; const [inputValue, setInputValue] = React.useState(''); const inputRef = React.useRef<HTMLInputElement>(null); const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { setInputValue(e.target.value); }; const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { if (e.key === 'Enter' || e.key === ',') { e.preventDefault(); const newTag = inputValue.trim(); if (newTag && !tags.includes(newTag)) { setTags([...tags, newTag]); } setInputValue(''); } }; const removeTag = (tagToRemove: string) => { setTags(tags.filter((tag) => tag !== tagToRemove)); }; return ( <div> <div className={`flex flex-wrap gap-2 rounded-md ${tags.length !== 0 && 'mb-3'}`}> {tags.map((tag, index) => ( <span key={index} className="transition-all border bg-secondary text-secondary-foreground hover:bg-secondary/80 inline-flex h-8 items-center text-sm pl-2 rounded-md"> {tag} <Button type="button" variant="ghost" onClick={() => removeTag(tag)} className={cn("py-1 px-3 h-full hover:bg-transparent")} > <X size={14} /> </Button> </span> ))} </div> <Input ref={inputRef} type="text" placeholder={placeholder} value={inputValue} onChange={handleInputChange} onKeyDown={handleKeyDown} className={className} /> </div> ); }); TagInput.displayName = 'TagInput'; export { TagInput };
Usage
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form" import React from 'react' import { TagInput } from '@/components/tag-input' const FormSchema = z.object({ topics: z.array(z.string()), }) const [tags, setTags] = React.useState<string[]>([]); const { setValue } = form; function onSubmit(data: z.infer<typeof FormSchema>) { toast({ title: "You submitted the following values:", description: ( <pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4"> <code className="text-white">{JSON.stringify(data, null, 2)}</code> </pre> ), }) } <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8"> <FormField control={form.control} name="topics" render={({ field }) => ( <FormItem> <FormLabel>Topics</FormLabel> <FormControl> <TagInput {...field} placeholder="Enter a topic" tags={tags} className='sm:min-w-[450px]' setTags={(newTags) => { setTags(newTags); setValue("topics", newTags as [string, ...string[]]); }} /> </FormControl> <FormDescription> These are the topics that you're interested in. </FormDescription> <FormMessage /> </FormItem> )} /> <Button type="submit">Submit</Button> </form> </Form>