TL;DR
- Most AI-generated apps ignore animation entirely
- Key patterns: entrance timing, easing curves, stagger delays, reduced-motion support
- Motion prompts encode all these best practices so AI follows them automatically
AI coding tools generate functional apps in minutes. Layouts work. Buttons connect to logic. Data flows correctly. But the ui animations feel like an afterthought — random fade-ins, linear easings, no choreography between elements.
The gap between "works" and "feels good" is almost entirely animation quality. Web animations are the difference between software that feels like a prototype and software that feels like a product. Here are 8 best practices that close that gap for AI-generated applications.
Why AI tools get animations wrong by default
AI coding assistants optimize for correctness, not feel. When Claude, Cursor, or Lovable generate a component, they prioritize semantic HTML, accessibility, data handling, and responsive layout. Animation is treated as decoration — if it appears at all, it's a generic transition: 0.3s ease applied without consideration for the interaction context.
This isn't a limitation of the AI — it's a limitation of the prompt. Without specific animation constraints, AI tools make safe, minimal choices. These 8 practices are the constraints that transform AI output from static to polished.
1. Use spring physics, not linear easing
Linear and cubic-bezier easings feel mechanical. Spring physics feel natural because they model real-world momentum and deceleration.
// Bad: linear easing feels robotic
<motion.div
animate={{ y: 0 }}
transition={{ duration: 0.3, ease: "linear" }}
/>
// Good: spring physics feel natural
<motion.div
animate={{ y: 0 }}
transition={{ type: "spring", damping: 25, stiffness: 250 }}
/>Spring animations don't need a duration — the damping and stiffness values determine how the element settles. Lower damping creates more bounce; higher stiffness creates snappier movement. AI tools almost never use springs unless explicitly told to.
2. Stagger list and grid animations
When AI tools animate a list, they typically animate every item simultaneously. This creates a jarring flash rather than a readable sequence. Stagger animations give the eye a path to follow.
const containerVariants = {
hidden: {},
visible: {
transition: {
staggerChildren: 0.06,
},
},
};
const itemVariants = {
hidden: { opacity: 0, y: 16 },
visible: {
opacity: 1,
y: 0,
transition: { type: "spring", damping: 25, stiffness: 250 },
},
};
<motion.ul variants={containerVariants} initial="hidden" animate="visible">
{items.map((item) => (
<motion.li key={item.id} variants={itemVariants}>
{item.name}
</motion.li>
))}
</motion.ul>For lists, use 50-60ms stagger. For grids, use 60-80ms. For section groups, use 100-150ms. AI tools default to either zero stagger or identical timing on everything.
3. Match duration to interaction type
Different interactions need different timing. Hover states should feel instant. Entrance animations can take longer because the user is waiting for content. Exit animations should be faster than entrances because the user has already decided to move on.
// Hover: fast and responsive (150-200ms equivalent)
<motion.button
whileHover={{ scale: 1.03, y: -2 }}
transition={{ type: "spring", damping: 20, stiffness: 300 }}
/>
// Entrance: smooth and readable (300-500ms equivalent)
<motion.div
initial={{ opacity: 0, y: 24 }}
animate={{ opacity: 1, y: 0 }}
transition={{ type: "spring", damping: 25, stiffness: 200 }}
/>
// Exit: quick departure (200-300ms equivalent)
<motion.div
exit={{ opacity: 0, y: -12 }}
transition={{ type: "spring", damping: 28, stiffness: 350 }}
/>AI tools apply one timing to everything. The result feels unnatural — hovers feel sluggish and exits feel slow.
4. Always animate transforms, not layout properties
Animating width, height, padding, or margin triggers layout recalculations and causes jank. Transforms (scale, translateX, translateY, rotate) are GPU-accelerated and render at 60fps.
// Bad: animating layout properties causes jank
<motion.div animate={{ width: "100%", height: 200 }} />
// Good: animating transforms is GPU-accelerated
<motion.div animate={{ scaleX: 1, scaleY: 1 }} />The exception is Framer Motion's layout prop, which handles layout animations by measuring positions and translating between them using transforms internally. Use layout when you need elements to animate to new positions in a list.
<motion.div layout transition={{ type: "spring", damping: 25, stiffness: 250 }}>
{content}
</motion.div>AI tools frequently animate height and width directly. Always specify transform-based animations in your prompts.
5. Add exit animations, not just entrances
Most AI-generated web animations only handle the entrance. Elements fade in but then vanish instantly when removed. This breaks the visual continuity of the interface. AnimatePresence handles exit animations in Framer Motion.
import { AnimatePresence, motion } from "framer-motion";
<AnimatePresence mode="wait">
{isVisible && (
<motion.div
key="panel"
initial={{ opacity: 0, scale: 0.96 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.96 }}
transition={{ type: "spring", damping: 25, stiffness: 300 }}
>
{content}
</motion.div>
)}
</AnimatePresence>The mode="wait" prop ensures the exiting element finishes before the entering element starts, preventing overlap. Without this, route transitions and modals feel broken.
6. Respect prefers-reduced-motion
Some users experience motion sickness or have vestibular disorders. The prefers-reduced-motion media query lets you serve a reduced-motion experience.
import { useReducedMotion } from "framer-motion";
function AnimatedCard({ children }) {
const shouldReduce = useReducedMotion();
return (
<motion.div
initial={shouldReduce ? { opacity: 0 } : { opacity: 0, y: 24 }}
animate={{ opacity: 1, y: 0 }}
transition={
shouldReduce
? { duration: 0.15 }
: { type: "spring", damping: 25, stiffness: 250 }
}
>
{children}
</motion.div>
);
}When reduced motion is preferred, collapse transform animations to simple opacity fades with short durations. AI tools almost never include this check — it needs to be explicitly requested.
7. Use consistent easing across your app
Mixing different easing functions across components creates a disjointed feel. Define a motion system with consistent spring parameters and reuse them everywhere.
// motion.config.ts — define once, use everywhere
export const springs = {
hover: { type: "spring", damping: 20, stiffness: 300 },
enter: { type: "spring", damping: 25, stiffness: 250 },
exit: { type: "spring", damping: 22, stiffness: 280 },
tap: { type: "spring", damping: 15, stiffness: 400 },
};
export const easings = {
default: [0.16, 1, 0.3, 1],
enter: [0, 0, 0.2, 1],
exit: [0.4, 0, 1, 1],
};
// Usage in components
<motion.div
whileHover={{ scale: 1.02 }}
transition={springs.hover}
/>AI tools generate different easing values for every component because each generation is independent. A shared config file solves this — include it in your prompt context so AI generates consistent values.
8. Let motion reinforce hierarchy
Primary actions deserve more motion than secondary elements. A submit button should have a more noticeable hover and tap response than a secondary link. Motion communicates importance.
// Primary action: pronounced motion
<motion.button
whileHover={{ scale: 1.04, y: -3 }}
whileTap={{ scale: 0.97 }}
transition={{ type: "spring", damping: 18, stiffness: 300 }}
className="bg-blue-600 text-white px-6 py-3 rounded-lg"
>
Submit
</motion.button>
// Secondary action: subtle motion
<motion.button
whileHover={{ scale: 1.01, y: -1 }}
whileTap={{ scale: 0.99 }}
transition={{ type: "spring", damping: 24, stiffness: 350 }}
className="text-gray-500 px-4 py-2"
>
Cancel
</motion.button>AI tools apply the same animation intensity to everything. Without hierarchy in motion, the interface feels flat even when the animations themselves are technically good.
The shortcut: motion prompts implement all 8
Each of these best practices requires specific knowledge and deliberate prompting. You could add detailed animation instructions to every AI prompt — specifying spring values, stagger timing, exit animations, reduced motion handling, and hierarchy rules for each component.
Or you could use motion prompts that encode all 8 practices into a single instruction file. Add the file as context in Cursor, Claude, or Lovable, and every component the AI generates follows these best practices automatically.
The result: detailed animations with consistent timing, proper physics, stagger patterns, exit handling, accessibility support, and motion hierarchy — without writing a single animation specification yourself.
For more on choosing between animation approaches, read Framer Motion vs CSS Animations in AI-Generated Code. If your AI-built app already feels lifeless, start with Why Your Vibe-Coded App Feels Static. For background on how prompt-based animation systems work, see What Are UI Motion Prompts?.
UI Motion Prompts — pre-built animation specifications that implement all 8 best practices in your AI coding workflow. Works with Cursor, Claude, Lovable, and v0.
More articles
How to Build a Smooth UI with AI Prompts
Practical guide on what makes a UI feel smooth (timing, easing, springs, reduced motion), why AI tools default to janky transitions, and how structured motion prompts solve this.
CSS Animations Are Dead — Here's What Replaced Them
Why CSS animations are declining (-40%) as AI coding tools take over, what's replacing them (Framer Motion via AI prompts), and why you should stop hand-coding @keyframes.