Shimmering Text

Animated gradient shimmer text with viewport-triggered and repeating animation support.

Made by Léo
Open in

Text Shimmer Effect

Animated gradient text with automatic cycling

Agent is thinking...
demo-shimmering-text.tsx
"use client";

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

import { ShimmeringText } from "@/components/unlumen-ui/shimmering-text";

const phrases = [
  "Agent is thinking...",
  "Processing your request...",
  "Analyzing the data...",
  "Generating response...",
  "Almost there...",
];

export function TextShimmerDemo() {
  const [currentIndex, setCurrentIndex] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setCurrentIndex((prev) => (prev + 1) % phrases.length);
    }, 3000);

    return () => clearInterval(interval);
  }, []);

  return (
    <div className="bg-card w-full rounded-lg border p-6">
      <div className="mb-4">
        <h3 className="text-lg font-semibold">Text Shimmer Effect</h3>
        <p className="text-muted-foreground text-sm">
          Animated gradient text with automatic cycling
        </p>
      </div>

      <div className="space-y-4">
        <div className="bg-muted/10 flex items-center justify-center rounded-lg py-8">
          <AnimatePresence mode="wait">
            <motion.div
              key={currentIndex}
              initial={{ opacity: 0, y: 10 }}
              animate={{ opacity: 1, y: 0 }}
              exit={{ opacity: 0, y: -10 }}
              transition={{ duration: 0.3 }}
            >
              <ShimmeringText text={phrases[currentIndex]} />
            </motion.div>
          </AnimatePresence>
        </div>
      </div>
    </div>
  );
}

Installation

Install the following dependencies:

Copy and paste the following code into your project:

components/unlumen-ui/shimmering-text.tsx
"use client";

import React, { useMemo, useRef } from "react";
import { motion, useInView, UseInViewOptions } from "motion/react";

import { cn } from "@/lib/cn";

interface ShimmeringTextProps {
  text: string;
  duration?: number;
  delay?: number;
  repeat?: boolean;
  repeatDelay?: number;
  className?: string;
  startOnView?: boolean;
  once?: boolean;
  /** rootMargin for in-view detection */
  inViewMargin?: UseInViewOptions["margin"];
  /** spread multiplier based on text length */
  spread?: number;
  color?: string;
  shimmerColor?: string;
}

export function ShimmeringText({
  text,
  duration = 2,
  delay = 0,
  repeat = true,
  repeatDelay = 0.5,
  className,
  startOnView = true,
  once = false,
  inViewMargin,
  spread = 2,
  color,
  shimmerColor,
}: ShimmeringTextProps) {
  const ref = useRef<HTMLSpanElement>(null);
  const isInView = useInView(ref, { once, margin: inViewMargin });

  const dynamicSpread = useMemo(() => {
    return text.length * spread;
  }, [text, spread]);

  const shouldAnimate = !startOnView || isInView;

  return (
    <motion.span
      ref={ref}
      className={cn(
        "relative inline-block bg-[length:250%_100%,auto] bg-clip-text text-transparent",
        "[--base-color:var(--muted-foreground)] [--shimmer-color:var(--foreground)]",
        "[background-repeat:no-repeat,padding-box]",
        "[--shimmer-bg:linear-gradient(90deg,transparent_calc(50%-var(--spread)),var(--shimmer-color),transparent_calc(50%+var(--spread)))]",
        "dark:[--base-color:var(--muted-foreground)] dark:[--shimmer-color:var(--foreground)]",
        className,
      )}
      style={
        {
          "--spread": `${dynamicSpread}px`,
          ...(color && { "--base-color": color }),
          ...(shimmerColor && { "--shimmer-color": shimmerColor }),
          backgroundImage: `var(--shimmer-bg), linear-gradient(var(--base-color), var(--base-color))`,
        } as React.CSSProperties
      }
      initial={{
        backgroundPosition: "100% center",
        opacity: 0,
      }}
      animate={
        shouldAnimate
          ? {
              backgroundPosition: "0% center",
              opacity: 1,
            }
          : {}
      }
      transition={{
        backgroundPosition: {
          repeat: repeat ? Infinity : 0,
          duration,
          delay,
          repeatDelay,
          ease: "linear",
        },
        opacity: {
          duration: 0.3,
          delay,
        },
      }}
    >
      {text}
    </motion.span>
  );
}

Update the import paths to match your project setup.

Usage

import { ShimmeringText } from "@/components/unlumen-ui/shimmering-text";
<ShimmeringText text="Agent is thinking..." />
<ShimmeringText text="Processing..." duration={1.5} repeat={false} />

Props

PropTypeDefaultDescription
textstringText to display with the shimmer effect.
durationnumber2Shimmer sweep duration in seconds.
delaynumber0Delay before the animation starts.
repeatbooleantrueWhether the shimmer repeats infinitely.
repeatDelaynumber0.5Pause between repeats in seconds.
startOnViewbooleantrueStart animation when the element enters the viewport.
oncebooleanfalseOnly animate once when entering the viewport.
inViewMarginstringRoot margin for the in-view observer.
spreadnumber2Shimmer spread multiplier (scales with text length).
colorstringOverride the base text color CSS value.
shimmerColorstringOverride the shimmer highlight color CSS value.
classNamestringAdditional CSS classes.

Built by Léo from Unlumen :3

Last updated: 3/15/2026