Home
Contact
Blog
Playground
BlogThoughtsMotion (ex Framer Motion): Animations, UX, and Performance

Published September 20, 2026

Motion (ex Framer Motion): Animations, UX, and Performance

What UI animations are, why they matter for UX, and how to keep Motion (ex Framer Motion) fast: GPU-friendly properties, LazyMotion, and reduced motion.

ZB
Ziane Badr Eddine@EddineZian27143

Animations are one of the fastest ways to make an interface feel alive, and one of the fastest ways to make it feel slow, noisy, or broken. The difference is rarely the library. It is what you animate, why, and how.

In this post we will look at:

  • what a UI animation really is, and why we use them,
  • how they affect UX (for better and for worse),
  • how to keep them fast with Motion, the library formerly known as Framer Motion,
  • a checklist of best choices you can apply today.

TL;DR. Animate to explain a change, not to decorate. Stick to transform and opacity, keep most transitions short (roughly 150 to 300 ms), ship the smallest Motion bundle you can, and always respect prefers-reduced-motion.

Framer Motion is now Motion

Framer Motion started as the animation library of Framer, the website builder. It is now an independent project simply called Motion. The API you already know is the same, but the package and the imports changed:

npm install motion 
// Before
import { motion } from "framer-motion";

// Now
import { motion } from "motion/react";

If you use the Next.js App Router, there is also motion/react-client. It lets you use motion components directly from a Server Component file without adding "use client" yourself:

app/page.tsx
import * as motion from "motion/react-client";

export default function Page() {
  return (
    <motion.h1 initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }}>
      Hello, Motion
    </motion.h1>
  );
}

What is a UI animation?

An animation is simply a value that changes over time: an opacity going from 0 to 1, a card moving 12 px up, a panel scaling from 0.95 to 1. The browser draws many frames in between, and your brain reads them as movement.

In a product, animations usually fall into a few families:

FamilyExamplePurpose
Micro-interactionButton press, hover, toggleConfirm that the UI heard you
Enter and exitModal, toast, dropdownShow where something comes from and goes to
Layout changeReordering a list, expanding a cardKeep the user oriented while things move
Scroll-linkedReading progress bar, parallaxConnect the page to the user's position
Loading and feedbackSkeleton, spinner, success checkMake waiting feel shorter and clearer
DecorativeFloating shapes, background loopsPersonality (use sparingly)

Why do we animate at all?

Because the screen has no physical continuity. In the real world, a drawer slides out. On a screen, without animation, it simply appears. Motion gives the eye a path to follow.

Good animations do real work:

  • Feedback. A pressed button that scales down slightly says "I got your click".
  • Orientation. A page or panel that slides in from the right tells you where you are in the flow.
  • Continuity. A card that grows into a detail view keeps the user's mental model intact.
  • Attention. A subtle change on a new notification points to what changed.
  • Perceived performance. A skeleton or a short transition makes a wait feel intentional instead of frozen.

Animation is not free. Every animated element costs attention from the user and CPU or GPU time from the device. If an animation does not explain, confirm, or guide, remove it.

The impact on UX

When it helps

People perceive a response under about 100 ms as instant, which is why hover and press feedback should feel immediate. For transitions, a common rule of thumb is:

  • Small feedback (hover, tap, toggle): roughly 100 to 200 ms.
  • Most UI transitions (menus, dialogs, tabs): roughly 150 to 300 ms.
  • Large movements (full page or big panel): up to about 500 ms.

These are guidelines, not specifications. Test them with real people and real devices.

Easing matters as much as duration. Things entering the screen usually look best when they decelerate (fast start, soft landing). Springs are a great default in Motion because they feel physical and handle interruptions gracefully:

<motion.div
  initial={{ opacity: 0, y: 20 }}
  animate={{ opacity: 1, y: 0 }}
  transition={{ type: "spring", stiffness: 260, damping: 24 }}
/>

When it hurts

  • Slow animations on frequent actions. A 600 ms menu is charming once and infuriating the fortieth time.
  • Animations that block the user. Never make people wait for an animation to finish before they can act.
  • Too much movement. Large parallax, spinning, or zooming can cause dizziness or nausea for people with vestibular disorders.
  • Motion without meaning. If everything moves, nothing stands out.

Before adding one, ask this:

Performance: what makes an animation fast or slow

The browser turns your code into pixels in roughly three steps: layout (where things go), paint (what they look like), and composite (combining layers on the GPU). The earlier a step you trigger, the more work every frame costs.

At 60 fps you have about 16.7 ms per frame, and only about 8.3 ms at 120 Hz. Miss it, and users see jank.

Choose the right properties

PropertyWhat it triggersVerdict
transform (x, y, scale, rotate)Composite onlyBest choice
opacityComposite onlyBest choice
filter, clipPathUsually compositeGood, test on low-end devices
backgroundColorPaintFine for small elements
width, height, top, left, marginLayout, then paintAvoid on elements that push other content around

A concrete example. Both bars below "fill up", but only one is cheap:

// Triggers layout on every frame
<motion.div animate={{ width: "100%" }} className="h-1 bg-primary" />

// Composite only
<motion.div
  animate={{ scaleX: 1 }}
  initial={{ scaleX: 0 }}
  style={{ transformOrigin: "0% 50%" }}
  className="h-1 w-full bg-primary"
/>

Need to animate a size or position change of a real layout element, like reordering a list? Use the layout prop. Motion measures the before and after, then animates with transforms instead of animating layout properties directly:

<motion.li
  layout
  transition={{ type: "spring", stiffness: 300, damping: 30 }}
/>

According to Motion's own performance guide, transform and opacity are the cheapest values to render in every browser. Beyond those, test across browsers and low-powered devices before you commit.

Hardware acceleration and the Motion engine

Motion is a hybrid engine. It is built on the Web Animations API (WAAPI), so for values like transform, opacity, filter, and clipPath it can run the animation on the GPU, separately from your JavaScript. That means the animation stays smooth even when the main thread is busy. For everything else, Motion falls back to its JavaScript engine.

One nuance worth knowing: shorthand props like x, y, and scale are independent transforms, which perform great but run on the JavaScript side. Motion documents that setting transform directly is what unlocks hardware acceleration:

<motion.div animate={{ transform: "translateX(100px)" }} />

Use it when the main thread is under pressure. In most everyday UI, independent transforms are perfectly fine.

Keep the bundle small

Motion's motion component is around 34 kb and bundlers cannot tree-shake it further. The docs show two ways to go much smaller:

  • LazyMotion + m brings the initial cost down to about 4.6 kb, then loads features on demand: domAnimation (+15 kb) covers animations, variants, exit animations and tap, hover, focus gestures; domMax (+25 kb) adds drag and layout animations.
  • useAnimate mini is only 2.3 kb and uses WAAPI exclusively.
components/motion-provider.tsx
"use client";

import { LazyMotion, domAnimation } from "motion/react";

export function MotionProvider({ children }: { children: React.ReactNode }) {
  return (
    <LazyMotion features={domAnimation} strict>
      {children}
    </LazyMotion>
  );
}
components/fade-in.tsx
"use client";

import * as m from "motion/react-m";

export function FadeIn({ children }: { children: React.ReactNode }) {
  return (
    <m.div initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }}>
      {children}
    </m.div>
  );
}

The strict prop makes Motion throw if someone accidentally uses motion.div inside LazyMotion, which would silently bring the full bundle back.

Need domMax only on one route? Load it asynchronously:

const loadFeatures = () => import("./features").then((res) => res.default);

<LazyMotion features={loadFeatures}>{children}</LazyMotion>;

with features.ts containing:

components/features.ts
import { domMax } from "motion/react";

export default domMax;

Avoid re-renders with motion values

If something updates on every scroll or pointer event, do not put it in React state. Use motion values, which update the DOM without re-rendering your component:

components/reading-progress.tsx
"use client";

import { motion, useScroll, useSpring } from "motion/react";

export function ReadingProgress() {
  const { scrollYProgress } = useScroll();
  const scaleX = useSpring(scrollYProgress, {
    stiffness: 120,
    damping: 24,
    restDelta: 0.001,
  });

  return (
    <motion.div
      style={{ scaleX, transformOrigin: "0% 50%" }}
      className="fixed inset-x-0 top-0 z-50 h-1 bg-primary"
    />
  );
}

This animates scaleX (a transform) driven by scroll, with zero React re-renders.

Do not animate your LCP element

A very common mistake is fading in the hero title or image with initial={{ opacity: 0 }}. That element is invisible until JavaScript loads and hydrates, which can delay your Largest Contentful Paint.

  • Do not hide above-the-fold content behind an entrance animation.
  • If you want an intro, animate secondary elements, or use initial={false} so the first render matches the final state.

Transform-based animations do not cause layout shifts, so they are safe for CLS. Animating height or margin can push content around and hurt it.

Scroll-triggered animations

whileInView is convenient, but every observed element has a cost. Animate once and stop observing:

<motion.section
  initial={{ opacity: 0, y: 24 }}
  whileInView={{ opacity: 1, y: 0 }}
  viewport={{ once: true, margin: "-80px" }}
/>

Accessibility: respect reduced motion

Animations can cause real discomfort. Operating systems expose a Reduced Motion setting, exposed to the web as prefers-reduced-motion. Motion makes it simple to respect it.

Wrap your app once:

components/motion-provider.tsx
import { MotionConfig } from "motion/react";

<MotionConfig reducedMotion="user">{children}</MotionConfig>;

With reducedMotion="user", Motion disables transform and layout animations for users who asked for less motion, while keeping things like opacity and backgroundColor. Content still fades in, but nothing slides or zooms.

For finer control, use the hook:

const shouldReduceMotion = useReducedMotion();

<motion.aside
  animate={{
    opacity: isOpen ? 1 : 0,
    x: isOpen ? 0 : shouldReduceMotion ? 0 : "-100%",
  }}
/>;

Also remember:

  • Anything that moves automatically for more than 5 seconds should have a way to pause, stop, or hide it (WCAG 2.2.2).
  • Avoid flashing content, and be careful with parallax and auto-playing video.
  • Never rely on animation alone to convey information.

A practical workflow

Decide the purpose Write one sentence: "This animation helps the user to

...". If you cannot finish it, skip the animation.

Pick cheap properties Use transform and opacity. For layout changes,

reach for the layout prop.

Keep it short and interruptible Use short durations or springs, and

never block the user's next action.

Ship less JavaScript Use LazyMotion with m, load domMax only where

you need drag or layout animations, and consider useAnimate mini for simple cases.

Respect reduced motion Add MotionConfig with reducedMotion="user"

once at the root.

Measure on real devices Open Chrome DevTools, record a Performance

profile with CPU throttling, and turn on paint flashing and layout shift regions. Test on a real mid-range phone, not just your laptop.

Common questions

Conclusion

Great motion is mostly restraint: a few purposeful animations, on cheap properties, in a small bundle, that respect the people using your product. Motion gives you a hybrid engine, springs, layout animations, and tools like LazyMotion and MotionConfig to do exactly that.

Want to go deeper? The official docs are the best next step:

  • Motion for React documentation
  • Animation performance guide
  • Reduce bundle size
  • Accessible animations

On this page

Framer Motion is now MotionWhat is a UI animation?Why do we animate at all?The impact on UXWhen it helpsWhen it hurtsPerformance: what makes an animation fast or slowChoose the right propertiesHardware acceleration and the Motion engineKeep the bundle smallAvoid re-renders with motion valuesDo not animate your LCP elementScroll-triggered animationsAccessibility: respect reduced motionA practical workflowDecide the purpose Write one sentence: "This animation helps the user toPick cheap properties Use transform and opacity. For layout changes,Keep it short and interruptible Use short durations or springs, andShip less JavaScript Use LazyMotion with m, load domMax only whereRespect reduced motion Add MotionConfig with reducedMotion="user"Measure on real devices Open Chrome DevTools, record a PerformanceCommon questionsConclusion

Navigation

  • Home
  • About
  • Projects
  • Contact

Explore

  • Experience
  • Education
  • Activities

Blog

  • Why I Chose Opaque Tokens Over JWT for My Spring Boot API
  • Java Interview Questions & Answers — My Prep Notes
  • TanStack Query — The Complete Crash Course
  • Motion (ex Framer Motion): Animations, UX, and Performance
  • Why I Stopped Optimizing Early
  • Is Bun Ready for Production in 2026?
  • React vs Vue in 2026 — An Honest Comparison
  • All posts

Language

Connect

GitHub
LinkedIn
Email

Ziane Badr Eddine.