Cinematic Interfaces in Next.js: When Animation Does Work Instead of Just Moving
Good animation isn't seen, it's felt. A practical account of building motion-driven UI with Next.js, Motion and Anime.js — and, more importantly, where to stop yourself.
Mehran Hatami4 min readNext.jsAnime.jsMotion DesignThe first animation I wrote for a real admin panel was spectacular, and that was exactly the problem. Cards rotated, numbers leapt, everything shimmered. Three weeks later the same client told me the panel felt heavy. Nothing had gotten slower. Every click just started a little performance nobody had asked for.
Motion is an answer, not a decoration
An animation earns its place when removing it leaves the user lost — not merely when removing it makes the page quieter.
Motion in a UI has one job: tell the user what just happened and where the thing they wanted went. A modal that grows out of the button that opened it shows cause and effect. The same modal fading in at the centre of the screen is just a 300ms delay. The difference isn't beauty, it's meaning.
My working test is blunt: delete the animation. If users get confused, it was doing real work. If the page just got quieter, it was a cost, not an investment.
Why both Motion and Anime.js
They aren't competitors. They're two different kinds of movement, and they sit happily in the same codebase.
- Motion (Framer Motion) for movement tied to interaction: opening and closing, moving between states, layout and shared-element transitions. Because it's physics-based, you can interrupt it halfway and the path still feels natural.
- Anime.js for movement with a timeline: a list entering in sequence, a number counting up, a series of steps with precise offsets. Its stagger function does in two lines what takes thirty with useEffect and setTimeout.
- My rule of thumb: whatever the user touches directly goes to Motion; whatever narrates itself goes to Anime.js.
The curve matters more than the duration
Most bad animation isn't mistimed, it's mis-curved. A linear ease makes movement feel mechanical. A bouncy spring attributes momentum to a surface that merely arrived. I default to a critically damped spring and spend overshoot only where the user genuinely threw something.
// Default: it arrives, it isn't thrown
const CALM = { type: "spring", bounce: 0, duration: 0.5 } as const;
// Exception: a confirmation that should land like a stamp
const STAMP = { type: "spring", bounce: 0.35, duration: 0.4 } as const;
<motion.div layout transition={CALM} />Three rules I no longer break
- Animate transform and opacity only. Anything else forces the browser back into layout, and on a cheap phone that goes straight to visible lag.
- No animation may block the user's path. If someone clicks mid-animation, their click should win rather than wait for the show to finish.
- Looping motion stays below the attention threshold. A slow seven-second wash is background; the same wash in one second is an interruption.
And the setting I never skip
Some users get motion sickness from heavy animation, and they've already declared that in their operating system settings. Honouring it is one line of code, and for those users it's the difference between a product that works and one they close.
const reduced = useReducedMotion();
<motion.div
initial={reduced ? undefined : { opacity: 0, y: 22 }}
animate={{ opacity: 1, y: 0 }}
transition={reduced ? { duration: 0 } : CALM}
/>A cinematic interface is one with rhythm, not one that's busy. When it works, users don't talk about the animation at all — they say the panel feels easy to use. That sentence is the best review motion design can get.
