Components
Character Morph
A character-by-character text morphing component with staggered animations.
Last updated on
"use client";
import { CharacterMorph } from "@/components/ui/character-morph";
export function CharacterMorphDemo() {
return (
<div className="flex items-center justify-center">
<CharacterMorph
texts={["Hello", "World", "Morph", "Effect"]}
className="font-bold text-4xl text-foreground"
/>
</div>
);
}Examples
Variants
"use client";
import { CharacterMorph } from "@/components/ui/character-morph";
export function CharacterMorphCustomDemo() {
return (
<div className="flex flex-col items-center justify-center gap-8">
<div className="text-center">
<h3 className="mb-4 font-semibold text-lg">Fast Morph</h3>
<CharacterMorph
texts={["Code", "Hack", "Debug", "Fix"]}
interval={2000}
staggerDelay={0.02}
charDuration={0.3}
className="font-mono text-2xl text-primary"
/>
</div>
<div className="text-center">
<h3 className="mb-4 font-semibold text-lg">Slow Morph</h3>
<CharacterMorph
texts={["Design", "Create", "Build", "Deploy"]}
interval={4000}
staggerDelay={0.05}
charDuration={0.8}
className="font-bold text-3xl text-destructive"
/>
</div>
</div>
);
}Installation
CLI
npx shadcn@latest add "https://jolyui.dev/r/character-morph"Manual
Install the following dependencies:
npm install motionCopy and paste the following code into your project. component/ui/character-morph.tsx
import { AnimatePresence, motion } from "motion/react";
import * as React from "react";
import { cn } from "@/lib/utils";
interface CharacterMorphProps {
texts: string[];
className?: string;
interval?: number;
staggerDelay?: number;
charDuration?: number;
}
const CharacterMorph = React.forwardRef<HTMLDivElement, CharacterMorphProps>(
(
{
texts,
className,
interval = 3000,
staggerDelay = 0.03,
charDuration = 0.5,
},
ref,
) => {
const [currentIndex, setCurrentIndex] = React.useState(0);
const currentText = texts[currentIndex] || "";
React.useEffect(() => {
const timer = setInterval(() => {
setCurrentIndex((prev) => (prev + 1) % texts.length);
}, interval);
return () => clearInterval(timer);
}, [interval, texts.length]);
const maxLength = Math.max(...texts.map((t) => t.length));
return (
<div
ref={ref}
className={cn("relative inline-flex whitespace-nowrap", className)}
>
<AnimatePresence mode="popLayout">
{currentText.split("").map((char, i) => (
<motion.span
key={`${currentIndex}-${i}-${char}`}
initial={{ opacity: 0, y: 20, filter: "blur(8px)", rotateX: -90 }}
animate={{ opacity: 1, y: 0, filter: "blur(0px)", rotateX: 0 }}
exit={{ opacity: 0, y: -20, filter: "blur(8px)", rotateX: 90 }}
transition={{
duration: charDuration,
delay: i * staggerDelay,
ease: [0.215, 0.61, 0.355, 1],
}}
className="inline-block"
style={{ transformStyle: "preserve-3d" }}
>
{char === " " ? "\u00A0" : char}
</motion.span>
))}
</AnimatePresence>
{/* Maintain minimum width */}
<span className="invisible absolute">{"M".repeat(maxLength)}</span>
</div>
);
},
);
CharacterMorph.displayName = "CharacterMorph";
export { CharacterMorph };API Reference
Prop
Type
Notes
- Uses motion/react for smooth character-by-character animations
- Each character animates with opacity, vertical movement, blur, and 3D rotation
- Staggered animation delays create a wave-like morphing effect
- Automatically cycles through texts at specified intervals
- Supports custom CSS classes for styling
- Maintains consistent width by using the longest text as reference
- Perfect for dynamic headlines, loading states, or eye-catching text effects
How is this guide?