Slide Up Reveal

Buttery smooth text reveal with slide up reveal animation.

Howwwwwwww youu doin :)
TSX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
"use client"; import { useEffect, useState, useCallback } from "react"; import { motion, AnimatePresence, Transition } from "motion/react"; import { cn } from "@/lib/utils"; interface TypewriterLoopProps { LeadText?: string; morphingText?: string[]; className?: string; interval?: number; transition?: Transition; LeadTextClassName?: string; morphingTextClassName?: string; backgroundClassName?: string; cursorClassName?: string; } const TypewriterLoop = ({ LeadText = "Design", morphingText = ["Limitless", "Timeless", "Flawless"], className, interval = 4000, transition = { duration: 0.8, ease: "easeInOut" }, LeadTextClassName, morphingTextClassName, backgroundClassName, cursorClassName, }: TypewriterLoopProps) => { const [index, setIndex] = useState(0); const [colorIndex, setColorIndex] = useState(0); const [nextIndex, setNextIndex] = useState(1); const gradientColors = [ "from-violet-400 to-violet-800 dark:from-violet-400 dark:to-violet-600", "from-blue-400 to-blue-800 dark:from-blue-400 dark:to-blue-600", "from-emerald-400 to-emerald-800 dark:from-emerald-400 dark:to-emerald-600", "from-amber-400 to-amber-800 dark:from-amber-400 dark:to-amber-600", "from-rose-400 to-rose-800 dark:from-rose-400 dark:to-rose-600", "from-fuchsia-400 to-fuchsia-800 dark:from-fuchsia-400 dark:to-fuchsia-600", ]; const backgroundColors = [ "from-transparent via-purple-200/30 to-purple-200 dark:from-transparent dark:via-violet-950/30 dark:to-violet-950/60", "from-transparent via-blue-200/30 to-blue-200 dark:from-transparent dark:via-blue-950/30 dark:to-blue-950/60", "from-transparent via-emerald-200/30 to-emerald-200 dark:from-transparent dark:via-emerald-950/30 dark:to-emerald-950/60", "from-transparent via-amber-200/30 to-amber-200 dark:from-transparent dark:via-amber-950/30 dark:to-amber-950/60", "from-transparent via-rose-200/30 to-rose-200 dark:from-transparent dark:via-rose-950/30 dark:to-rose-950/60", "from-transparent via-fuchsia-200/30 to-fuchsia-200 dark:from-transparent dark:via-fuchsia-950/30 dark:to-fuchsia-950/60", ]; const cursorColors = [ "bg-violet-500", "bg-blue-500", "bg-emerald-500", "bg-amber-500", "bg-rose-500", "bg-fuchsia-500", ]; // Pre-calculate next index useEffect(() => { setNextIndex((index + 1) % morphingText.length); }, [index, morphingText.length]); useEffect(() => { const timer = setInterval(() => { setIndex((prev) => (prev + 1) % morphingText.length); }, interval); return () => clearInterval(timer); }, [morphingText.length, interval]); const handleExitComplete = useCallback(() => { setColorIndex((prev) => (prev + 1) % gradientColors.length); }, [gradientColors.length]); return ( <div className={cn( "flex flex-wrap items-center justify-start w-fit gap-x-2 md:gap-x-3 gap-y-1 text-3xl md:text-7xl font-medium tracking-tight", className, )} > <span className={cn("whitespace-nowrap text-foreground", LeadTextClassName)} > {LeadText} </span> <div className="relative flex items-center"> <AnimatePresence mode="wait" onExitComplete={handleExitComplete}> <motion.div key={morphingText[index]} initial={{ width: 0, opacity: 0 }} animate={{ width: "auto", opacity: 1 }} exit={{ width: 0, opacity: 0 }} transition={transition} className="overflow-hidden whitespace-nowrap relative" > {/* Background gradient box */} <div className={cn( "absolute inset-0", "bg-gradient-to-r", backgroundColors[colorIndex], backgroundClassName, )} /> <span className={cn( "relative bg-clip-text text-transparent", "bg-gradient-to-r", gradientColors[colorIndex], "pr-1", morphingTextClassName, )} > {morphingText[index]} </span> </motion.div> </AnimatePresence> {/* Cursor Line */} <motion.div key={`cursor-${colorIndex}`} className={cn( "w-[3px] md:w-[4px] h-[1.10em] sm:h-[1em]", cursorColors[colorIndex], cursorClassName, )} animate={{ opacity: [1, 0.5] }} transition={{ duration: 0.8, repeat: Infinity, repeatType: "reverse", }} /> </div> </div> ); }; export default TypewriterLoop;

Installation

pnpm dlx shadcn@latest add @satoriui/slide-up-reveal