Agent skill
ui-animation
Guide tasteful UI animation with easing, springs, layout animations, gestures, and accessibility. Covers Tailwind and Motion patterns. Use when: (1) Implementing enter/exit animations, (2) Choosing easing curves, (3) Configuring springs, (4) Layout animations and shared elements, (5) Drag/swipe gestures, (6) Micro-interactions, (7) Ensuring prefers-reduced-motion accessibility. Triggers: animate, animation, easing, spring, transition, motion, layout, gesture, drag, swipe, reduced motion, framer motion.
Install this agent skill to your Project
npx add-skill https://github.com/LukasStrickler/ai-dev-atelier/tree/main/content/skills/ui-animation
Metadata
Additional technical details for this skill
- author
- ai-dev-atelier
- version
- 1.0
SKILL.md
UI Animation
Tasteful UI animation with proper timing, accessibility, and performance.
Quick Start
Technique Decision:
- Simple transition? → CSS/Tailwind (
transition-*,animate-*) - Enter/exit with unmount? → Motion +
AnimatePresence - Gesture-driven? → Motion springs
- Layout changes? → Motion
layoutprop
Default: Start with Easing Decision Tree → Duration Guidelines → Implement → Add a11y → Verify
Core Principles
- Natural Motion: Mimic physics. Avoid linear easing - nothing moves at constant speed.
- Purposeful: Every animation must add meaning. If you can't explain its benefit, remove it.
- Fast: UI animations under 300ms. Hover effects under 150ms. Over 500ms feels sluggish.
- Interruptible: Use springs for gesture-driven animations - they handle interruption gracefully.
- Accessible: Always respect
prefers-reduced-motion. Non-negotiable.
Workflow
Step 1: Classify Animation Type
| Type | Examples | Technique |
|---|---|---|
| Micro-interaction | Button press, toggle, checkbox | CSS/Tailwind |
| Enter/Exit | Modal, toast, dropdown | Motion + AnimatePresence |
| Layout change | Accordion, reorder, expand | Motion layout prop |
| Shared element | Tab indicator, card expand | Motion layoutId |
| Gesture | Drag, swipe, pull-to-refresh | Motion springs |
Step 2: Choose Timing
- Use Easing Decision Tree below to select curve
- Use Duration Guidelines to select timing
- For gestures, use Spring Animations config
Step 3: Implement
- Check
references/recipes.mdfor copy-paste patterns - Apply timing from Step 2
- Wrap unmounting elements in
AnimatePresence
Use ui_to_artifact when starting from a design screenshot or mockup. Use ui_diff_check to compare expected vs implemented UI.
Step 4: Accessibility (Required)
- Check
prefers-reduced-motionwithuseReducedMotion()ormotion-safe: - Simplify to opacity-only for reduced motion users
- Verify focus timing (move focus AFTER animation starts)
Step 5: Verify
- Exit animations run (not instant unmount)
-
opacity: 0elements havepointerEvents: none - Focus moves after animation starts, not before
- Only animating
transformandopacity
Easing
Decision Tree
What triggers the animation?
│
├─ User action (click, tap, open)?
│ └─ Use: ease-out (fast start, slow end = responsive)
│
├─ Element moving on-screen (tab switch, reorder)?
│ └─ Use: ease-in-out (accelerate then decelerate)
│
├─ Continuous/looping (spinner, marquee)?
│ └─ Use: linear (constant speed appropriate here)
│
├─ Gesture-based (drag, swipe, pull)?
│ └─ Use: Spring animation (physics-based, interruptible)
│
└─ Hover/focus effect?
└─ Use: CSS ease, 150ms (subtle, immediate)
Quick Reference
| Purpose | CSS | Tailwind | Duration |
|---|---|---|---|
| Modal/drawer enter | cubic-bezier(0.32, 0.72, 0, 1) |
ease-out duration-200 |
200ms |
| Modal/drawer exit | cubic-bezier(0.32, 0.72, 0, 1) |
ease-out duration-150 |
150ms |
| On-screen movement | cubic-bezier(0.4, 0, 0.2, 1) |
ease-in-out duration-200 |
200-300ms |
| Hover effect | ease |
ease duration-150 |
150ms |
| Button press | — | active:scale-[0.97] |
instant |
Pro Curves
| Name | Value | Use Case |
|---|---|---|
| Vaul (buttery) | cubic-bezier(0.32, 0.72, 0, 1) |
Sheets, drawers, modals |
| Emphasized | cubic-bezier(0.2, 0, 0, 1) |
Material Design 3 |
| Snappy | cubic-bezier(0.25, 1, 0.5, 1) |
Fast UI transitions |
Avoid: Built-in ease-in—starts slow, feels sluggish.
Duration Guidelines
| Type | Duration | Notes |
|---|---|---|
| Micro-feedback | 100-150ms | Button press, toggle, checkbox |
| Small transition | 150-250ms | Tooltip, icon morph |
| Medium transition | 200-300ms | Modal, popover, dropdown |
| Large transition | 300-400ms | Page transition, complex layout |
| Maximum | <500ms | Exceptions: onboarding, data viz |
Key Rules:
- Exit faster than enter: 200ms enter → 150ms exit
- Hover = fast: Under 150ms
- High-frequency = instant: Keyboard nav, scrolling—<100ms or none
Spring Animations
Duration-based (Recommended)
Easier to compose with other timed animations. Use visualDuration (time to visually reach target) and bounce (0 = no bounce, 1 = very bouncy).
| Feel | Config | Use Case |
|---|---|---|
| Snappy | { duration: 0.3, bounce: 0.15 } |
Tabs, buttons, quick feedback |
| Standard | { duration: 0.4, bounce: 0.2 } |
Modals, menus, general UI |
| Gentle | { duration: 0.5, bounce: 0.25 } |
Smooth, human-like flow |
Physics-based (Legacy/Advanced)
Use when integrating with physics libraries or when precise control over spring dynamics is needed.
| Feel | Config | Use Case |
|---|---|---|
| Snappy | { stiffness: 400, damping: 30 } |
High-frequency interactions |
| Standard | { stiffness: 300, damping: 20 } |
Framer Handshake convention |
| Gentle | { stiffness: 120, damping: 14 } |
react-motion preset |
Gotcha:
stiffness/damping/massoverridesduration/bounce. Pick one approach—don't mix.
Layout Animations
The layout Prop
Add layout to animate position/size changes automatically. Use layout="position" for text (prevents distortion).
| Prop Value | Effect | Use Case |
|---|---|---|
layout={true} |
Animates position AND size | Default for flexible elements |
layout="position" |
Animates only translation | Text/icons that shouldn't stretch |
layout="size" |
Animates only dimensions | Fixed-position expanding panels |
Shared Element Transitions (layoutId)
Elements with matching layoutId animate between each other when entering/exiting.
Critical Trap: Duplicate layoutId values cause elements to teleport across the page. Use unique IDs per context or wrap in <LayoutGroup id="...">.
Layout Gotchas
- Text distortion: Apply
layout="position"to text elements - Border radius: Can warp during scale—Motion auto-corrects, but test it
- SVG elements:
layoutdoesn't work on<path>—use manual morphing
Gesture Gotchas
| Problem | Solution |
|---|---|
| Touch scroll conflicts | dragPropagation={false} |
| Element snaps back | Check dragConstraints + dragElastic |
| Momentum feels wrong | dragMomentum={false} for precise UIs |
| One-direction only | dragElastic={{ top: 0, bottom: 0.5 }} |
Swipe dismiss: Check BOTH distance AND velocity—users expect flicks to work.
Accessibility
prefers-reduced-motion (REQUIRED)
import { useReducedMotion } from "motion/react"
const shouldReduce = useReducedMotion()
const variants = shouldReduce
? { opacity: 1 } // Fade only
: { opacity: 1, scale: 1, y: 0 } // Full animation
Tailwind: motion-safe:animate-pulse / motion-reduce:transition-none
Best practice: Don't disable—simplify. Remove spatial movement, keep opacity.
Focus Management
- Move focus AFTER animation starts:
requestAnimationFrame(() => ref.focus()) - Restore focus to trigger on close
- Don't animate inside
aria-liveregions
Touch Targets
| Standard | Size | Tailwind | Physical |
|---|---|---|---|
| Material Design | 48×48 dp | min-h-12 min-w-12 |
~9mm (recommended) |
| Apple HIG | 44×44 pt | min-h-11 min-w-11 |
~7mm |
| WCAG 2.2 (AA) | 24×24 px | min-h-6 min-w-6 |
~5mm (minimum) |
Why? Average adult finger pad is ~9mm. Targets below 7mm cause "fat finger" errors. Use Material's 48dp for cross-platform; Apple's 44pt is iOS-specific minimum.
Performance
Golden Rules
- Only animate
transformandopacity—GPU-accelerated - Never animate:
width,height,top,left,margin,padding will-changesparingly—only during animation, remove after- Blur thresholds:
- ≤10px: Safe for animation
- 11-20px: May cause jank on mobile/4K—test thoroughly
-
20px: Avoid for real-time effects; use pre-blurred images instead
- Prefer CSS over JS for simple transitions
Key Traps
- Height animation: Use
layoutprop, notanimate={{ height }} - Invisible but clickable:
opacity: 0still receives clicks—addpointerEvents: "none" - will-change everywhere: Causes layer explosion, mobile crashes
See references/recipes.md for detailed examples.
Examples
Copy-paste patterns organized by category in references/recipes.md:
- Common UI Patterns: Button press, modal enter/exit, error shake, staggered lists, accordion
- Touch & Interaction: Accessible touch targets, hover on touch devices, instant tooltips
- Layout Animations:
layoutprop,layoutIdshared elements, collision fixes - Radix UI Integration:
forceMountpattern,asChild, origin-aware popovers - Accessibility: Focus timing, focus restoration, reduced motion variants
- Performance: Height animation (use
layout), invisible-but-clickable fix,will-change - Exit Patterns:
popLayoutwithforwardRef, SSR hydration (initial={false}) - Gestures: Swipe dismiss with velocity check, elastic drag boundaries
AnimatePresence
| Mode | Behavior | Use Case |
|---|---|---|
sync (default) |
Simultaneous enter/exit | Crossfades, overlays |
wait |
Exit completes before enter | Page transitions, tabs |
popLayout |
Exiting elements leave flow | List removals (with layout) |
Exit Animation Trap
Exit animations require AnimatePresence—without it, unmount is instant:
// ❌ Exit never runs
{isOpen && <motion.div exit={{ opacity: 0 }}>...</motion.div>}
// ✅ Wrap in AnimatePresence
<AnimatePresence>
{isOpen && <motion.div exit={{ opacity: 0 }}>...</motion.div>}
</AnimatePresence>
SSR: Use <AnimatePresence initial={false}> to prevent animation on page load.
Anti-patterns
| Don't | Do Instead | Why |
|---|---|---|
scale(0) start |
scale(0.9) or higher |
Avoids "popping" effect |
linear for UI |
ease-out or springs |
Linear feels robotic |
| Animations >500ms | Keep under 300ms | Feels sluggish |
| Same tooltip delay | First: 400ms, subsequent: 0ms | User mental model |
| Skip reduced-motion | Always motion-safe: |
Accessibility |
| Animate layout props | Use transform: scale() |
Performance |
| Excessive bounce | bounce: 0-0.2 |
Unprofessional |
tailwindcss-animate
Tailwind v4: Define keyframes via
@themein CSS, not config.
| Category | Classes |
|---|---|
| Enter | animate-in fade-in zoom-in-95 slide-in-from-top |
| Exit | animate-out fade-out zoom-out-95 slide-out-to-top |
| Timing | delay-150 duration-500 |
| Fill Mode | fill-mode-forwards fill-mode-backwards |
Integration with Other Skills
| When | Skill | Why |
|---|---|---|
| After implementing | code-quality |
Ensure code passes checks |
| Reusable patterns | docs-write |
Document component API |
| Before committing | git-commit |
Use feat(ui): or style: |
| Integration issues | search |
Look up latest patterns |
Output
- Artifacts: Code changes only (no
.ada/outputs) - Modifications: Component animations, CSS/Tailwind styles, Motion configs
- Type: Workflow skill (guidance only, no scripts)
References
Internal
references/recipes.md- Copy-paste patterns, integration examples, detailed traps
External
- Motion Documentation
- Material Design 3 Motion
- Apple HIG - Motion
- tailwindcss-animate
- easings.net - Easing function cheat sheet
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
resolve-pr-comments
Resolve bot review comments (CodeRabbit, Copilot, Gemini) on GitHub PRs using subagents. Use when: (1) User asks to 'review PR comments' or 'resolve PR comments', (2) User says 'work through PR N comments' or 'handle bot comments', (3) Need to triage CodeRabbit/Copilot/Gemini review comments, (4) Processing PR feedback at scale, (5) Want to see what's already fixed vs still pending. NOT for: creating PRs, reviewing code yourself, writing new reviews. Triggers: review PR comments, resolve PR comments, work through PR comments, handle bot comments, process CodeRabbit comments, triage PR feedback, fix PR review issues, resolve bot comments, pr comment resolver.
tdd
Strict Red-Green-Refactor workflow for robust, self-documenting code. Discovers project test setup via codebase exploration before assuming frameworks. Use when: (1) Implementing new features with test-first approach, (2) Fixing bugs with reproduction tests, (3) Refactoring existing code with test safety net, (4) Adding tests to legacy code, (5) Ensuring code quality before committing, (6) When tests exist but workflow unclear, or (7) When establishing testing practices in a new project. Triggers: test, tdd, red-green-refactor, failing test, test first, test-driven, write tests, add tests, run tests.
use-graphite
Manage stacked PRs with Graphite CLI (gt) instead of git push/gh pr create. Auto-detects Graphite repos and blocks conflicting commands with helpful alternatives. Use when: (1) About to run git push or gh pr create in a Graphite repo, (2) Creating a new branch for a feature, (3) Submitting code for review, (4) Large changes that should be split into reviewable chunks, (5) Hook blocks your git command and suggests gt equivalent. NOT for: repos not initialized with Graphite, git add/commit/status/log. Triggers: git push blocked, gh pr create blocked, create branch, submit PR, stacked PRs, split large PR, gt create, gt submit, graphite workflow.
git-commit
Write clear git commits with Conventional Commits format. Detects project conventions from history and config. Guides commit granularity. Use when: (1) Completing working code, (2) Code builds and tests pass, (3) Ready to save, (4) Before pushing, (5) After review feedback. Triggers: automatically when finishing commitable work that builds and passes tests.
code-review
Review code changes using CodeRabbit CLI - supports uncommitted files (task mode) or all PR files vs main branch (pr mode). Catches bugs, security issues, and code quality problems before committing or when reviewing pull requests. Use when: (1) Reviewing uncommitted changes before committing (task mode), (2) Reviewing all changed files in a PR against main branch (pr mode), (3) Working on subtasks and want to check progress, (4) Need feedback on work-in-progress code, (5) Preparing PR for merge, (6) When CodeRabbit review is needed, (7) For bug detection and security scanning, or (8) For automated code quality assessment. Triggers: review code, check code quality, review changes, code review, review PR, check for bugs, security scan, review uncommitted, finalize code, pre-commit review.
image-generation
Generate, edit, and upscale AI images. Use when creating visual assets for apps, websites, or documentation. FREE Cloudflare tier for iterate generation (~96/day), Fal.ai for paid tiers. Four quality tiers (iterate/default/premium/max). Supports text specialists, multi-ref editing, SVG, background removal. Triggers: generate image, create image, edit image, upscale, logo, picture of, remove background.
Didn't find tool you were looking for?