Rotating Text
Cycles through words with a springy vertical slide.
Ship products faster
components/ui/rotating-text.tsx
"use client";
import { AnimatePresence, motion } from "motion/react";
import { useEffect, useState } from "react";
type RotatingTextProps = {
words: string[];
/** Milliseconds each word stays on screen */
interval?: number;
className?: string;
};
export function RotatingText({ words, interval = 2200, className = "" }: RotatingTextProps) {
const [index, setIndex] = useState(0);
useEffect(() => {
const id = setInterval(() => setIndex((i) => (i + 1) % words.length), interval);
return () => clearInterval(id);
}, [words.length, interval]);
return (
<motion.span layout className={`relative inline-flex overflow-hidden py-1 align-bottom ${className}`}>
<AnimatePresence mode="popLayout" initial={false}>
<motion.span
key={words[index]}
className="inline-block whitespace-nowrap"
initial={{ y: "110%", opacity: 0, filter: "blur(6px)" }}
animate={{ y: "0%", opacity: 1, filter: "blur(0px)" }}
exit={{ y: "-110%", opacity: 0, filter: "blur(6px)" }}
transition={{ type: "spring", stiffness: 240, damping: 26 }}
>
{words[index]}
</motion.span>
</AnimatePresence>
</motion.span>
);
}
export default function Demo() {
return (
<h2 className="text-center text-4xl font-semibold tracking-tight text-zinc-900 sm:text-5xl">
Ship products{" "}
<RotatingText words={["faster", "smarter", "together", "beautifully"]} className="text-blue-600" />
</h2>
);
}
Installation
Install the dependencies
$ npm i motion lucide-reactCopy the source into
components/ui/rotating-text.tsxcomponents/ui/rotating-text.tsx"use client"; import { AnimatePresence, motion } from "motion/react"; import { useEffect, useState } from "react"; type RotatingTextProps = { words: string[]; /** Milliseconds each word stays on screen */ interval?: number; className?: string; }; export function RotatingText({ words, interval = 2200, className = "" }: RotatingTextProps) { const [index, setIndex] = useState(0); useEffect(() => { const id = setInterval(() => setIndex((i) => (i + 1) % words.length), interval); return () => clearInterval(id); }, [words.length, interval]); return ( <motion.span layout className={`relative inline-flex overflow-hidden py-1 align-bottom ${className}`}> <AnimatePresence mode="popLayout" initial={false}> <motion.span key={words[index]} className="inline-block whitespace-nowrap" initial={{ y: "110%", opacity: 0, filter: "blur(6px)" }} animate={{ y: "0%", opacity: 1, filter: "blur(0px)" }} exit={{ y: "-110%", opacity: 0, filter: "blur(6px)" }} transition={{ type: "spring", stiffness: 240, damping: 26 }} > {words[index]} </motion.span> </AnimatePresence> </motion.span> ); } export default function Demo() { return ( <h2 className="text-center text-4xl font-semibold tracking-tight text-zinc-900 sm:text-5xl"> Ship products{" "} <RotatingText words={["faster", "smarter", "together", "beautifully"]} className="text-blue-600" /> </h2> ); }Use it
The file exports the component and a
Demoshowing typical usage. Import whichever you need.