Style Library
Text Roll
AnimationanimationA text roll component that rotates each character, fully customizable for nice text animations.
Prompt
Paste into any AI assistant before describing what to build — or attach it to a project below.
# Animation pattern — Text Roll
A text roll component that rotates each character, fully customizable for nice text animations.
Build it in React with `motion` (motion/react) and Tailwind CSS. Respect `prefers-reduced-motion` (render the final state without movement), keep it under ~400 ms for UI feedback, and animate only transform and opacity where possible.
Reference implementation (Motion-Primitives, MIT):
```tsx
'use client';
import {
motion,
VariantLabels,
Target,
TargetAndTransition,
Transition,
} from 'motion/react';
export type TextRollProps = {
children: string;
duration?: number;
getEnterDelay?: (index: number) => number;
getExitDelay?: (index: number) => number;
className?: string;
transition?: Transition;
variants?: {
enter: {
initial: Target | VariantLabels | boolean;
animate: TargetAndTransition | VariantLabels;
};
exit: {
initial: Target | VariantLabels | boolean;
animate: TargetAndTransition | VariantLabels;
};
};
onAnimationComplete?: () => void;
};
export function TextRoll({
children,
duration = 0.5,
getEnterDelay = (i) => i * 0.1,
getExitDelay = (i) => i * 0.1 + 0.2,
className,
transition = { ease: 'easeIn' },
variants,
onAnimationComplete,
}: TextRollProps) {
const defaultVariants = {
enter: {
initial: { rotateX: 0 },
animate: { rotateX: 90 },
},
exit: {
initial: { rotateX: 90 },
animate: { rotateX: 0 },
},
} as const;
const letters = children.split('');
return (
<span className={className}>
{letters.map((letter, i) => {
return (
<span
key={i}
className='relative inline-block [perspective:10000px] [transform-style:preserve-3d] [width:auto]'
aria-hidden='true'
>
<motion.span
className='absolute inline-block [backface-visibility:hidden] [transform-origin:50%_25%]'
initial={
variants?.enter?.initial ?? defaultVariants.enter.initial
}
animate={
variants?.enter?.animate ?? defaultVariants.enter.animate
}
transition={{
...transition,
duration,
delay: getEnterDelay(i),
}}
>
{letter === ' ' ? '\u00A0' : letter}
</motion.span>
<motion.span
className='absolute inline-block [backface-visibility:hidden] [transform-origin:50%_100%]'
initial={variants?.exit?.initial ?? defaultVariants.exit.initial}
animate={variants?.exit?.animate ?? defaultVariants.exit.animate}
transition={{
...transition,
duration,
delay: getExitDelay(i),
}}
onAnimationComplete={
letters.length === i + 1 ? onAnimationComplete : undefined
}
>
{letter === ' ' ? '\u00A0' : letter}
</motion.span>
<span className='invisible'>
{letter === ' ' ? '\u00A0' : letter}
</span>
</span>
);
})}
<span className='sr-only'>{children}</span>
</span>
);
}
```Use it in an app
Create a free account to attach this style to a project: the hub adds it to the prompt your AI assistant gets, so it builds every page in this look.
From Motion-Primitives (MIT, commit 120f64f).View source