Skip to content
Animation

framer-motion animations for beginners

A practical start with framer-motion: understand animate, transition and variants, stagger lists and make motion accessible.

10 min readUpdated: 12 July 2026
Animationframer-motionReact
initial01
Series 04
Light that stays all day
Six lamps from one workshop in Utrecht.
See the rangeShowroom

framer-motion takes the tedious manual work out of animation. Instead of managing keyframes and timing yourself, you only describe the target state and the library computes the path there. This guide takes you from your first motion to staggered lists and shows what matters for calm, high-quality motion.

The short version
  • framer-motion does not replace CSS transitions. It pays off where states come and go.
  • Three properties are enough to start: initial, animate and transition.
  • Stagger instead of all at once: a few tenths of a second turn one motion into an order.
  • prefers-reduced-motion belongs in every component. It is one line of code and for some people the difference.
01

The motion element as the base

Every animation starts with a motion element. A div becomes motion.div, a button becomes motion.button. These elements behave like their normal counterparts but understand extra props for motion.

With the animate prop you define how the element should look. A value like animate with opacity 1 and y 0 describes the target state. When this value changes, framer-motion animates there smoothly without you calculating the path in between.

For the start state you use initial. This way an element fades in from opacity 0 and a slight shift on load. That produces the typical calm slide-up that shapes the first impression on many pages.

initial
opacity 0, y 12
animate
opacity 1, y 0
duration
0.5 s
ease
cubic-bezier(.22, 1, .36, 1)
stagger
80 ms

Twelve pixels of travel and half a second. A hero needs no more.

The same path, just started offset. The switch shows what happens with reduced motion.

<motion.div   initial={{ opacity: 0, y: 22 }}   animate={{ opacity: 1, y: 0 }}   transition={{ duration: 0.6, ease: [0.22, 1, 0.36, 1] }}   Weitfeld</motion.div>
The calm slide-up almost every section starts from.
02

Control transitions with transition

The transition prop determines the pace and character of the motion. With duration you set the time in seconds, with ease the acceleration curve. Values between 0.3 and 0.6 seconds feel pleasant for most UI motions.

Instead of a curve you can also pick a spring. With type spring the motion responds to stiffness and damping and feels more physical. For calm interfaces a soft curve like [0.22, 1, 0.36, 1] often stays the better choice, because it eases out more controlled.

0.15 sToo short. You see a jump.
0.5 sThe everyday value for fades.Recommended
1.6 sToo long. You wait for the interface.

The same curve, three durations. The usable range is narrower than it feels while building.

Curve before springA spring feels alive but overshoots and is hard to predict. For calm, high-end surfaces a soft curve is the more reliable choice. Springs pay off where something follows a finger.

Three curves cover everyday work.

A page with three curves feels ordered, one with twelve feels random. These three are almost always enough.

Soft0.22, 1, 0.36, 1Anything entering
Symmetric0.4, 0, 0.2, 1Open and close, back and forth
Calm0.32, 0.72, 0, 1Long distances across the page
03

Variants for clean states

As soon as an element has several states, inline values get messy. Variants solve this: you define named states like hidden and visible as an object and reference them through initial and animate by their name only.

The big advantage shows with nested elements. A parent can pass its variants down to all children. That way you control a whole group with a single state change, without addressing each child individually.

const auftauchen = {  hidden: { opacity: 0, y: 18 },   show: { opacity: 1, y: 0 },}; <motion.li variants={auftauchen} />
One name instead of two objects on every element.
04

Stagger lists with staggerChildren

When many elements appear at once, it feels flat. With staggerChildren in the parent variant you set a small offset between the children, usually 0.05 to 0.12 seconds. The elements then appear one after another and the eye is guided.

For this to work, each child gets the same variant names as the parent. framer-motion handles the order and the spacing. For cards, navigation items or feature lists this is an effective, subtle effect.

0.00s0.24s
Three elements, six hundredths of offset. That turns simultaneous into a sequence.
const liste = {  hidden: {},   show: { transition: { staggerChildren: 0.08 } },};
The offset sits on the parent, not on every child.
05

Animate on scroll

Many motions should only trigger once an element enters the visible area. For that you replace animate with whileInView and use viewport to define when and how often the animation starts. With once true it runs only the first time it becomes visible.

This avoids content further down having played its fade-in before the user even sees it. The result is a page that builds up calmly and lively as you scroll.

Do not forget onceWithout once true the entrance replays every time the section leaves and re-enters the viewport. Scrolling up and down then makes the page blink with every move.
06

Keep motion accessible

Not everyone wants to see motion. The hook useReducedMotion from framer-motion reads the system setting prefers-reduced-motion. When it is active you deliver a calmer variant, for example just a fade-in without any shift.

Also keep animations short and avoid strong scaling, rotations or hectic jumping. Good motion supports the content and never pushes itself into the foreground.

const reduce = useReducedMotion(); <motion.div   initial={reduce ? false : { opacity: 0, y: 22 }}   animate={{ opacity: 1, y: 0 }}/>
End state immediately, instead of the same motion faster.
07

The three most common beginner mistakes

Almost every problem with framer-motion is not a bug in the library but an expectation that does not match how it works.

  1. whileInView on absolutely positioned children. The observer does not report such elements reliably, and then the content stays invisible forever. Attach it to a box with real height.
  2. Animating width or height to auto. The target value gets measured mid-motion, and that visibly stutters. Measure once yourself and animate in pixels.
  3. Starting everything at once. Ten elements with the same delay feel like a jump. An offset of 0.06 to 0.12 seconds turns it into an order.
Janks or jumps
  • Animating height from 0 to auto.
  • Using top and left for a shift.
  • Ten elements with the same delay.
  • Scaling from 0.6 to 1 on large surfaces.
Runs smoothly
  • Animate a measured pixel height.
  • Use transform via x and y.
  • 0.06 to 0.12 seconds of offset per element.
  • From 0.98 to 1, barely visible and still felt.

If a motion needs explaining, it was too big.

08

Taking reduced motion seriously

Some people get dizzy or a headache from moving interfaces. Their operating system tells the page, if you ask.

In framer-motion you ask with useReducedMotion. The rest is a decision per component: show the final state right away instead of just speeding the motion up. A faster animation is still an animation.

Share
Written by
Amelie RoesmannCreative directionLeonie RoesmannDesign engineering

We build websites and web apps at Systra Studios in Münster and put the building blocks from that work here. What you read comes from real projects, not from a brochure.

Systra Studios: web design from Münster

You know the building blocks.
You do not have to redo the tuning.

Ready-made sequences with staggered steps, scroll triggers and calm curves. Copy, swap the copy, keep building.

  • Every motion sits in the range users read as fluid.
  • useReducedMotion is set in every component.
  • One file per component, three dependencies.
Browse process sections
FAQ

Frequently asked questions

For a hover change or a fade-in, CSS is enough. framer-motion pays off as soon as elements enter and leave, as soon as several parts share an order, or as soon as a motion needs to be interrupted and redirected. That is exactly where CSS gets awkward.

Didn't find your question? We're happy to help.

Get in touch