← Back to blog
·7 min read

UI Animation Best Practices for AI-Generated Apps (2026)

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
Get the prompts →

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.