Sparkles Text

Animated sparkle effects around text with configurable colors and density

animationtextsparkle
Install dependencies
$npm install framer-motion
Preview

Source Code

"use client";

import { useEffect, useState } from "react";
import { motion, AnimatePresence } from "framer-motion";

interface Sparkle {
  id: string;
  x: string;
  y: string;
  color: string;
  delay: number;
  size: number;
  rotation: number;
}

interface SparklesTextProps {
  children: React.ReactNode;
  colors?: string[];
  sparkleCount?: number;
  className?: string;
}

function generateSparkle(colors: string[]): Sparkle {
  return {
    id: Math.random().toString(36).slice(2),
    x: `${Math.random() * 120 - 10}%`,
    y: `${Math.random() * 120 - 10}%`,
    color: colors[Math.floor(Math.random() * colors.length)],
    delay: Math.random() * 0.3,
    size: Math.random() * 12 + 6,
    rotation: Math.random() * 360,
  };
}

export default function SparklesText({
  children,
  colors = ["#6366f1", "#8b5cf6", "#a78bfa", "#c4b5fd", "#e0e7ff"],
  sparkleCount = 12,
  className = "",
}: SparklesTextProps) {
  const [sparkles, setSparkles] = useState<Sparkle[]>([]);

  useEffect(() => {
    const interval = setInterval(() => {
      setSparkles((prev) => {
        const next = [...prev, generateSparkle(colors)];
        if (next.length > sparkleCount) next.shift();
        return next;
      });
    }, 200);
    return () => clearInterval(interval);
  }, [colors, sparkleCount]);

  return (
    <span className={`relative inline-block ${className}`}>
      <AnimatePresence>
        {sparkles.map((sparkle) => (
          <motion.svg
            key={sparkle.id}
            initial={{ opacity: 0, scale: 0, rotate: sparkle.rotation }}
            animate={{
              opacity: [0, 1, 1, 0],
              scale: [0, 1.2, 1, 0],
              rotate: sparkle.rotation + 180,
            }}
            exit={{ opacity: 0, scale: 0 }}
            transition={{
              duration: 1.2,
              delay: sparkle.delay,
              times: [0, 0.2, 0.7, 1],
            }}
            className="pointer-events-none absolute"
            style={{
              left: sparkle.x,
              top: sparkle.y,
              filter: `drop-shadow(0 0 ${sparkle.size / 2}px ${sparkle.color})`,
            }}
            width={sparkle.size}
            height={sparkle.size}
            viewBox="0 0 24 24"
            fill={sparkle.color}
          >
            <path d="M12 0L14.59 9.41L24 12L14.59 14.59L12 24L9.41 14.59L0 12L9.41 9.41L12 0Z" />
          </motion.svg>
        ))}
      </AnimatePresence>
      <strong className="relative z-10 bg-gradient-to-r from-indigo-400 via-purple-400 to-pink-400 bg-clip-text font-bold text-transparent">
        {children}
      </strong>
    </span>
  );
}