Skip to main content

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

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>
  )
}

How it works

Scale animations use the scale transform property:
1

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.
2

Define transform origin

By default, elements scale from their center. Change with transform-origin.
3

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

Button press effect

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 }}
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

Performance tips

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

Build docs developers (and LLMs) love