Site index
Arrow keys to navigateEnter to open / Esc to close
Writing index
Field note / Frontend engineering

Building stunning animated UI with Motion and Tailwind CSS in Next.js

A practical guide to building fluid, interactive interfaces with Motion and Tailwind CSS in a Next.js App Router project, without hurting performance.

2 min readnext.jsmotiontailwindanimation
On this page
Building stunning animated UI with Motion and Tailwind CSS in Next.js

Animation is not decoration. Used well, it explains where something came from, confirms an action, and makes an interface feel responsive. Used badly, it makes a product feel slow.

This guide covers the patterns I actually use: small, purposeful motion that is cheap to run and easy to remove.

Start with the user's preference

Every animation should be optional. Respecting prefers-reduced-motion is not a nice-to-have, it is a baseline requirement.

"use client";

import { MotionConfig } from "motion/react";

export function Providers({ children }: { children: React.ReactNode }) {
  return <MotionConfig reducedMotion="user">{children}</MotionConfig>;
}

Wrapping the app once means every component inherits the behaviour. You do not have to remember it in each file.

Animate transforms, not layout

The browser can composite transform and opacity on the GPU. Animating width, height, top, or left forces layout on every frame.

<motion.div
  initial={{ opacity: 0, y: 12 }}
  animate={{ opacity: 1, y: 0 }}
  transition={{ duration: 0.3, ease: "easeOut" }}
/>

y is a shorthand for translateY, so this stays on the compositor.

Stagger lists for rhythm

A little offset between items reads as intentional rather than abrupt.

const container = {
  hidden: {},
  show: { transition: { staggerChildren: 0.06 } },
};

const item = {
  hidden: { opacity: 0, y: 16 },
  show: { opacity: 1, y: 0 },
};

<motion.ul variants={container} initial="hidden" animate="show">
  {items.map((value) => (
    <motion.li key={value} variants={item} />
  ))}
</motion.ul>

Keep the total sequence under roughly 400ms. Past that, the user is waiting for your animation instead of doing their task.

Hover and press feedback

Micro-interactions should be instant. Hover is about affordance, press is about confirmation.

  • Hover: a subtle background change or a small lift
  • Press: a slight scale down, around 0.98
  • Focus: a visible ring, never removed
<button className="transition-colors hover:bg-black/5 active:scale-[0.98] focus-visible:ring-2">
  Copy
</button>

If you reach for Motion for a colour change, a CSS transition is simpler and cheaper.

When to reach for JavaScript motion

Use Motion when the animation depends on state that CSS cannot express:

ScenarioTool
Colour, shadow, small translateCSS transition
Enter and exit of dynamic contentAnimatePresence
Shared element between routeslayoutId
Gesture-driven or spring physicsMotion

Watch the cost

Motion is a real dependency. Keep it out of the critical path where you can and remember that every animated element is also a paint the browser has to perform.

Keep animations under 300ms for feedback and under 400ms for entrances. If it feels slow, it is slow.

The best animation is the one the user never consciously notices.

Continue reading

Related articles

Modal interface

Accent system

Choose a palette. The selection is saved on this device.

Accent color themes

28 palettes available

Modal interface

A note worth keeping

A randomly selected thought from the inspiration archive.