/arc:animate

Strategic motion

What it does

Animate reviews a component or page and identifies where motion would improve the experience — missing feedback on actions, jarring state changes, unclear spatial relationships, or functional-but-joyless interactions. It then adds motion systematically: entrance choreography, micro-interactions, state transitions. Uses motion/react for JS control or CSS transitions for simple cases. Always respects prefers-reduced-motion.

Why it exists

Most AI-generated UI is static. Buttons don't respond to press, elements appear instantly, state changes are jarring. But scattered animation everywhere is worse than none. Animate provides the strategic middle ground: purposeful motion that makes the interface feel crafted.

Design decisions

  • User-interactive, not agent-delegated. Animation is subjective and needs your approval.
  • One hero moment + systematic feedback layer. Not "animate everything."
  • motion/react for JS-controlled animation, CSS for simple transitions.
  • Always provides prefers-reduced-motion alternative.

Source document

Animate Workflow

Review a feature and add purposeful motion. One orchestrated experience beats scattered animations everywhere.

Announce at start: "I'm using the animate skill to add purposeful motion."

This skill is user-interactive. Do NOT spawn agents. Animation choices are subjective — the user must see and approve each addition.

Every animation must answer: "Why does this exist?"

One well-orchestrated experience beats scattered animations everywhere. Focus on high-impact moments.


Phase 0: Load References (MANDATORY)


Phase 1: Context

Ask the user using AskUserQuestion:

AskUserQuestion:
  question: "What's the personality of this feature?"
  header: "Motion tone"
  options:
    - label: "Snappy and confident"
      description: "Quick, decisive transitions. Enterprise, developer tools."
    - label: "Smooth and refined"
      description: "Gentle, polished. Premium, editorial."
    - label: "Playful and energetic"
      description: "Slightly exaggerated, delightful. Consumer, creative tools."
    - label: "Minimal and functional"
      description: "Almost invisible. Utility-focused, data-heavy."

Also determine:

  • Is this a page load, a component, or a user flow?
  • Performance budget constraints? (Mobile-first? Complex page?)
  • Existing motion patterns in the codebase?

Phase 2: Identify Animation Opportunities

Analyze the code and identify static areas:

Missing Feedback

  • Button clicks without visual acknowledgment
  • Form submissions with no loading state
  • Toggle switches that snap without transition
  • Actions with no confirmation animation

Jarring Transitions

  • Elements appearing/disappearing instantly (show/hide)
  • Page loads with no entrance choreography
  • Modal/drawer opens without transition
  • Tab switches with instant content swap

Unclear Relationships

  • Spatial hierarchy that isn't communicated
  • Parent-child relationships without visual connection
  • List items with no stagger to show order

Opportunities for Delight

  • Empty states that could benefit from subtle motion
  • Success moments (form submit, task complete)
  • First-time user moments

Phase 3: Plan Animation Strategy

Present a focused plan. Less is more.

## Animation Plan

### Hero Moment (1 max)
- **What**: [e.g., Page load entrance sequence]
- **How**: [e.g., Staggered fade-up of sections, 50ms delay]
- **Duration**: [e.g., 300-500ms total]

### Feedback Layer (per-interaction)
| Element | Trigger | Animation | Duration |
|---------|---------|-----------|----------|
| Buttons | Press | `active:scale-[0.97]` | CSS instant |
| Cards | Hover | `hover:shadow-lg hover:-translate-y-0.5` | 150ms |
| Toggle | Click | Slide + color transition | 200ms |

### Transition Layer (state changes)
| State Change | Animation | Duration |
|-------------|-----------|----------|
| Modal open | Fade + scale(0.98→1) + y(20→0) | 200ms ease-out |
| Dropdown | Scale(0.95→1) + opacity | 150ms ease-out |
| Accordion | grid-rows 0fr→1fr | 300ms ease-out |

### Reduced Motion Alternatives
| Full Animation | Reduced Motion |
|---------------|---------------|
| Slide + fade | Fade only (200ms) |
| Scale + move | Opacity only |
| Staggered entrance | Simultaneous fade |

Ask using AskUserQuestion:

AskUserQuestion:
  question: "Does this animation plan feel right?"
  header: "Animation plan"
  options:
    - label: "Looks good"
      description: "Proceed with implementation"
    - label: "Adjust"
      description: "I have changes to suggest"
    - label: "Too much"
      description: "Scale it back — fewer animations"
    - label: "Too little"
      description: "Add more — I want more motion"

Phase 4: Implement

Timing & Easing Quick Reference

PurposeDurationEasing
Instant feedback (press, toggle)100-150ms— (CSS transitions)
State changes (hover, menu)150-200msease-out
Layout changes (modal, accordion)200-300mscubic-bezier(0.25, 1, 0.5, 1)
Entrance animations300-500mscubic-bezier(0.16, 1, 0.3, 1)
Exit = 75% of entranceease-in

CSS-Only (simple transitions)

<!-- Button press feedback -->
<button class="transition-transform active:scale-[0.97]">

<!-- Card hover lift -->
<div class="transition-all duration-150 hover:shadow-lg hover:-translate-y-0.5">

<!-- Dropdown -->
<div class="transition-all duration-150 ease-out origin-top
            data-[state=open]:scale-100 data-[state=open]:opacity-100
            data-[state=closed]:scale-95 data-[state=closed]:opacity-0">

motion/react (JS-controlled animation)

// Modal entrance
<motion.div
  initial={{ opacity: 0, y: 20, scale: 0.98 }}
  animate={{ opacity: 1, y: 0, scale: 1 }}
  exit={{ opacity: 0, y: 10, scale: 0.98 }}
  transition={{ duration: 0.2, ease: [0.25, 1, 0.5, 1] }}
/>

// Staggered list
<motion.div variants={{ visible: { transition: { staggerChildren: 0.03 } } }}>
  {items.map(item => (
    <motion.div
      key={item.id}
      variants={{ hidden: { opacity: 0, y: 8 }, visible: { opacity: 1, y: 0 } }}
    />
  ))}
</motion.div>

// Interactive spring
<motion.button whileTap={{ scale: 0.97 }} transition={{ type: "spring", stiffness: 400, damping: 25 }}>

Accordion (height animation without animating height)

.accordion-content {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 300ms cubic-bezier(0.25, 1, 0.5, 1);
}
.accordion-content[data-state="open"] {
  grid-template-rows: 1fr;
}
.accordion-content > div {
  overflow: hidden;
}

Reduced Motion (MANDATORY)

const shouldReduce = useReducedMotion();

<motion.div
  animate={{
    opacity: 1,
    y: shouldReduce ? 0 : 20,
  }}
/>
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

Phase 5: Verify

After implementing:

  1. Screenshot desktop and mobile via Chrome MCP (if available)
  2. Test interactions — do animations feel natural?
  3. Test reduced motion — toggle in browser devtools
  4. Check performance — no jank, 60fps on target devices
  5. Check timing — not too fast (jarring) or too slow (laggy)

NEVER

  • Use bounce or elastic easing — they feel dated and amateurish
  • Animate layout properties (width, height, top, left) — use transform
  • Use durations over 500ms for UI feedback — feels laggy
  • Animate without purpose — "delight" is not a purpose
  • Ignore prefers-reduced-motion — accessibility violation
  • Animate everything — animation fatigue is real
  • Block interaction during animations unless intentional