Logo SmoothUI
ComponentsAnimated Tags

Animated Tags

Component that displays tags with an animation when they are added or removed from the list of selected tags

Selected Tags

react
tailwindcss
javascript

Code

Install with shadcn Beta

Terminal

npx shadcn@latest add "https://smoothui.dev/r/animated-tags.json"

Manual install

Terminal

npm install motion lucide-react

AnimatedTags.tsx

"use client"

import { useState } from "react"
import { CircleX, Plus } from "lucide-react"
import { AnimatePresence, motion } from "motion/react"

export default function AnimatedTags() {
  const [selectedTag, setSelectedTag] = useState<string[]>([])

  const [tags, setTags] = useState<string[]>(initialTags)

  const handleTagClick = (tag: string) => {
    if (!selectedTag.includes(tag)) {
      setSelectedTag([...selectedTag, tag])
      setTags(tags.filter((t) => t !== tag))
    }
  }
  const handleDeleteTag = (tag: string) => {
    const newSelectedTag = selectedTag.filter((selected) => selected !== tag)
    setSelectedTag(newSelectedTag)
    setTags([...tags, tag])
  }
  return (
    <div className="flex w-[300px] flex-col gap-4 p-4">
      <div className="flex flex-col items-start justify-center gap-1">
        <p>Selected Tags</p>
        <AnimatePresence>
          <div className="border-light-300 dark:border-dark-300 flex min-h-12 w-full flex-wrap items-center gap-1 rounded-xl border p-2">
            {selectedTag?.map((tag) => (
              <motion.div
                key={tag}
                layout
                className="group bg-light-200 text-light-900 group-hover:bg-light-100 group-hover:text-light-950 dark:bg-dark-200 dark:text-dark-900 dark:group-hover:bg-dark-100 dark:group-hover:text-dark-950 flex cursor-pointer flex-row items-center justify-center gap-2 rounded-md px-2 py-1"
                onClick={() => handleDeleteTag(tag)}
                initial={{ y: 20, opacity: 0, filter: "blur(4px)" }}
                animate={{
                  y: 0,
                  opacity: 1,
                  filter: "blur(0px)",
                }}
                exit={{ y: 20, opacity: 0, filter: "blur(4px)" }}
                transition={{ duration: 0.3, bounce: 0, type: "spring" }}
              >
                {tag}{" "}
                <CircleX
                  size={16}
                  className="flex items-center justify-center rounded-full transition-all duration-300 ease-in-out"
                />
              </motion.div>
            ))}
          </div>
        </AnimatePresence>
      </div>
      <AnimatePresence>
        <div className="flex flex-wrap items-center gap-1">
          {tags.map((tag, index) => (
            <motion.div
              layout
              key={index}
              className="group bg-light-200 text-light-900 dark:bg-dark-200 dark:text-dark-900 flex cursor-pointer flex-row items-center justify-center gap-2 rounded-md px-2 py-1"
              onClick={() => handleTagClick(tag)}
              initial={{ y: -20, opacity: 0, filter: "blur(4px)" }}
              animate={{
                y: 0,
                opacity: 1,
                filter: "blur(0px)",
              }}
              exit={{ y: -20, opacity: 0, filter: "blur(4px)" }}
              transition={{ duration: 0.3, bounce: 0, type: "spring" }}
            >
              {tag}{" "}
              <Plus
                size={16}
                className="hover:bg-light-100 group-hover:text-light-950 dark:group-hover:bg-dark-100 dark:group-hover:text-dark-950 flex items-center justify-center rounded-full transition-all duration-300 ease-in-out"
              />
            </motion.div>
          ))}
        </div>
      </AnimatePresence>
    </div>
  )
}

const initialTags: string[] = ["react", "tailwindcss", "javascript"]