GodUIGodUI
129Follow on X

Anatomy of Aurora Text

How a looping linear-gradient becomes drifting rainbow text via background-clip, and why the animation pauses the moment it leaves the viewport.

01/04Fill, clip, together

gradient fill

clip only

together

Gradient
135° linear-gradient, looped stops
Clip
bg-clip-text + text-transparent
Together
gradient visible only through glyphs
tsx
const stops = [...colors, colors[0]].join(", ");<span className="sr-only">{children}</span><spanaria-hidden="true"className="animate-aurora-text bg-[length:200%_auto] bg-clip-text text-transparent motion-reduce:animate-none"style={{  backgroundImage: `linear-gradient(135deg, ${stops})`,  "--aurora-text-speed": `${10 / speed}s`,}}>{children}</span>
01

Fill, clip, together

AuroraText is a span with a 200%-wide rainbow painted behind the letters, clipped so the gradient shows only through the glyphs. One CSS keyframe slides background-position back and forth. It uses no canvas, SVG filter, or font trick.

The component renders two nested spans inside a relative wrapper. The first is sr-only and holds the real children for screen readers. The second is aria-hidden and carries the aurora: a linear-gradient(135deg, …) with every stop listed, then the first color repeated so the loop has no visible seam.

bg-clip-text plus text-transparent is the whole clip. The gradient fills a box twice as wide as the text; only the letter shapes stay visible. Default stops are the system rainbow (#ff2d55 through #bf5af2), but any colors array works the same way.

02/04The background-position sweep

bg-[length:200%_auto] · animate-aurora-text

Gradient
200% wide, stops looped to first
Position
only property that moves
tsx
@keyframes aurora-text {0%   { background-position: 0% 50%; }50%  { background-position: 100% 50%; }100% { background-position: 0% 50%; }}--animate-aurora-text: aurora-text var(--aurora-text-speed, 10s) infinite linear;
02

The background-position sweep

The motion is one keyframe. aurora-text walks background-position from 0% 50% to 100% 50% and back, linear infinite, timed by --aurora-text-speed (defaults to 10s; speed sets it to 10 / speed).

Nothing else moves: no transform, no opacity pulse, and no layout work. Only the paint position of an existing gradient changes. Because the stops loop back to the first color, the turnaround at either end reads as continuous drift rather than a hard bounce.

03/04Cheap when idle
in view
128px past edge

animationPlayState flips to “paused” outside rootMargin 128px and resumes where it left off on scroll-in

Playing
intersecting: animationPlayState empty
Paused
off-screen: animationPlayState paused
tsx
const io = new IntersectionObserver(([entry]) => {  el.style.animationPlayState = entry.isIntersecting ? "" : "paused";},{ rootMargin: "128px" },);io.observe(el);
03

Cheap when idle

A forever-running background-position animation is main-thread paint work. AuroraText pauses it off-screen. An IntersectionObserver with rootMargin: "128px" watches the gradient span and flips animationPlayState as soon as it clears that margin: paused outside, running again on the way back in, with the timeline intact.

Clearing animationPlayState (empty string) restores the stylesheet default without restarting the keyframe, so scroll-in resumes wherever the aurora left off. The 128px pad means the sweep is already moving before the text fully enters the viewport.

04/04Result

Ship beautiful UI

04

The result

A clipped gradient, one background-position keyframe, an observer that pauses the animation off-screen, and an sr-only twin that keeps the words readable. That is the whole aurora.

Accessibility

Two spans, two jobs. Screen readers hit the sr-only copy and never see the decorative twin (aria-hidden). Sighted users who prefer reduced motion get motion-reduce:animate-none on the gradient span: the rainbow still paints but doesn't move. There is no slower fallback and no partial sweep.

tsx
 
className="animate-aurora-text … motion-reduce:animate-none"

The observer still mounts under reduced motion; pausing a no-op animation is harmless, and removing the effect would just add a branch for no gain.

Motion Score

Aurora TextCC: Paint-triggering
Cbackground-positionGradient text continuous keyframe loop
Each property is graded by how the browser runs it, from S (composited off the main thread) down to F (layout thrashing); the component takes the worst. MotionScore methodology →