Shimmering Text
Animated text with a gradient shimmer effect powered by Motion.
Installation
$
Usage
import { ShimmeringText } from "@/components/ui/shimmering-text"Examples
Default
Shimmering Text
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
| text* | string | - | The text to render with the shimmer effect. |
| duration | number | 2 | Duration of one shimmer sweep in seconds. |
| delay | number | 0 | Delay before the animation starts, in seconds. |
| repeat | boolean | true | Whether the shimmer sweep loops indefinitely. |
| repeatDelay | number | 0.3 | Pause between repeated sweeps, in seconds. |
| startOnView | boolean | true | Starts the animation only once the text enters the viewport. Set false to animate immediately. |
| once | boolean | false | When true, in-view detection fires only the first time the text enters the viewport. |
| inViewMargin | string | - | Margin (rootMargin) for in-view detection, e.g. "-100px". |
| spread | number | 3 | Shimmer band width multiplier, scaled by text length to size the highlight. |
| color | string | - | Base (resting) text color. Defaults to a dimmed --muted-foreground mix. |
| shimmerColor | string | `var(--foreground)` | Color of the bright shimmer band that sweeps across the text. |
"use client"
import React, { useMemo, useRef } from "react"
import { motion, useInView, useReducedMotion, UseInViewOptions } from "motion/react"
import { cn } from "@/registry/lib/utils"
interface ShimmeringTextProps {
/** Text to display with shimmer effect */
text: string
/** Animation duration in seconds */
duration?: number
/** Delay before starting animation */
delay?: number
/** Whether to repeat the animation */
repeat?: boolean
/** Pause duration between repeats in seconds */
repeatDelay?: number
/** Custom className */
className?: string
/** Whether to start animation when component enters viewport */
startOnView?: boolean
/** Whether to animate only once */
once?: boolean
/** Margin for in-view detection (rootMargin) */
inViewMargin?: UseInViewOptions["margin"]
/** Shimmer spread multiplier */
spread?: number
/** Base text color */
color?: string
/** Shimmer gradient color */
shimmerColor?: string
}
export function ShimmeringText({
text,
duration = 2,
delay = 0,
repeat = true,
repeatDelay = 0.3,
className,
startOnView = true,
once = false,
inViewMargin,
spread = 3,
color,
shimmerColor,
}: ShimmeringTextProps) {
const ref = useRef<HTMLSpanElement>(null)
const isInView = useInView(ref, { once, margin: inViewMargin })
const shouldReduceMotion = useReducedMotion()
// Calculate dynamic spread based on text length
const dynamicSpread = useMemo(() => {
return text.length * spread
}, [text, spread])
// Determine if we should start animation
const shouldAnimate = !shouldReduceMotion && (!startOnView || isInView)
return (
<motion.span
ref={ref}
data-slot="shimmering-text"
className={cn(
"relative inline-block bg-[length:250%_100%,auto] bg-clip-text text-transparent",
// Dim the resting text so the bright sweep clearly stands out (the
// base/highlight delta is what reads as "shimmer"). The band has a small
// solid core for a defined glint rather than a single soft peak.
"[--base-color:color-mix(in_oklab,var(--muted-foreground)_55%,transparent)] [--shimmer-color:var(--foreground)]",
"[background-repeat:no-repeat,padding-box]",
"[--shimmer-bg:linear-gradient(90deg,transparent_calc(50%_-_var(--spread)),var(--shimmer-color)_calc(50%_-_var(--spread)_*_0.25),var(--shimmer-color)_calc(50%_+_var(--spread)_*_0.25),transparent_calc(50%_+_var(--spread)))]",
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: shouldReduceMotion ? 1 : 0,
}}
animate={
shouldReduceMotion
? { opacity: 1 }
: shouldAnimate
? {
backgroundPosition: "0% center",
opacity: 1,
}
: {}
}
transition={
shouldReduceMotion
? { duration: 0 }
: {
backgroundPosition: {
repeat: repeat ? Infinity : 0,
duration,
delay,
repeatDelay,
ease: "linear",
},
opacity: {
duration: 0.3,
delay,
},
}
}
>
{text}
</motion.span>
)
}