Live demo
Scale animations change an element’s size by growing or shrinking it from its center point. They’re perfect for hover effects, button feedback, and drawing attention to important elements.
Scale animations work best when combined with smooth easing functions and subtle opacity changes.
Complete code example
Framer Motion
React Spring
CSS
import { motion } from 'framer-motion'
export default function ScaleDemo () {
return (
< div className = "grid grid-cols-3 gap-4" >
{ /* Hover scale */ }
< motion.div
whileHover = { { scale: 1.1 } }
whileTap = { { scale: 0.9 } }
className = "p-6 bg-blue-500 text-white rounded-lg cursor-pointer"
>
Hover me
</ motion.div >
{ /* Pulse scale */ }
< motion.div
animate = { { scale: [ 1 , 1.2 , 1 ] } }
transition = { {
duration: 2 ,
repeat: Infinity ,
ease: 'easeInOut'
} }
className = "p-6 bg-purple-500 text-white rounded-lg"
>
Pulsing
</ motion.div >
{ /* Pop in */ }
< motion.div
initial = { { scale: 0 } }
animate = { { scale: 1 } }
transition = { {
type: 'spring' ,
stiffness: 260 ,
damping: 20
} }
className = "p-6 bg-green-500 text-white rounded-lg"
>
Pop in
</ motion.div >
</ div >
)
}
import { useSpring , animated } from '@react-spring/web'
import { useState } from 'react'
export default function ScaleDemo () {
const [ isScaled , setIsScaled ] = useState ( false )
const scaleProps = useSpring ({
scale: isScaled ? 1.2 : 1 ,
config: { tension: 300 , friction: 10 }
})
return (
< animated.div
style = { {
... scaleProps ,
padding: '1.5rem' ,
background: '#3B82F6' ,
color: 'white' ,
borderRadius: '0.5rem' ,
cursor: 'pointer'
} }
onClick = { () => setIsScaled ( ! isScaled ) }
>
Click to scale
</ animated.div >
)
}
import './scale.css'
export default function ScaleDemo () {
return (
< div className = "grid grid-cols-2 gap-4" >
< div className = "pulse-scale p-6 bg-blue-500 text-white rounded-lg" >
Pulse scale
</ div >
< div className = "hover-scale p-6 bg-purple-500 text-white rounded-lg" >
Hover scale
</ div >
</ div >
)
}
@keyframes pulse {
0% , 100% {
transform : scale ( 1 );
}
50% {
transform : scale ( 1.2 );
}
}
.pulse-scale {
animation : pulse 2 s ease-in-out infinite ;
}
.hover-scale {
transition : transform 0.3 s ease ;
}
.hover-scale:hover {
transform : scale ( 1.1 );
}
.hover-scale:active {
transform : scale ( 0.95 );
}
How it works
Scale animations use the scale transform property:
Set scale value
Scale value of 1 = normal size, less than 1 = smaller, greater than 1 = larger. For example, 1.5 = 150% of original size.
Define transform origin
By default, elements scale from their center. Change with transform-origin.
Apply transition
Add timing and easing to create smooth scaling motion.
Scale variations
Uniform scale scale: 1.5 - Scales both width and height equally
Horizontal scale scaleX: 1.5 - Scales only width
Vertical scale scaleY: 1.5 - Scales only height
Independent axes scale: [1.5, 0.8] - Different X and Y scaling
Variations
Scale down on press for tactile feedback:
import { motion } from 'framer-motion'
function Button () {
return (
< motion.button
whileHover = { { scale: 1.05 } }
whileTap = { { scale: 0.95 } }
className = "px-6 py-3 bg-blue-500 text-white rounded-lg"
>
Click Me
</ motion.button >
)
}
Entrance animation
Pop in effect for appearing elements:
import { motion } from 'framer-motion'
function PopIn () {
return (
< motion.div
initial = { { scale: 0 , opacity: 0 } }
animate = { { scale: 1 , opacity: 1 } }
transition = { {
type: 'spring' ,
stiffness: 260 ,
damping: 20
} }
>
Content
</ motion.div >
)
}
Breathing animation
Subtle scale pulse for attention:
import { motion } from 'framer-motion'
function Breathing () {
return (
< motion.div
animate = { {
scale: [ 1 , 1.05 , 1 ]
} }
transition = { {
duration: 2 ,
repeat: Infinity ,
ease: 'easeInOut'
} }
className = "w-16 h-16 bg-red-500 rounded-full"
>
Live
</ motion.div >
)
}
Scale with origin
Scale from specific point:
import { motion } from 'framer-motion'
function ScaleFromCorner () {
return (
< motion.div
whileHover = { { scale: 1.2 } }
style = { { transformOrigin: 'top left' } }
className = "w-32 h-32 bg-purple-500"
>
Scales from top-left
</ motion.div >
)
}
Example from the playground
From the AdvancedAnimations component:
import { motion } from 'framer-motion'
import { useState } from 'react'
function ScaleAnimation () {
const [ isActive , setIsActive ] = useState ( false )
return (
< div >
< motion.div
animate = { isActive ? {
scale: [ 1 , 1.5 , 0.5 , 1 ]
} : {} }
transition = { {
duration: 2 ,
repeat: Infinity ,
repeatType: 'loop'
} }
className = "w-20 h-20 bg-blue-500 rounded-lg"
/>
< button
onClick = { () => setIsActive ( ! isActive ) }
className = "mt-4 px-4 py-2 bg-blue-500 text-white rounded"
>
{ isActive ? 'Stop' : 'Start' } Animation
</ button >
</ div >
)
}
Best practices
Large scale changes can be jarring. Stick to 0.9-1.2 range for most UI interactions. // Good - subtle
whileHover = {{ scale : 1.05 }}
// Too much for UI
whileHover = {{ scale : 2 }}
Adding opacity changes makes scale animations smoother. < motion.div
initial = { { scale: 0 , opacity: 0 } }
animate = { { scale: 1 , opacity: 1 } }
/>
Spring transitions create more natural scaling motion. transition = {{ type : 'spring' , stiffness : 260 , damping : 20 }}
Consider transform origin
Default center scaling works for most cases, but custom origins can create interesting effects.
Common use cases
Button hover and press states
Card hover effects
Image zoom on hover
Loading pulse animations
Notification badges
Success/error indicators
Modal entrances
Emoji reactions
Scale using transform is GPU-accelerated and performs excellently. Avoid animating width/height properties as they cause layout recalculations.
// Good - GPU accelerated
< motion.div animate = { { scale: 1.5 } } />
// Bad - causes layout recalc
< motion.div animate = { { width: 200 , height: 200 } } />
Advanced: Independent axis scaling
Scale X and Y independently for squeeze/stretch effects:
import { motion } from 'framer-motion'
function Squash () {
return (
< motion.div
animate = { {
scaleX: [ 1 , 1.2 , 0.8 , 1 ],
scaleY: [ 1 , 0.8 , 1.2 , 1 ]
} }
transition = { {
duration: 1 ,
repeat: Infinity ,
ease: 'easeInOut'
} }
className = "w-32 h-32 bg-gradient-to-r from-pink-500 to-purple-500"
/>
)
}
Fade in Opacity transitions
Pulse Rhythmic scale animations
Bounce Bouncing scale effects