Rotating Text
Rotating Text brings controlled expression to your interface.
Make it yours.
Install the editable source and its dependencies into your React + Tailwind project.
Scroll horizontally for long lines
pnpm dlx shadcn@4.0.8 add http://localhost:3000/r/rotating-text.jsonUse it
Scroll horizontally for long lines
"use client";
import {RotatingText} from '@/components/jez-ui/ui/rotating-text';
export default function Example(){
return <><div className="w-full rounded-xl bg-muted p-8 md:p-14"><div className="font-display text-5xl font-medium leading-tight tracking-tight md:text-6xl">A place for<br/><RotatingText words={['bold ideas.','small details.','your next thing.']} className="text-primary"/></div><p className="mt-9 text-sm text-muted-foreground">Keep making room.</p></div></>;
}Props & types
Scroll horizontally for long lines
{
words: string[];
interval?: number;
className?: string;
}Source
View 2 source files
registry/ui/rotating-text.tsx
Scroll horizontally for long lines
"use client";
import * as React from "react";
import { cn } from "./utils";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
export function RotatingText({
words,
interval = 2400,
className,
}: {
words: string[];
interval?: number;
className?: string;
}) {
const [index, setIndex] = React.useState(0);
const reduce = useReducedMotion();
React.useEffect(() => {
if (reduce || words.length < 2) return;
const timer = setInterval(
() => setIndex((i) => (i + 1) % words.length),
interval,
);
return () => clearInterval(timer);
}, [reduce, words.length, interval]);
return (
<span
className={cn("inline-grid overflow-hidden align-bottom", className)}
aria-label={words.join(", ")}
>
<AnimatePresence mode="wait">
<motion.span
aria-hidden="true"
key={index}
initial={{ y: reduce ? 0 : 24, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: reduce ? 0 : -24, opacity: 0 }}
transition={{ duration: 0.25 }}
>
{words[index]}
</motion.span>
</AnimatePresence>
</span>
);
}
registry/ui/utils.ts
Scroll horizontally for long lines
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...values: ClassValue[]) {
return twMerge(clsx(values));
}
Dependencies
motion@13.2.0 · clsx@2.1.1 · tailwind-merge@3.5.0
Accessibility & behaviour
Respect reduced-motion preferences. Decorative effects must not carry essential information. WebGL scenes include a static fallback and only render while visible.
Read the accessibility and performance guide →