ForgeUI leverages Framer Motion to deliver smooth, declarative animations across all components. Every animation is carefully crafted for performance and user experience.
Framer Motion integration
ForgeUI uses motion/react for all animations. Components import motion elements and animate them using:
Initial/Animate states - Define start and end states
Transitions - Control timing, easing, and delays
Layout animations - Automatic animations when layout changes
Variants - Reusable animation configurations
Animation patterns
Staggered animations
The TextReveal component demonstrates staggered word reveals using the stagger utility:
import { motion , stagger , useAnimate } from "motion/react" ;
const [ scope , animate ] = useAnimate ();
useEffect (() => {
animate (
"span" ,
{
opacity: 1 ,
filter: filter ? "blur(0px)" : "none" ,
},
{
duration: 0.5 ,
delay: stagger ( 0.2 ),
ease: "easeOut" ,
},
);
}, [ animate ]);
Each word animates sequentially with a 0.2s stagger delay, creating a cascading reveal effect.
Character-by-character animations
The AnimatedForm component shows how to animate individual characters with custom delays:
const nameStaggerDelay = nameAnimationDuration / name . length ;
{ name . split ( "" ). map (( char , index ) => (
< motion.span
key = { `name- ${ index } ` }
className = "inline-block font-[350]"
initial = { { opacity: 0 } }
animate = { { opacity: 1 } }
transition = { {
duration: 0.1 ,
delay: index * nameStaggerDelay ,
ease: "easeOut" ,
} }
>
{ char === " " ? " \u00A0 " : char }
</ motion.span >
))}
Calculate stagger delays dynamically based on content length for consistent timing across different text lengths.
Layout animations
The AnimatedTabs component uses layoutId for smooth morphing between states:
{ isActive && (
< motion.div
layoutId = "active-tab-background"
className = "absolute inset-0 rounded-full bg-primary"
initial = { false }
transition = { {
type: "spring" ,
stiffness: 500 ,
damping: 30 ,
} }
/>
)}
The layoutId prop automatically animates the background as it moves between tabs.
Spring animations
Spring physics create natural, bouncy animations:
transition = {{
type : "spring" ,
stiffness : 500 ,
damping : 30 ,
}}
stiffness : How quickly the spring moves (higher = faster)
damping : How quickly the spring settles (higher = less bounce)
SVG path animations
The AnimatedCheckmarkCircle component animates SVG stroke paths:
< motion.circle
cx = "10"
cy = "10"
r = "7"
stroke = "#22c55e"
strokeWidth = "2"
fill = "transparent"
strokeDasharray = { circleLength }
strokeDashoffset = { circleLength }
animate = { { strokeDashoffset: 0 } }
transition = { {
duration: strokeDuration ,
ease: "easeInOut" ,
delay: strokeDelay ,
} }
/>
Animate from full strokeDashoffset to 0 to create a drawing effect.
Timing and easing
Common easing functions
ForgeUI uses these easing functions for different animation types:
Used for entrance animations and reveals. Creates a fast start with gradual deceleration. transition = {{ ease : "easeOut" , duration : 0.5 }}
Used for state changes and morphing. Creates smooth acceleration and deceleration. transition = {{ ease : "easeInOut" , duration : 0.3 }}
Used for continuous animations like shimmer effects. transition = {{ ease : "linear" , duration : 2 }}
Duration guidelines
Micro-interactions : 0.1-0.2s (button hovers, toggles)
UI transitions : 0.3-0.5s (tabs, modals, cards)
Complex animations : 0.5-1s (multi-step sequences)
Background effects : 2s+ (shimmer, gradients)
AnimatePresence
Use AnimatePresence to animate components when they’re removed from the DOM:
import { AnimatePresence , motion } from "motion/react" ;
< AnimatePresence mode = "popLayout" >
< motion.span
key = { index }
initial = { { opacity: 0 , y: 5 } }
animate = { { opacity: 1 , y: 0 } }
exit = { { opacity: 0 , y: - 5 } }
transition = { { duration: 0.4 } }
>
{ children }
</ motion.span >
</ AnimatePresence >
The mode="popLayout" ensures siblings don’t animate when one element exits.
Animate transform and opacity properties for best performance:
// Good - GPU accelerated
initial = {{ opacity : 0 , y : 20 , scale : 0.95 }}
animate = {{ opacity : 1 , y : 0 , scale : 1 }}
// Avoid - causes layout recalculation
initial = {{ height : 0 }}
animate = {{ height : 'auto' }}
Disable animations during resize
The theme provider disables transitions on theme change:
< ThemeProvider
attribute = "class"
defaultTheme = "dark"
enableSystem
disableTransitionOnChange
>
Will-change for complex animations
For animations that run continuously:
style = {{ willChange : 'transform' }}
Use will-change sparingly - it increases memory usage. Only use it for animations that are actively running.
Customizing animations
Override timing
Most components accept timing props:
< TextReveal
text = "Hello World"
duration = { 0.8 } // Animation duration per word
staggerDelay = { 0.3 } // Delay between words
/>
Custom transitions
Provide custom transition objects:
< motion.div
animate = { { x: 100 } }
transition = { {
type: "spring" ,
stiffness: 300 ,
damping: 20 ,
mass: 0.5 ,
} }
/>
Disable animations
Set initial={false} to disable entrance animations:
< motion.div
initial = { false }
animate = { { opacity: 1 } }
/>
Best practices
Check for reduced motion preferences: const prefersReducedMotion = window . matchMedia (
'(prefers-reduced-motion: reduce)'
). matches ;
< motion.div
animate = { { y: prefersReducedMotion ? 0 : 20 } }
transition = { { duration: prefersReducedMotion ? 0 : 0.5 } }
/>
Animations should enhance, not distract. ForgeUI uses:
Small movements (5-20px)
Short durations (0.1-0.5s)
Subtle opacity changes (0-1)
Trigger animations in response to user interactions rather than on mount. This provides better feedback and feels more responsive.
Maintain consistent timing across similar animations. ForgeUI uses:
0.2s for micro-interactions
0.3s for UI transitions
0.5s for complex animations
Real-world examples
Continuous shimmer effect
From TextShimmer component:
initial = {{ backgroundPosition : "105% center" }}
animate = {{ backgroundPosition : "-5% center" }}
transition = {{
repeat : Number .POSITIVE_INFINITY,
duration : 2 ,
ease : "linear" ,
delay : 0 ,
repeatDelay : 0 ,
}}
Pulsing glow effect
From AnimatedOTP component:
initial = {{
opacity : 0 ,
scale : 1 ,
filter : "blur(0px)" ,
}}
animate = {{
opacity : [ 0 , 1 , 0 ],
scale : [ 0.85 , 1.3 ],
filter : "blur(2px)" ,
}}
transition = {{
duration : 0.5 ,
ease : "easeInOut" ,
delay : 2.25 ,
}}
Coordinated sequence
From AnimatedForm component showing multiple animations with calculated delays:
const nameAnimationDuration = Math . ceil ( name . length / 5 );
const passwordAnimationDuration = 2 ;
// First animation
strokeDelay = { 0 }
fillDelay = {nameAnimationDuration + 0.1 }
checkmarkDelay = {nameAnimationDuration + 0.2 }
// Second animation (waits for first)
strokeDelay = {nameAnimationDuration + 0.5 }
fillDelay = {nameAnimationDuration + passwordAnimationDuration + 0.6 }
checkmarkDelay = {nameAnimationDuration + passwordAnimationDuration + 0.7 }