Skip to main content
This hook is deprecated. Use useAnimate() for new projects, which provides a more flexible and modern animation API.
useAnimation() is a React hook that creates LegacyAnimationControls, which can be used to manually control animations on one or more Motion components.

Signature

function useAnimation(): LegacyAnimationControls
Alias for useAnimationControls().

Return Value

LegacyAnimationControls
object
Animation controller with methods to start, stop, and set animations.

Usage

Pass the returned controls to the animate prop of motion components:
import { motion, useAnimation } from "motion/react"
import { useEffect } from "react"

function Component() {
  const controls = useAnimation()
  
  useEffect(() => {
    controls.start({
      x: 100,
      transition: { duration: 0.5 }
    })
  }, [])
  
  return <motion.div animate={controls} />
}

Examples

Trigger on Event

import { motion, useAnimation } from "motion/react"

function Component() {
  const controls = useAnimation()
  
  return (
    <>
      <motion.div animate={controls} />
      
      <button
        onClick={() => controls.start({ scale: 1.2 })}
      >
        Animate
      </button>
    </>
  )
}

Control Multiple Elements

import { motion, useAnimation } from "motion/react"

function Component() {
  const controls = useAnimation()
  
  return (
    <>
      <motion.div animate={controls} className="box-1" />
      <motion.div animate={controls} className="box-2" />
      <motion.div animate={controls} className="box-3" />
      
      <button
        onClick={() => controls.start({ 
          rotate: 180,
          transition: { duration: 0.5 }
        })}
      >
        Rotate All
      </button>
    </>
  )
}

Sequence Animations

import { motion, useAnimation } from "motion/react"

function Component() {
  const controls = useAnimation()
  
  async function sequence() {
    await controls.start({ x: 100 })
    await controls.start({ y: 100 })
    await controls.start({ x: 0, y: 0 })
  }
  
  return (
    <>
      <motion.div animate={controls} />
      <button onClick={sequence}>Run Sequence</button>
    </>
  )
}

Using Variants

import { motion, useAnimation } from "motion/react"

const variants = {
  hidden: { 
    opacity: 0, 
    y: 50 
  },
  visible: { 
    opacity: 1, 
    y: 0,
    transition: {
      duration: 0.5,
      ease: "easeOut"
    }
  }
}

function Component() {
  const controls = useAnimation()
  
  return (
    <>
      <motion.div
        variants={variants}
        initial="hidden"
        animate={controls}
      />
      
      <button onClick={() => controls.start("visible")">
        Show
      </button>
      <button onClick={() => controls.start("hidden")">
        Hide
      </button>
    </>
  )
}

Dynamic Animation Based on State

import { motion, useAnimation } from "motion/react"
import { useEffect, useState } from "react"

function Component() {
  const controls = useAnimation()
  const [isActive, setIsActive] = useState(false)
  
  useEffect(() => {
    if (isActive) {
      controls.start({
        scale: 1.2,
        backgroundColor: "#ff0000"
      })
    } else {
      controls.start({
        scale: 1,
        backgroundColor: "#0000ff"
      })
    }
  }, [isActive])
  
  return (
    <>
      <motion.div animate={controls} />
      <button onClick={() => setIsActive(!isActive)">
        Toggle
      </button>
    </>
  )
}

Scroll-Triggered Animation

import { motion, useAnimation } from "motion/react"
import { useEffect } from "react"
import { useInView } from "motion/react"

function Component() {
  const controls = useAnimation()
  const ref = useRef(null)
  const isInView = useInView(ref)
  
  useEffect(() => {
    if (isInView) {
      controls.start("visible")
    }
  }, [isInView])
  
  return (
    <motion.div
      ref={ref}
      animate={controls}
      initial="hidden"
      variants={{
        hidden: { opacity: 0, y: 50 },
        visible: { opacity: 1, y: 0 }
      }}
    />
  )
}

Automatic Cleanup

The hook automatically handles cleanup when the component unmounts. All subscribed animations will be stopped.

Important Notes

controls.start() and controls.set() must be called after the component has mounted. Call them inside useEffect or event handlers, not during render.
// ❌ Wrong - called during render
function Component() {
  const controls = useAnimation()
  controls.start({ x: 100 }) // Error!
  
  return <motion.div animate={controls} />
}

// ✅ Correct - called in useEffect
function Component() {
  const controls = useAnimation()
  
  useEffect(() => {
    controls.start({ x: 100 })
  }, [])
  
  return <motion.div animate={controls} />
}

// ✅ Correct - called in event handler
function Component() {
  const controls = useAnimation()
  
  return (
    <>
      <motion.div animate={controls} />
      <button onClick={() => controls.start({ x: 100 })">
        Animate
      </button>
    </>
  )
}

Type Definition

interface LegacyAnimationControls {
  start(
    definition: AnimationDefinition,
    transitionOverride?: Transition
  ): Promise<any>
  
  set(definition: AnimationDefinition): void
  
  stop(): void
  
  subscribe(visualElement: VisualElement): () => void
  
  mount(): () => void
}

type AnimationDefinition = 
  | TargetAndTransition
  | VariantLabels
  | TargetResolver

type VariantLabels = string | string[]

type TargetAndTransition = Target & {
  transition?: Transition
  transitionEnd?: ResolvedValues
}

Migration to useAnimate

For new projects, use the modern useAnimate() hook:
import { motion, useAnimation } from "motion/react"
import { useEffect } from "react"

function Component() {
  const controls = useAnimation()
  
  useEffect(() => {
    controls.start({ x: 100 })
  }, [])
  
  return <motion.div animate={controls} />
}

Why Migrate?

  1. No wrapper required: Works with regular HTML elements
  2. Scoped selectors: Target child elements easily
  3. Better sequences: More intuitive API for complex animations
  4. Type safety: Improved TypeScript support
  5. Modern API: Aligned with current web standards

See Also

Build docs developers (and LLMs) love