Browse the docs
TextNo. 71 of 109
Shimmer Text
A band of light sweeping across text now and then, paused off screen.
Installation
pnpm dlx shadcn@latest add @corsair-ui/shimmer-textNo setup needed: the CLI finds @corsair-ui in the shadcn registry directory. The files land in your project as source: edit them like your own code. More in Installation.
- Also adds
- utilsuse-in-view
- npm
clsxtailwind-merge
Examples
Status line
The light is the page colour, so it reads in both themes.
Charting a course…
Heading
Gold on the horizon
API
| Prop | Type | Default |
|---|---|---|
duration / pause / delayIn seconds. | number | 1.8 / 2 / 0.8 |
highlightColour of the light; defaults to the page colour mixed into the text. | string | — |
Accessibility
- The text keeps its colour; only a band of light passes over it.
- It pauses off screen. With prefers-reduced-motion it is plain text.
Compatibility
- Tailwind CSS
- 3.4 and 4, both checked in CI
- React
- 19
- Rendering
- Client component ("use client")
Source
1 file, installed as source you own
shimmer-text.tsxView on GitHub"use client";
import { useMemo, type ComponentProps, type CSSProperties, type Ref } from "react";
import { useInView } from "@/registry/default/hooks/use-in-view";
import { cn } from "@/registry/default/lib/utils";
function mergeRefs<T>(...refs: (Ref<T> | undefined)[]) {
return (node: T | null) => {
for (const ref of refs) {
if (typeof ref === "function") ref(node);
else if (ref) ref.current = node;
}
};
}
// The band is 40% of the text wide, so a background position of -70% hides
// it past the left edge and 170% past the right.
const BAND = "40% 100%";
const SWEEP_FROM = -70;
const SWEEP_LENGTH = 240;
interface ShimmerTextProps extends ComponentProps<"span"> {
/** Seconds the light takes to cross the text. */
duration?: number;
/** Seconds of rest between passes. */
pause?: number;
/** Seconds before the first pass. */
delay?: number;
/** Colour of the light. Defaults to the page colour mixed into the text colour. */
highlight?: string;
}
/**
* A band of light that sweeps across text now and then, like sun on a
* brass plate. The text keeps its own colour (set it with a `text-*` class);
* the light is the page colour by default, so it reads in light and dark
* themes alike. It is one CSS animation of the background, paused while off
* screen, and plain text with `prefers-reduced-motion`.
*
* @example
* <ShimmerText className="text-muted-foreground">Charting a course…</ShimmerText>
*/
function ShimmerText({
duration = 1.8,
pause = 2,
delay = 0.8,
highlight,
className,
style,
ref,
children,
...props
}: ShimmerTextProps) {
const [observe, inView] = useInView<HTMLSpanElement>({ rootMargin: "100px" });
const mergedRef = useMemo(() => mergeRefs(ref, observe), [ref, observe]);
// The rest between passes is the band travelling on past the end of the
// text, so one animation covers both and the timing stays in CSS.
const cycle = Math.max(duration, 0.01) + Math.max(pause, 0);
const end = SWEEP_FROM + SWEEP_LENGTH * (cycle / Math.max(duration, 0.01));
return (
<span
ref={mergedRef}
data-slot="shimmer-text"
className={cn(
"motion-safe:animate-shimmer-text inline-block bg-clip-text bg-no-repeat [-webkit-text-fill-color:transparent]",
"motion-reduce:[-webkit-text-fill-color:currentColor]",
className
)}
style={
{
"--shimmer-text-light":
highlight ?? "color-mix(in oklab, currentColor 25%, var(--background))",
"--shimmer-text-from": `${SWEEP_FROM}%`,
"--shimmer-text-to": `${end}%`,
backgroundColor: "currentColor",
// A soft, slanted peak of light over the text colour.
backgroundImage:
"linear-gradient(105deg, transparent 20%, var(--shimmer-text-light) 50%, transparent 80%)",
backgroundSize: BAND,
backgroundPositionX: `${SWEEP_FROM}%`,
animationDuration: `${cycle}s`,
animationDelay: `${delay}s`,
animationPlayState: inView ? undefined : "paused",
...style,
} as CSSProperties
}
{...props}
>
{children}
</span>
);
}
export { ShimmerText, type ShimmerTextProps };