All about variants in framer-motion

If there's one feature that makes framer-motion an absolute kick-ass framework, it's variants.

If there's one feature that makes framer-motion an absolute kick-ass framework, it's variants.

Variants are among the most powerful animation tools for frontend development. What exactly are they? Basically they're pre-defined animation states that you can re-use in your framer animations.

They look something like this:

import { motion } from "framer-motion"

<motion.div variants={{
	start: {opacity: 0, rotate: 0}
    end: {opacity: 1, rotate: 360}
}} />

To break down what we're looking at:

  • We define variants using a variants keyword on a motion component
  • Within variants, we define keys and within those keys, we define some animation states
  • The names of your keys are the names of your variants

đŸšĻ Variants are just animation states


So basically if we translated the above motion component's variants to English, we can say it has two variants: a start and end state. We can then refer to these variants as a string to do actual animations like this:

<motion.div 
	variants={{
	start: {opacity: 0, rotate: 0}
    end: {opacity: 1, rotate: 360}
		}}
    initial="start"
    animate="end"
/>

That's really awesome. Basically variants let us reduce (or rather clean up) the animation syntax for motion components. The cool part about variants is that they're just regular JavaScript objects, you can define them anywhere in your React component!

🚀 You can define variants anywhere


Here's an example of that:

import { motion } from "framer-motion"

const variants = {
	open: { ... },
    close: { ... }
    }

export const Component = () => (
	return <motion.div variants={variants}> ... </motion.div>
)
ℹī¸
There isn't really a best practice for where you should define your variants. Typically I just define them inline with the motion component since, for the most part, variants aren't very big. If you're dealing with variants that have a ton of keys then you probably want to extract it out to a variable and pass it in.

đŸ”ĸ You can define as many variants as you want


Don't think you're just restricted to two variants for each of your framer animations. Variants are kinda arbitrary, literally they are just an object of string keys and framer animations, you can define as many of these keys as you want!

Let's say you want to build an animation that has 4 different variants and each one is activated depending on some state.

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

export const MyComponent = () => {
	const [state, setState] = useState("");
    
    const variants = {
    	first_state: { opacity: "0%" },
        second_state: { scale: 2 },
        third_state: { rotate: 360},
        fourth_state: { skew: 99 }
    }
    
    return (
    	<div>
        	<button onClick={() => setState("first_state")}>Click me for State 1</button>
            <button onClick={() => setState("second_state")}>Click me for State 2</button>
            <button onClick={() => setState("third_state")}>Click me for State 3</button>
            <button onClick={() => setState("fourth_state")}>Click me for State 4</button>
        	<motion.div variants={variants} animate={variants[state]} />
        </div>
    	
    )
}

🌲 Variants get inherited to create complex animations and timelines


One of the best features of variants are that they're inherited! This allows you to create some pretty complex animations and timelines. We'll actually cover this in more detail in the next tutorial on timeline animations and keyframing, for now let's just look at how this works.

By inherited, I mean if you pass a motion component as a child of another motion component with a variant(s), then the child component has access to those variants.

Here's what I'm talking about:

import { motion } from "framer-motion"

<motion.div variants={{open: {opacity: "100%"}, closed: {opacity: "0%"}}} initial="closed" animate="open">
	<motion.h1 variants={{open: {scale: 2}, closed: {scale: 0}}}>Hey I'm content!</motion.h1>
</motion.div>

In the example above we defined a motion component that has some open and closed variants (which toggle its opacity really). Within this motion component, we define another motion component and set some content inside of it as well as some more variants to scale it.

Now, notice how the variants between the parent and child motion components are 1:1? That's on purpose. Also notice we've set the initial state of this animation to be closed and are setting animate to open.

Alright, so the really cool part about variants is this: if your child motion components use the exact same variants names as its parent, then any initial and animate props defined on that parent are automatically passed down to its children.

What does that really mean? It means that we've created a timeline animations where we'll fade in our parent motion component and, automatically, our scaling animation on the h1 component will play automagically, thanks for variants inheritance by framer-motion.

...it also means that the animations run at the exact same time, which may not be ideal (again next tutorial we'll talk a lot more about timelines, keyframing, etc.).

🎁 Wrapping up


In this tutorial we quickly covered the use of variants in framer-motion! You should now be using variants wherever possible over defining your animations within initial / animate. In the next tutorial of this series, we'll talk about timelines and keyframing in order to build some really fun animations.