A character-level text animation for logos, hero words, product names, launch titles, and technical brand moments where the final word should feel assembled rather than simply faded in.

Rolling Text turns a short word or phrase into a controlled character reveal.
Each letter moves through a rolling transition before settling into its final position. The effect can feel like a slot mechanism, system readout, rotating glyph strip, or mechanical title sequence depending on timing and typography. The final state is the important part: the word resolves cleanly and stays readable.
Use Rolling Text when a brand word, product name, headline fragment, or short label deserves a sharper arrival. It works for developer tools, AI products, cyber interfaces, portfolio intros, launch pages, studio identities, and experimental hero sections where the typography should feel constructed, technical, or engineered.
The page job is identity. The visitor should remember the word, not the effort it took to read it. Rolling motion is useful when it gives the text presence. It becomes friction when the word keeps moving after the message has already landed.
npx hyperiux add rolling-textimport RollingText from "@/components/effects/rolling-text";
const page = () => {
return (
<>
<RollingText/>
</>
)
}
export default page
"use client";
import { useRef } from "react";
import gsap from "gsap";
import { useGSAP } from "@gsap/react";
gsap.registerPlugin(useGSAP);
const LINE_HEIGHT = 0.8;
const FONT_FAMILY = "Helvetica Neue, Arial Narrow, system-ui, sans-serif";
/** Deterministic PRNG so the server and client build identical reels. */
const mulberry32 = (seed) => () => {
seed = (seed + 0x6d2b79f5) | 0;
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
const buildReel = (charIndex, config) => {
const rand = mulberry32(charIndex * 1013 + 7);
const cycles = config.minCycles + Math.floor(rand() * config.cycleVariance);
return {
copies: cycles + 1,
to: cycles,
duration: config.duration + rand() * config.durationVariance,
};
};
const RollingText = ({ text = "HYPERIUX", textColor = "#ffffff", minCycles = 3, cycleVariance = 3, duration = 2.4, durationVariance = 1.2, }) => {
const containerRef = useRef(null);
useGSAP(() => {
const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
const reels = gsap.utils.toArray("[data-reel]", containerRef.current);
if (reduced) {
reels.forEach((reel) => {
reel.style.setProperty("--k", reel.dataset.to ?? "0");
});
const heading = containerRef.current?.querySelector("h3");
if (heading) {
gsap.fromTo(heading, { opacity: 0 }, { opacity: 1, duration: 0.6, ease: "power1.out" });
}
return;
}
reels.forEach((reel) => {
const to = Number(reel.dataset.to);
const scroll = { k: 0 };
reel.style.setProperty("--k", "0");
gsap.to(scroll, {
k: to,
duration: Number(reel.dataset.duration),
ease: "expo.out",
onUpdate: () => reel.style.setProperty("--k", String(scroll.k)),
});
});
}, {
scope: containerRef,
dependencies: [minCycles, cycleVariance, duration, durationVariance],
});
return (<div ref={containerRef} className="relative grid place-items-center min-h-dvh bg-[#2a0e5f] bg-cover bg-center"
<div className="absolute inset-0 bg-black/40" aria-hidden/>
{/*
globals.css has an UNLAYERED `h1,h2,h3,h4 { font-weight: 400;
line-height: 1.2 }` rule. Unlayered CSS always beats anything in
Tailwind's `@layer utilities`, so font-weight/leading need `!` here
to actually apply — every other class on this h3 is safe without it.
font-family is set inline instead, which wins regardless of layers.
*/}
<h3 aria-label={text} style={{ color: textColor, fontFamily: FONT_FAMILY }} className="relative z-10 m-0 text-9xl max-[1025px]:text-6xl max-md:text-5xl font-light! leading-[0.8]! tracking-[0.02em] whitespace-nowrap select-none">
{text.split("").map((char, charIndex) => {
if (char === " ") {
return (<span key={charIndex} className="inline-block" aria-hidden>
</span>);
}
const reel = buildReel(charIndex, { minCycles, cycleVariance, duration, durationVariance });
return (<span key={charIndex} className="relative inline-block align-top" aria-hidden>
{/* Invisible copy of the letter: it alone sets the cell box, so
the landed word matches plain text exactly. */}
<span className="block invisible">{char}</span>
<span className="absolute inset-0 overflow-hidden">
<span data-reel="" data-to={reel.to} data-duration={reel.duration} className="block will-change-transform [transform:translate3d(0,calc(-1em*0.8*var(--k,0)),0)]">
{Array.from({ length: reel.copies }, (_, copy) => (<span key={copy} className="block w-full h-[0.8em] text-center">
{char}
</span>))}
</span>
</span>
</span>);
})}
</h3>
</div>);
};
export default RollingText;
A developer-tool homepage can use Rolling Text on the product name or primary hero word. The characters roll into place, resolve quickly, and give the brand name a technical, deliberate entrance before the supporting copy and CTA appear.
The outcome is memorability. The word feels constructed without becoming a puzzle.
A creative studio can use the same effect for project category labels, launch titles, or selected campaign headings where the typography needs a little mechanical energy without becoming a full kinetic sequence.
Not for body copy, long headlines, legal text, documentation, form labels, pricing details, error messages, or instructions.
Not for phrases where every word carries essential information and needs immediate readability.
Not for endless loops beside a CTA. A rolling word is memorable once. After the fifth loop, it is just nervous typography.
Keep the animated phrase short, animate transform and opacity, avoid layout-changing character movement, and clean up timers or animation instances when the component unmounts.
Do not run multiple Rolling Text effects in the same viewport unless the page is intentionally built around kinetic typography. Too many rolling words turn hierarchy into machinery noise.
Expose the final word or phrase once to assistive technology. Do not make screen readers read every rolling character layer or transitional glyph.
On mobile, reduce stagger, travel distance, and replay behavior so the word resolves quickly on smaller screens. For reduced motion, render the final text immediately or use a short fade with no rolling character movement.
| Prop | Type | Default | Description |
|---|---|---|---|
text | string | HYPERIUX | The word rendered by the reel. |
textColor | string | #ffffff | Letter color. |
minCycles | number | 3 | Minimum whole letter-heights a character travels before landing. |
cycleVariance | number | 3 | Extra random cycles added on top of minCycles. |
duration | number | 2.4 | Base spin duration in seconds. |
durationVariance | number | 1.2 | Extra random duration added per character, in seconds. |
Rolling Text is a React text animation where letters roll, cycle, or shift into position before resolving into a readable word or short phrase.
Use it for product names, logo-style text, short hero words, launch titles, and technical brand moments where the text should feel assembled or activated.
No. Rolling Text works best on short display copy. Long headlines become harder to read when every character has to perform before the sentence lands.
Render the final phrase as real text and expose it once. Hide duplicated rolling layers or decorative character fragments from assistive technology where needed.
Not if the final text renders as real HTML. Avoid placing the meaningful word only inside canvas, image layers, or decorative animation fragments.
Usually no. Let the word roll in, settle, and stay readable. Looping should be reserved for controlled demo or decorative contexts, not primary page copy.
Show the final word immediately or with a minimal fade. Remove rolling movement, character cycling, stagger, and repeated replay.
Yes. A custom version should define typography rules, roll direction, stagger timing, character behavior, layout reservation, accessibility handling, mobile tuning, reduced-motion fallback, source handoff, and implementation notes.
Need a custom effect? Tell us what to create.