Skip to main content

Live demo

Rotation animations add dynamic motion by spinning elements around their center point. They’re commonly used for loading spinners, interactive icons, and attention-grabbing effects.
Continuous rotations work best with infinite loops, while discrete rotations (90°, 180°) are great for interactive elements.

Complete code example

import { motion } from 'framer-motion'
import { useState } from 'react'

export default function RotateDemo() {
  const [rotationSpeed, setRotationSpeed] = useState(2)

  return (
    <div>
      <label className="block mb-4">
        Rotation Speed: {rotationSpeed}s
        <input
          type="range"
          min="0.5"
          max="5"
          step="0.5"
          value={rotationSpeed}
          onChange={(e) => setRotationSpeed(parseFloat(e.target.value))}
          className="w-full"
        />
      </label>

      <motion.div
        animate={{
          rotate: [0, 180, 0],
          scale: [1, 1.2, 1]
        }}
        transition={{
          duration: rotationSpeed,
          ease: 'easeInOut',
          repeat: Infinity,
          repeatDelay: 0.5
        }}
        className="w-32 h-32 bg-blue-500 mx-auto"
      />
    </div>
  )
}

How it works

Rotation animations use the CSS rotate or transform: rotate() property:
1

Set rotation angle

Define the target rotation in degrees (0-360°) or radians.
2

Choose timing function

Use linear for continuous spins or ease-in-out for smoother starts/stops.
3

Set repeat behavior

Decide if the animation should loop infinitely or play once.

Rotation types

Continuous spin

360° rotation with linear timing, infinite repeat. Perfect for loading indicators.

Discrete rotation

Fixed angle rotations (90°, 180°) with easing. Great for interactive elements.

Wobble rotation

Back-and-forth rotation using negative values. Creates attention-grabbing effect.

Combined rotation

Rotation combined with other transforms like scale or translate.

Variations

Loading spinner

Classic loading indicator with rotation:
import { motion } from 'framer-motion'

function LoadingSpinner() {
  return (
    <motion.div
      animate={{ rotate: 360 }}
      transition={{
        duration: 1,
        repeat: Infinity,
        ease: 'linear'
      }}
      className="w-12 h-12 border-4 border-blue-500 border-t-transparent rounded-full"
    />
  )
}

Rotate on hover

Interactive rotation triggered by hover:
import { motion } from 'framer-motion'

function RotateOnHover() {
  return (
    <motion.button
      whileHover={{ rotate: 180 }}
      transition={{ duration: 0.3 }}
      className="p-4 bg-blue-500 text-white rounded-lg"
    >
      Hover Me
    </motion.button>
  )
}

3D rotation

Rotate on multiple axes for 3D effect:
import { motion } from 'framer-motion'

function Rotate3D() {
  return (
    <motion.div
      animate={{
        rotateY: [0, 360],
        rotateX: [0, 180, 0]
      }}
      transition={{
        duration: 3,
        repeat: Infinity,
        ease: 'easeInOut'
      }}
      style={{ transformStyle: 'preserve-3d' }}
      className="w-32 h-32 bg-gradient-to-r from-purple-500 to-pink-500"
    />
  )
}

Wobble effect

Back-and-forth rotation for attention:
<motion.div
  animate={{ rotate: [-5, 5, -5] }}
  transition={{
    duration: 0.5,
    repeat: Infinity,
    repeatType: 'reverse'
  }}
>
  Notification Badge
</motion.div>

Example from the playground

This code is extracted from the Animation Playground’s ReactAnimations component:
import { motion } from 'framer-motion'
import { useState } from 'react'

function RotationPlayground() {
  const [rotationSpeed, setRotationSpeed] = useState(2)
  const [scaleAmount, setScaleAmount] = useState(1.2)

  return (
    <div>
      <div className="flex gap-4 mb-4">
        <label>
          Rotation Speed: {rotationSpeed}s
          <input
            type="range"
            min="0.5"
            max="5"
            step="0.5"
            value={rotationSpeed}
            onChange={(e) => setRotationSpeed(parseFloat(e.target.value))}
          />
        </label>
        <label>
          Scale: {scaleAmount}x
          <input
            type="range"
            min="1"
            max="2"
            step="0.1"
            value={scaleAmount}
            onChange={(e) => setScaleAmount(parseFloat(e.target.value))}
          />
        </label>
      </div>
      
      <motion.div
        animate={{
          scale: [1, scaleAmount, 1],
          rotate: [0, 180, 0],
          borderRadius: ['0%', '50%', '0%']
        }}
        transition={{
          duration: rotationSpeed,
          ease: 'easeInOut',
          repeat: Infinity,
          repeatDelay: 0.5
        }}
        className="w-32 h-32 bg-blue-500 mx-auto cursor-pointer"
        whileHover={{ scale: 1.1 }}
        whileTap={{ scale: 0.9 }}
      />
    </div>
  )
}

Best practices

Set transform-origin to control the rotation pivot point. Default is center.
<motion.div
  animate={{ rotate: 90 }}
  style={{ transformOrigin: 'top left' }}
/>
  • linear: Continuous, mechanical rotations
  • ease-in-out: Natural starts and stops
  • spring: Bouncy, playful rotations
Too much rotation can cause motion sickness. Use sparingly and respect prefers-reduced-motion.
Rotation works great with scale and translate for more dynamic animations.

Common use cases

  • Loading spinners and progress indicators
  • Refresh/reload buttons
  • Expand/collapse icons (chevrons)
  • Animated logos
  • Card flips
  • Gear/cog animations
  • Success/error indicators

Performance tips

Rotation using transform: rotate() is GPU-accelerated. Avoid using deprecated rotation properties.
// Good - GPU accelerated
<motion.div animate={{ rotate: 360 }} />

// Also good - 3D rotation
<motion.div animate={{ rotateY: 360 }} />

// Avoid - not standard
<motion.div style={{ rotation: 360 }} />

Scale

Grow and shrink animations

Bounce

Bouncing motion effects

Morph

Shape transformations

Build docs developers (and LLMs) love