App animation & motion
Motion design for apps and websites: what motion is for, Disney's principles adapted to interfaces, easing curves and springs, durations, choreography, shared-element and view transitions, loading, gestures, performance and reduced motion, plus the implementation vocabulary (CSS, the Web Animations API, View Transitions, Motion, GSAP, Lottie, Rive, iOS and Android springs) you need to ask for motion specs and code. It applies the general method in prompting fundamentals. The code side is in CSS animation and Web Animations API; screens and states are in UI/UX; motion graphics for video are in video editing.
How experts think
| Novice asks for | Expert thinks about |
|---|---|
| "make it animate nicely" | what the motion explains: where something came from, what changed, that an action worked |
| a long, smooth animation | short: most interface motion takes 100–500 ms, and repeated actions should feel instant |
| one easing for everything | entering things decelerate, leaving things accelerate, on-screen moves ease in and out |
| a separate animation per screen | a motion system: duration, easing and spring tokens shared by the whole app |
| everything moving at once | choreography: one focal movement, related things moving together, staggers in reading order |
| an animation that plays to the end | interruptible motion that responds when the user acts mid-animation |
| a fancy effect | performance: animating only cheap properties, holding the frame rate on a low-end phone |
| motion for everyone | a reduced-motion version for people who get dizzy or nauseous from movement |
| "just build it" | a spec engineers can implement: property, from, to, duration, delay, easing, trigger |
Two consequences for prompting. First, describe the state change and its purpose, not the animation: "the card expands into the detail view so users keep their place" lets the model choose a container transform; "make it slide" does not. Second, ask for numbers: durations in milliseconds, cubic-bézier values or spring parameters, and a reduced-motion fallback. Words like "snappy" and "smooth" mean different things to everyone.
Vocabulary
| Term | What it means | Say it like |
|---|---|---|
| Micro-interaction | a small, single-purpose animated response: a toggle, a like, a press | "A micro-interaction for the favorite button: press, fill, settle" |
| Transition | the animated change between two states or screens | "Spec the transition from list to detail" |
| Enter / exit | an element appearing / disappearing | "Enter from the bottom, exit faster than it entered" |
| Easing | how speed changes over an animation's duration | "Name the easing for each movement" |
| Ease-out (decelerate) | starts fast, slows to a stop: feels responsive | "Entering elements decelerate" |
| Ease-in (accelerate) | starts slow, speeds up: things leaving for good | "Dismissed toasts accelerate off-screen" |
| Ease-in-out (standard) | slow, fast, slow: moving between two on-screen positions | "Reordering uses standard easing" |
| Linear | constant speed; looks mechanical for movement | "Linear only for spinners and progress" |
| Cubic bézier | a curve defined by four numbers, cubic-bezier(x1, y1, x2, y2); y outside 0–1 overshoots | "Give the exact cubic-bezier values" |
| Keyframe | a defined value at a point in time; the system fills in between | "Keyframes at 0%, 60% and 100%" |
| Interpolation (tweening) | computing the in-between frames | "Interpolate color in OKLCH, not RGB" |
| Duration / delay | how long it runs / how long before it starts | "250 ms duration, no delay on the lead element" |
| Spring | motion simulated as a mass on a spring instead of a fixed curve and duration | "Use a spring so drags can hand off velocity" |
| Stiffness | how strongly the spring pulls toward its target; higher is snappier | "Higher stiffness, same damping" |
| Damping / damping ratio | resistance that stops oscillation / damping relative to the amount that just prevents overshoot | "Damping ratio 0.8: a hint of overshoot, no wobble" |
| Mass | the simulated weight; higher feels heavier and slower | "Keep mass at 1; tune stiffness and damping" |
| Overshoot / bounce | passing the target and settling back | "No overshoot on opacity or color" |
| Velocity | speed carried into an animation, usually from a gesture | "Start the settle animation with the fling's velocity" |
| Interruptible / retargeting | can change direction mid-flight from its current position and speed | "If the user taps again mid-close, it reverses from where it is" |
| Stagger | offsetting the start of several similar animations | "Stagger list items 30 ms apart, max 8 items" |
| Choreography | the order and relationship of several movements in one transition | "Choreograph: container first, then content fades in" |
| Shared-element (hero) transition | one element visibly travels from the old screen into the new one | "The thumbnail becomes the header image" |
| Container transform | a Material pattern: a container (card, button) grows into the next screen | "Use a container transform from the card to the detail page" |
| Shared axis | a Material pattern: screens move together along x, y or z to show a spatial relationship | "Shared x-axis for steps in the wizard" |
| Fade through | a Material pattern: the old screen fades out, then the new one fades and scales in, for unrelated screens | "Fade through between bottom-nav tabs" |
| Crossfade | the old and new fade simultaneously | "Crossfade the images; don't slide them" |
| FLIP | First, Last, Invert, Play: animate a layout change with transforms | "Animate the reorder with FLIP, not by animating top/left" |
| View transition | a browser API that snapshots the old and new page states and animates between them | "Use the View Transitions API for route changes" |
| Skeleton screen | gray placeholder shapes shaped like the content that is loading | "Skeleton with a subtle shimmer; static under reduced motion" |
| Determinate / indeterminate progress | shows how much is done / shows only that work is happening | "Determinate progress for uploads" |
| Optimistic UI | showing the result before the server confirms, rolling back on failure | "Optimistic like with a rollback animation" |
| Direct manipulation | the element tracks the finger or pointer exactly | "The sheet follows the finger 1:1 while dragging" |
| Rubber banding | resistance and spring-back when dragging past a boundary | "Rubber-band past the top, then spring back" |
| Snap points | positions a dragged element settles to | "Sheet snap points at 30%, 60% and 100% height" |
| Parallax | layers moving at different speeds as you scroll | "No parallax: it's a vestibular trigger" |
| Scroll-driven animation | animation progress tied to scroll position rather than time | "Reading progress bar driven by scroll" |
| Frame rate / frame budget | frames per second / time available per frame (16.7 ms at 60 Hz, 8.3 ms at 120 Hz) | "Must hold 60 fps on a mid-range Android phone" |
| Jank | visible stutter from dropped frames | "Find what causes jank in this animation" |
| Compositor | the stage of rendering that combines layers; transform and opacity can animate there cheaply | "Only animate properties the compositor can handle" |
| Reduced motion | an OS setting asking apps to minimize movement | "Specify the reduced-motion version of every animation" |
| Vestibular disorder | an inner-ear balance condition; screen motion can cause dizziness and nausea | "Treat large slides and zooms as vestibular risks" |
| Motion tokens | named, shared values for durations, easings and springs | "Use duration-medium-2 and easing-standard-decelerate tokens" |
| Motion spec | a table of what animates, from what to what, how long and with which easing | "Write a motion spec table I can hand to engineers" |
| Storyboard / animatic | key frames with timings, drawn or described / a rough timed preview | "Storyboard the transition frame by frame with timestamps" |
Purpose of motion
| Purpose | What it explains | Example | If it's missing |
|---|---|---|---|
| Feedback | "your action registered" | a button compresses on press; a checkmark draws in | users tap twice |
| Orientation | where things live in space | a drawer slides from the edge it lives on | users don't know how to get back |
| Continuity | this is the same object, changed | a thumbnail grows into the full image | context is lost on every screen change |
| Hierarchy and attention | what to look at first | the new item highlights; items enter in reading order | the change goes unnoticed |
| Status | the system is working, and how far along | skeletons, spinners, progress bars | the app looks frozen |
| Delight and brand | personality | a celebration on the first completed task | fine, as long as it's rare and quick |
If an animation serves none of these, cut it or make it near-instant.
Principles
Disney animators Frank Thomas and Ollie Johnston set out the 12 principles of animation in The Illusion of Life: Disney Animation (1981). Most translate to interfaces in a quieter form.
| Principle | In film | In UI | Say it like |
|---|---|---|---|
| Squash and stretch | shapes deform to show weight and speed | a slight scale on press (about 0.97), not rubbery | "Press scales to 0.97 and springs back" |
| Anticipation | a small move before the main action | a hover or press state before the result; a slight dip before a sheet rises | "Add a hover state that hints the card is clickable" |
| Staging | present one idea clearly | dim the background behind a dialog; one thing moves at a time | "Scrim fades in under the dialog" |
| Straight ahead and pose to pose | drawing frame by frame vs key poses first | keyframes vs physics; most UI is pose to pose | "Define start and end poses; let the spring do the rest" |
| Follow-through and overlapping action | parts keep moving after the body stops | content settles a beat after its container; a small overshoot | "Content fades in 50 ms after the container lands" |
| Slow in and slow out | acceleration and deceleration | easing; never linear for movement | "Decelerate on enter" |
| Arcs | natural motion follows curves | a floating action button travels in a slight arc as it expands | "Move along an arc, not a straight diagonal" |
| Secondary action | a smaller action supports the main one | an icon rotates as its menu opens | "Chevron rotates 180° as the panel expands" |
| Timing | the number of frames sets weight and mood | durations scale with size and distance | "Small 150 ms, panels 300 ms, screens 400 ms" |
| Exaggeration | push beyond realism for clarity | keep it small; error shakes are the exception | "A 3-cycle horizontal shake on wrong password" |
| Solid drawing | volume and perspective | consistent light, depth and elevation; shadows grow as things lift | "Shadow deepens as the card lifts" |
| Appeal | characters people want to watch | coherent tokens make motion feel like one product | "Same easing family everywhere" |
Easing and springs
Easing curves
Material Design 3's easing tokens (values as published in Google's Jetpack Compose Material 3 library) are a good default set:
| Token (Material 3) | cubic-bezier | Use for |
|---|---|---|
| Standard | (0.2, 0, 0, 1) | moves that start and end on screen |
| Standard decelerate | (0, 0, 0, 1) | elements entering |
| Standard accelerate | (0.3, 0, 1, 1) | elements leaving |
| Emphasized decelerate | (0.05, 0.7, 0.1, 1) | expressive, larger entrances |
| Emphasized accelerate | (0.3, 0, 0.8, 0.15) | expressive exits |
| Legacy (Material 2) standard | (0.4, 0, 0.2, 1) | still common in older systems |
| Linear | linear | spinners, progress, color loops, scroll-linked motion |
CSS's built-in keywords (ease, ease-in, ease-out, ease-in-out) are milder versions of the same shapes. A
cubic bézier's four numbers are the two control points of a curve from (0, 0) to (1, 1); a y value above 1 makes the
motion overshoot. The curves are plotted in CSS animation.
Springs
A spring has no fixed duration: it runs until it settles, can start with a gesture's velocity, and retargets smoothly when interrupted. That is why native platforms use springs for most motion.
| Parameter | What it does | Raising it |
|---|---|---|
| Stiffness (tension) | the pull toward the target | faster, snappier |
| Damping (friction) | resistance to movement | less bounce, slower settle if very high |
| Mass | simulated weight | slower and heavier |
| Damping ratio | damping ÷ critical damping: below 1 overshoots (underdamped), 1 is the fastest settle without overshoot (critically damped), above 1 creeps in (overdamped) | less bounce |
| Initial velocity | speed at the start, usually from a fling | a livelier start |
| Perceptual duration and bounce | a designer-friendly way to set a spring: how long it feels and how bouncy (0 = none) | — |
Material 3's newer spring tokens (as published in the Jetpack Compose Material 3 library) show what "standard" and "expressive" mean in numbers. Spatial springs move position, size, rotation and shape; effects springs change color and opacity and never overshoot.
| Scheme | Spring | Damping ratio | Stiffness |
|---|---|---|---|
| Standard | fast / default / slow spatial | 0.9 | 1400 / 700 / 300 |
| Expressive | fast spatial | 0.6 | 800 |
| Expressive | default / slow spatial | 0.8 | 380 / 200 |
| Both | fast / default / slow effects | 1.0 | 3800 / 1600 / 800 |
To move a spring between libraries, match the damping ratio and stiffness (with mass 1), then check it by eye: libraries differ in units and defaults.
Durations
| Motion | Typical duration | Source and notes |
|---|---|---|
| Press, toggle, checkbox, color change | ≈ 50–150 ms | NN/g suggests about 100 ms for simple feedback; Material 3 short1–short3 |
| Small change: tooltip, chip, icon swap | ≈ 150–200 ms | Material 3 short3–short4 |
| Menu, dropdown, popover | ≈ 150–250 ms | exit faster than entry |
| Dialog, sheet, expanding card | ≈ 250–400 ms | NN/g: 200–300 ms for substantial changes; Material 3 medium1–medium4 |
| Full-screen or route transition | ≈ 300–500 ms | NN/g puts about 400 ms as the upper limit for large moves on big screens; Material 3 long1–long2 |
| Expressive: onboarding, celebration | ≈ 500–1000 ms, rarely | Material 3 extra-long tokens |
| Stagger step | ≈ 20–50 ms per item | cap the whole sequence at about 300 ms |
| Enter vs exit | exits shorter | Material's older guidance: about 225 ms in, 195 ms out on mobile; NN/g's example is 300 ms in, 200–250 ms out |
| Device size | larger screens, longer moves | Material's older guidance: tablets about 30% longer than phones, wearables about 30% shorter |
| Material 3 duration tokens | 1 | 2 | 3 | 4 |
|---|---|---|---|---|
| short | 50 | 100 | 150 | 200 |
| medium | 250 | 300 | 350 | 400 |
| long | 450 | 500 | 550 | 600 |
| extra-long | 700 | 800 | 900 | 1000 |
Values in milliseconds. NN/g's main warning: animations are far more often too long than too short.
Choreography and transitions
| Technique | Rule | Say it like |
|---|---|---|
| Lead element | one thing moves first and most; others follow | "The card leads; text and buttons follow 50 ms later" |
| Stagger | in reading order, small steps, capped | "Stagger 30 ms, max 6 items, then the rest appear together" |
| Same direction | related elements move the same way | "Everything in the sheet moves up together" |
| Spatial logic | forward moves one way, back the other | "Push left on forward navigation, right on back" |
| Enter and exit | enter decelerating from where it lives; exit accelerating, faster, toward where it goes | "Toast enters from the bottom in 250 ms, exits down in 200 ms" |
| Container transform | a container grows into the next view | "Card to detail: container transform, 400 ms, emphasized" |
| Shared axis | screens slide or scale together on x, y or z | "Wizard steps: shared x-axis" |
| Fade through | unrelated screens: out, then in with a slight scale | "Tabs: fade through, 300 ms" |
| Shared element | one element visibly travels between screens | "Avatar moves from list row to profile header" |
Shared-element APIs by platform: on the web, the View Transitions API (view-transition-name; same-document
transitions are Baseline 2025, cross-document ones are in Chromium and Safari) and Motion's layoutId; in SwiftUI,
matchedGeometryEffect, and from iOS 18 the zoom navigation transition; in Jetpack Compose, SharedTransitionLayout
with Modifier.sharedElement.
1/4
0 ms: the user taps a card. Its image and title exist on both screens, so each pair gets the same name (a view-transition-name, or Motion's layoutId). Focus moves now, not when the motion ends.
Loading, gestures and physics
Loading
| Wait | Show | Source |
|---|---|---|
| under about 1 s | nothing; a spinner would only flicker | NN/g |
| about 2–10 s, one module | a spinner in that module | NN/g |
| full-page load under about 10 s | a skeleton screen shaped like the content | NN/g |
| over about 10 s | a determinate progress bar with a time estimate | NN/g |
| likely-success actions | optimistic UI with a rollback animation on failure | — |
Skeleton shimmer is itself motion: keep it slow and subtle, and make it static under reduced motion.
Gestures
| Behavior | Rule |
|---|---|
| Direct manipulation | during a drag the element tracks the finger exactly; no easing |
| Velocity handoff | on release, a spring starts with the finger's speed, so a fling carries on naturally |
| Thresholds | release past a distance or speed threshold commits the action; otherwise it springs back |
| Snap points | settle to defined positions, chosen by position plus velocity |
| Rubber banding | increasing resistance past a boundary, then spring back |
| Interruptible | a new touch catches the element mid-animation instead of waiting |
| Alternatives | every drag needs a tap or button alternative (WCAG 2.5.7) |
Performance
| Rule | Why |
|---|---|
Animate transform (translate, scale, rotate) and opacity | browsers can run them on the compositor, often on the GPU, without layout or paint |
Avoid animating width, height, top, left, margin | they trigger layout every frame; use FLIP or a transform |
Be careful with filter, backdrop-filter, large blurs and shadows | expensive per pixel; fade a pre-rendered shadow layer instead |
| Respect the frame budget | 16.7 ms per frame at 60 Hz, 8.3 ms at 120 Hz; less is available in practice |
Use will-change sparingly | it promotes a layer, which costs memory; add it just before animating |
| Keep Lottie files light | complex After Effects compositions become heavy JSON; test on low-end devices |
| Measure | browser DevTools Performance panel; Xcode Instruments on iOS; Android's GPU rendering profiler and Perfetto |
The rendering pipeline behind these rules is covered in CSS animation.
Reduced motion
A vestibular disorder affects the inner-ear balance system; for people who have one, on-screen motion can cause dizziness, nausea and trouble concentrating. Parallax, large slides, zooms, spinning and anything that moves on its own are the usual triggers.
| Platform | User setting | How code reads it |
|---|---|---|
| Web | OS reduce-motion settings (macOS, iOS, Windows animation effects, Android) | CSS @media (prefers-reduced-motion: reduce); matchMedia(); Motion's MotionConfig reducedMotion="user"; GSAP's gsap.matchMedia() |
| iOS | Settings → Accessibility → Motion → Reduce Motion | UIAccessibility.isReduceMotionEnabled; SwiftUI's accessibilityReduceMotion environment value |
| Android | Accessibility → Remove animations (wording varies by version) | sets the system animation scale to zero; check custom animations honor it (ValueAnimator.areAnimatorsEnabled()) |
What a reduced-motion version should do: replace movement with a short crossfade or an instant change; remove parallax, zooms, large slides, autoplay and scroll-jacking; keep small changes that carry meaning (color, opacity, a progress bar); keep feedback. WCAG requirements: anything that moves on its own for more than 5 seconds needs pause, stop or hide (2.2.2, A); nothing flashes more than 3 times a second (2.3.1, A); motion triggered by interaction can be turned off (2.3.3, AAA).
Implementation vocabulary
| Tool | What it is | Pick it for | Ask for it like |
|---|---|---|---|
| CSS transitions | animate a property when its value changes | hover, focus, toggles, simple state changes | "CSS transitions on transform and opacity using our tokens" |
| CSS keyframes | named multi-step animations (@keyframes) | loaders, entrances on load, loops | "A keyframe animation with a reduced-motion override" |
@starting-style | CSS for an element's first frame, so entry from display: none can transition | dialogs and popovers appearing | "Entry and exit for the dialog with @starting-style" |
| Web Animations API | element.animate(): CSS-grade animations controlled from JavaScript, with pause, reverse, seek and promises | values computed at runtime, FLIP, sequencing | "WAAPI, awaiting finished between steps" |
| View Transitions API | the browser snapshots old and new states and animates between them | route changes, shared elements on the web | "A view transition with the image as a named shared element" |
| Scroll-driven animations | animation tied to a scroll or view timeline | progress bars, reveals; not supported in every browser yet | "A scroll-driven progress bar with a static fallback" |
| Motion (formerly Framer Motion) | animation library for React and JavaScript: springs, exit animations, layout and shared-element animation, gestures | React apps | "Motion for React with AnimatePresence and layoutId" |
| GSAP | JavaScript timeline library with ScrollTrigger and SVG tools; free since version 3.13 | long sequenced timelines, marketing pages | "A GSAP timeline inside gsap.matchMedia()" |
| Lottie | a JSON animation format exported from After Effects (via the Bodymovin plugin), with players for web, iOS and Android | illustrations and icons animated by designers | "A Lottie brief: size, frame rate, loop, segments to play" |
| Rive | an editor and runtimes for interactive vector animation driven by state machines | animations that react to input: toggles, characters, mascots | "A Rive state machine with inputs hover, pressed, success" |
| SwiftUI / UIKit | withAnimation, springs, matchedGeometryEffect; UIViewPropertyAnimator for interruptible UIKit animation | iOS and iPadOS | "SwiftUI with .spring(duration: 0.35, bounce: 0.15)" |
| Jetpack Compose | animate*AsState, AnimatedVisibility, updateTransition, spring(), shared elements | Android | "Compose AnimatedVisibility with a spring, damping 0.8" |
Brief the model
Each input has a slot in the prompt skeleton from fundamentals; the Slot column says where it goes.
| Input | Why it matters | Example | Slot |
|---|---|---|---|
| Existing tokens | consistency | "Durations: 150/250/400 ms; easing tokens: [paste]" | <materials> |
| The state change and trigger | motion explains a change; the model needs both ends | "Tap a card in the grid; detail view opens" | <context> |
| Purpose | decides the pattern | "Keep users oriented: they should see where the card went" | <context> |
| Platform and stack | APIs and idioms differ | "Next.js + Motion for React" or "SwiftUI, iOS 17+" | <context> |
| Personality | sets bounce and duration | "Calm and precise; no bounce except on success" | <context> |
| Frequency | frequent actions must be faster | "Opened 50 times a day" | <context> |
| Distance and size | bigger moves need more time | "Full-screen on phone, 480 px panel on desktop" | <context> |
| Motion you like, and one you don't | feel is easier to show than to describe | "A gallery app's zoom into a photo: quick, no bounce. Our current modal: slow and floaty" | <examples> |
| Interruption | what happens if the user acts mid-animation | "Swipe down to dismiss at any point" | <requirements> |
| Devices and performance | budgets differ | "Must be smooth on a 3-year-old mid-range Android" | <requirements> |
| Accessibility | reduced motion and focus behavior | "Reduced motion: crossfade only; focus moves immediately" | <requirements> |
| Output | what you'll do with it | "Spec table first, then CSS, then a reduced-motion variant" | <output_format> |
What to ask for
| Stage | Named deliverables |
|---|---|
| System | a one-page motion principles doc; motion tokens (durations, easings, springs) with usage rules; a pattern library (which transition for which navigation) |
| Design | a motion spec table per component or transition; a storyboard with timestamps; a state diagram showing which transition plays between states; reduced-motion variants |
| Prototype | an HTML/CSS or Motion prototype with a slow-motion toggle; a brief for a Lottie or Rive file |
| Build | production code for your stack using the tokens; interruption handling; reduced-motion handling; tests that animations end in the right state |
| Review | a motion audit of an existing app (purpose, duration, easing, performance, accessibility); a performance check; a QA checklist |
The prompt chain
The main deliverable is a motion spec for one app interaction: the timing table, easing, choreography and reduced-motion fallback, then the code that implements it. Run stages 1–3 in one chat. Run stage 4 in a fresh chat, because a model reviewing its own draft shares its blind spots.
1. Interview
Use first, when you know the interaction but not what a motion designer needs before speccing it.
<context>
I need a motion spec, then code, for one interaction in my app: [interaction, e.g. tapping a product card opens its detail page].
Stack: [stack: CSS | Web Animations API | View Transitions API | Motion for React | SwiftUI | Jetpack Compose].
I am not a motion designer.
</context>
<task>
Interview me first: ask the questions a motion designer asks before writing a motion spec.
</task>
<requirements>
- At most 10 questions, grouped under: the state change, purpose, frequency and context, gestures and interruption, accessibility, the existing motion system.
- For each question: why it matters to the spec, and 2-3 typical answers I can pick from.
- Do not propose any motion, numbers or code yet.
</requirements>
<process>
Ask the questions, then stop and wait for my answers.
</process>2. Options
Use once the interview is answered, to see materially different patterns before committing to one.
<materials>
[paste the interview questions and my answers]
</materials>
<task>
Propose 2-4 materially different motion approaches for this interaction, e.g. a container transform, a shared-element transition, a shared axis or a fade through.
</task>
<requirements>
- For each: the pattern by name, the lead element, a duration range and easing family, what it explains to the user (orientation, continuity, hierarchy), the effort to build it on my stack, its performance risk on a low-end phone, and its reduced-motion version.
- Say which Material 3 transition pattern or Apple Human Interface Guidelines convention each follows, and where it departs from it.
- Recommend one and say what would make you pick another.
</requirements>
<process>
Give the options as a table, then the recommendation, then stop and wait for my choice. Do not write the spec yet.
</process>3. Produce
Use after you have chosen: the full skeleton, filled in for the chosen pattern.
<materials>
Motion tokens: [paste, e.g. durations 150/250/400 ms; standard easing cubic-bezier(0.2, 0, 0, 1)]
Interview answers and the option I chose: [paste]
</materials>
<context>
Interaction: [interaction, e.g. tapping a product card in a grid opens its detail page]
Purpose: [purpose: feedback | orientation | continuity | hierarchy | status | delight]
Chosen pattern: [pattern: container transform | shared element | shared axis | fade through | crossfade]
Stack: [stack: CSS | Web Animations API | View Transitions API | Motion for React | SwiftUI | Jetpack Compose]
Frequency: [frequency: dozens of times a day | a few times a day | once per session | rarely]
The spec goes to engineers who will implement it exactly as written.
</context>
<examples>
[optional: 1-2 interactions in other apps whose motion you like and why, e.g. "a gallery app's zoom into a photo: quick, no bounce, I never lose my place"; 1 you don't, e.g. "our current modal: 600 ms and floaty"]
</examples>
<task>
A motion spec for this interaction, then production code that implements it, so engineers can build it without guessing numbers.
</task>
<requirements>
- Durations within the usual ranges (feedback 50-150 ms, menus 150-250 ms, dialogs and sheets 250-400 ms, screens 300-500 ms); justify anything outside them.
- Easing by direction: entering elements decelerate, leaving elements accelerate, on-screen moves use standard easing. Give exact cubic-bezier values or spring parameters (damping ratio and stiffness), using my tokens where they exist.
- The exit is faster than the entry, not the same animation reversed.
- Animate only transform and opacity unless you justify the exception; never scale from 0.
- Interruptible: a tap or swipe mid-transition reverses from the current position and velocity.
- Holds 60 fps on [devices, e.g. a 3-year-old mid-range Android phone].
- Focus moves immediately, input is never blocked, and the end state is correct even if the animation never runs.
- Reduced motion (prefers-reduced-motion on the web, Reduce Motion on iOS, Remove animations on Android): replace movement with a short crossfade or an instant change; keep feedback.
- Name the Material 3 pattern or Apple HIG convention you follow and where you depart from it.
- Never invent numbers, names, quotes or sources: write [NEEDS SOURCE] where support is missing, and [CHECK DOCS] next to any API you are not sure exists in the current version.
- End with a list of the assumptions you made.
</requirements>
<quality_bar>
Before answering, check your draft against:
- Every animated element serves the stated purpose; anything that doesn't is cut.
- One lead element; the others follow it; any stagger is capped at about 300 ms in total.
- Every row has a duration, a delay and an easing or spring in numbers; no "smooth" or "snappy" without them.
- The code matches the spec table value for value, including the reduced-motion path and when focus moves.
- Nothing in the code animates width, height, top, left, margin or a large blur.
</quality_bar>
<output_format>
1. A spec table: element | property | from | to | duration (ms) | delay (ms) | easing or spring | notes.
2. The choreography in one sentence, and how the reverse transition differs in one sentence.
3. Interruption, focus and reduced-motion rules as bullets, with the reduced-motion replacement for each row.
4. The code in one self-contained file.
5. The assumptions.
</output_format>
<process>
Ask me up to 3 questions first if anything missing would change your answer; otherwise go on.
Write the spec table first and check it against the quality bar before writing any code.
</process>4. Review
Use when the spec and code are back: open a fresh chat, paste the brief and the output, and ask for a ranked critique, not a rewrite.
<materials>
The brief: [paste the stage 3 prompt]
The spec and code: [paste]
</materials>
<context>
This is a fresh chat. The spec below was written in another conversation; review it as someone seeing it for the first time, before it goes to engineering.
</context>
<task>
A review of this motion spec and its code. Do not rewrite them.
</task>
<requirements>
- Judge against: purpose, duration ranges, easing direction, choreography, interruption, performance (transform and opacity only), reduced motion, consistency with the tokens, accessibility (focus, input, flashing; WCAG 2.2.2, 2.3.1 and 2.3.3), and a correct end state without the animation.
- Walk through it three times: as a user with reduced motion on, as a keyboard user, and on a slow phone that drops frames. Say what breaks in each.
- Compare the code with the spec table row by row.
- Quote the row or line behind every finding.
</requirements>
<quality_bar>
Before answering, check that every finding names a criterion, cites a row or line, and gives a fix in numbers or code terms.
</quality_bar>
<output_format>
A table: rank | finding | criterion | where | fix, most severe first. Then one line: ship, fix first, or redo.
</output_format>
<process>
Rank the issues by impact on users; do not produce a revised spec or code.
</process>Goals and loops
In an agent that runs commands and checks its own work, such as Claude Code, the Produce stage can run until it
measurably passes: /goal keeps starting turns until a condition holds, and /loop repeats a prompt or skill on
an interval. The checker reads only the conversation, so every condition below names a check whose output Claude
prints, the constraints that must survive, and a bound. How to write your own:
Goals and loops.
| Task | Completion condition |
|---|---|
| Implement the motion spec | /goal npx playwright test motion.spec.ts passes, including a run with reduced motion emulated in which nothing moves position, and every duration matches its token in the spec (print each); or stop after 15 turns |
| Performance-safe animations | /goal npx stylelint "src/**/*.css" with stylelint-high-performance-animation reports 0 problems, so only transform and opacity animate (show its output); or stop after 10 turns |
| Motion tokens | /goal tokens.json defines every duration and easing named in the spec, and a search of the code for raw millisecond values in transitions finds 0 (print the count); or stop after 8 turns |
Loops that fit: rarely: motion work is short and visual. Use /loop only to watch a long performance or visual-regression run in CI.
What a checker can't judge: how it feels. Watch it at full speed, slowed down and on a slow phone, with reduced motion on and off.
Prompt library
One entry per deliverable a motion designer produces, besides the spec in the chain. Quick is for when you're in a hurry; Full is for when the result matters.
Motion tokens
Stage: produce · Use when: you're starting a design system, or every screen animates differently.
Quick
Create motion tokens for [product, e.g. a calm budgeting app] on [stack: CSS | Motion for React | SwiftUI | Jetpack Compose]: 5-7 durations in ms, easings as cubic-bezier (standard, decelerate, accelerate, one emphasized pair), 3 spatial springs and 1 effects spring, and which token to use for press, menus, dialogs, sheets and page transitions.
Base the ranges on Material 3 and NN/g, and check that frequent actions stay under about 200 ms.Full
<materials>
[optional: paste the animation values the app uses today, or a list of its animations]
</materials>
<context>
Product: [product, e.g. a budgeting app for freelancers]
Personality: [personality: calm and precise | brisk and efficient | playful | expressive]
Platforms: [platforms, e.g. web with CSS and Motion for React; iOS with SwiftUI]
The tokens will be used by [team, e.g. 3 engineers and no motion designer].
</context>
<examples>
[optional: 1-3 apps whose motion feels right for us and why, e.g. "fast, never bounces, things leave quicker than they arrive"; 1 that feels wrong, and why]
</examples>
<task>
A set of motion tokens with usage rules, so every component animates from the same values.
</task>
<requirements>
- Duration tokens: 5-7 steps in ms, each with what it is for.
- Easing tokens as cubic-bezier values: standard, enter (decelerate), exit (accelerate), and one emphasized pair.
- Spring tokens: 3 spatial (fast, default, slow) and 1 effects spring with no overshoot, as damping ratio and stiffness, plus the equivalent in each of my platforms' own parameters.
- Usage rules: which tokens for press feedback, menus, dialogs, sheets, page transitions and staggers.
- A reduced-motion rule for each category.
- Base the ranges on Material 3's motion tokens and NN/g's duration guidance, and say where you deviate and why.
- Never invent numbers, names, quotes or sources: write [NEEDS SOURCE] where support is missing.
- End with a list of the assumptions you made.
</requirements>
<quality_bar>
Before answering, check your draft against:
- Frequent actions (press, toggle, menu) stay at or under about 200 ms.
- Every exit token is shorter than its matching enter token.
- No effects token (color, opacity) overshoots.
- The personality shows in the numbers (bounce, duration), not in adjectives.
</quality_bar>
<output_format>
Tables for durations, easings and springs (name | value | use for), the usage rules as a table, then the tokens as CSS custom properties, plus a constants file for each native platform I listed.
</output_format>
<process>
Ask me up to 3 questions first if anything missing would change your answer; otherwise go on.
</process>Micro-interaction
Stage: produce · Use when: a button, toggle or like feels dead, or users tap twice because nothing seems to happen.
Quick
Spec and code a press micro-interaction for [component, e.g. the primary button] in [stack: CSS | Motion for React | SwiftUI | Jetpack Compose]: scale to about 0.97 in about 100 ms, spring back with no visible bounce, the same pressed state on Space and Enter, color change only under reduced motion.
Check that feedback starts within 100 ms and the action never waits for the animation.Full
<materials>
Tokens: [paste, or write "none yet"]
</materials>
<context>
Component: [component: primary button | toggle | favorite or like | checkbox | add to cart]
What it confirms: [result, e.g. "added to favorites", saved optimistically before the server answers]
Stack: [stack: CSS | Motion for React | SwiftUI | Jetpack Compose]
Frequency: [frequency: dozens of times a day | a few times a day | rarely]
</context>
<examples>
[optional: a micro-interaction you like and why, e.g. "a like that fills and settles in under 200 ms"; one you don't, e.g. "a 600 ms bounce on every tap"]
</examples>
<task>
A micro-interaction spec and code for this component: rest, hover, pressed, success, failure and disabled states.
</task>
<requirements>
- Press feedback within about 100 ms: a slight scale (about 0.97) or one color step; never scale from 0.
- Release springs back with no visible overshoot; a success state may overshoot once, on spatial properties only.
- Hover only on devices that support it (@media (hover: hover)); the same pressed state on Space and Enter.
- Optimistic: show the result at once and animate a rollback if the request fails.
- Reduced motion: color or opacity change only; keep the feedback.
- Animate only transform and opacity.
- Never invent numbers, names, quotes or sources: write [NEEDS SOURCE] where support is missing.
- End with a list of the assumptions you made.
</requirements>
<quality_bar>
Before answering, check your draft against:
- It still feels instant on the 50th press, not just the first.
- The action never waits for the animation to finish.
- Every state has a duration and an easing or spring in numbers.
</quality_bar>
<output_format>
A spec table (state | property | from | to | duration | easing or spring), then the code.
</output_format>
<process>
Ask me up to 3 questions first if anything missing would change your answer; otherwise go on.
</process>Storyboard
Stage: produce · Use when: you need to see or explain a transition before anyone builds it.
Quick
Storyboard the transition from [screen A, e.g. the inbox] to [screen B, e.g. one message] and back as 6-8 key frames each way: timestamp (ms), what's on screen, what moves and which way, and where the eye should be.
Flag any frame where two things compete for attention.Full
<materials>
[paste or describe both screens: layout and main elements]
</materials>
<context>
Screens: from [screen A, e.g. the inbox list] to [screen B, e.g. one message], and back.
User and moment: [user, e.g. a commuter on a phone, one hand, going back and forth often]
Pattern so far: [pattern: container transform | shared element | shared axis | fade through | not decided]
The storyboard is for [readers, e.g. a designer who will animate it and a product lead who must approve it].
</context>
<task>
A storyboard of the transition in both directions, so it can be reviewed before it is built.
</task>
<requirements>
- 6-8 key frames per direction, each with a timestamp (ms), what is on screen, what is moving and in which direction, and where the eye should be.
- One lead element; say when each other element starts relative to it.
- Keep to the usual range for screen transitions (about 300-500 ms) and name the easing for each movement.
- Then narrate the same transition at 10% speed, as if describing a slow-motion recording.
- Describe the reduced-motion version in one line.
- Never invent numbers, names, quotes or sources: write [NEEDS SOURCE] where support is missing.
- End with a list of the assumptions you made.
</requirements>
<quality_bar>
Before answering, check your draft against:
- At every frame there is one obvious place to look.
- Spatial logic holds: forward and back move in opposite directions, and things leave toward where they live.
- The back transition is shorter than the forward one.
- Nothing slides or zooms across the full screen without a reason the user would notice.
</quality_bar>
<output_format>
A table per direction (frame | ms | on screen | moving | eye on), the slow-motion narration in under 150 words, the reduced-motion line, then the assumptions.
</output_format>
<process>
Ask me up to 3 questions first if anything missing would change your answer; otherwise go on.
</process>Gesture-driven component
Stage: produce · Use when: a sheet, drawer, swipeable card or carousel should follow the finger and settle naturally.
Quick
Implement a bottom sheet in [stack: Motion for React | SwiftUI | Jetpack Compose] with snap points at [snap points, e.g. 40% and 90% of screen height]: it follows the finger 1:1, rubber-bands past the top, settles with a spring that carries the release velocity, and dismisses past [threshold, e.g. 30% down or a fast fling].
Add a close button as the non-drag alternative, and check that a new touch catches it mid-flight.Full
<context>
Component: [component: bottom sheet | side drawer | swipeable card | carousel]
Stack: [stack: Motion for React | SwiftUI | Jetpack Compose | UIKit]
Snap points: [snap points, e.g. 40% and 90% of screen height]
Dismiss rule: [rule, e.g. dragged past 30% down, or flung down fast]
Frequency: [frequency: dozens of times a day | a few times a day | rarely]
</context>
<task>
A gesture-driven version of this component, with a behavior spec and code: drag, release, snap, dismiss and interruption.
</task>
<requirements>
- While dragging: direct manipulation; the element tracks the finger exactly, with no easing.
- Rubber banding past the outermost snap point, with increasing resistance.
- On release: pick the snap point from position plus velocity; a spring (damping ratio about 0.85) starts with the release velocity.
- Interruptible: a new touch catches the element mid-animation.
- The backdrop's opacity tracks the position.
- A tap or button alternative for every drag (WCAG 2.5.7 Dragging Movements).
- Reduced motion: no overshoot and a shorter settle; the gesture still works.
- Spring parameters in the platform's own terms: [spring parameters: dampingRatio and stiffness | duration and bounce | stiffness, damping and mass].
- Never invent numbers, names, quotes or sources: write [NEEDS SOURCE] where support is missing, and [CHECK DOCS] next to any API you are not sure exists in the current version.
- End with a list of the assumptions you made.
</requirements>
<quality_bar>
Before answering, check your draft against:
- A slow drag and a fast fling from the same position can land on different snap points.
- Nothing jumps when the finger lifts: the spring starts from the current position and speed.
- The non-drag alternative reaches every snap point.
- Only transform and opacity animate.
</quality_bar>
<output_format>
A behavior table (phase | rule | numbers), one self-contained code example, then what to test on a real device.
</output_format>
<process>
Ask me up to 3 questions first if anything missing would change your answer; otherwise go on.
</process>Loading and arrival
Stage: produce · Use when: content takes 1–10 seconds to load, the screen looks frozen or flashes a spinner, or a list appears all at once.
Quick
Design the loading state for [screen, e.g. the order history list]: a skeleton that matches the final layout exactly, a slow low-contrast shimmer (static under reduced motion), nothing at all if data arrives within about 300 ms, then a 150 ms crossfade or a 30 ms stagger capped at 6 items.
Check that nothing shifts when the content lands.Full
<materials>
[paste the screen's layout or component code]
</materials>
<context>
Screen: [screen, e.g. the order history list, up to 50 items]
Typical wait: [wait: under 1 s | 1-10 s | over 10 s | varies a lot]
Stack: [stack: CSS | Motion for React | SwiftUI | Jetpack Compose]
</context>
<task>
The loading and arrival motion for this screen: what shows while waiting, and how the content enters when it arrives.
</task>
<requirements>
- Match the indicator to the wait, following NN/g: nothing under about 1 s, a skeleton for full-page loads up to about 10 s, a determinate progress bar with an estimate beyond that.
- Skeleton: the same heights and positions as the final layout; a slow, subtle shimmer about every 1.5 s.
- Don't show the skeleton at all if data arrives within about 300 ms.
- Arrival: a 150 ms crossfade, or items fade in and rise 8 px over 200 ms with a 30 ms stagger for the first 6 visible items; the rest appear with the sixth.
- Never re-run the entrance on re-render, filtering or pagination.
- Reduced motion: a static skeleton and a single short fade on arrival.
- Mark the region aria-busy while loading.
- Never invent numbers, names, quotes or sources: write [NEEDS SOURCE] where support is missing.
- End with a list of the assumptions you made.
</requirements>
<quality_bar>
Before answering, check your draft against:
- Nothing moves or resizes when content replaces the skeleton (no layout shift).
- The whole entrance is finished within about 350 ms.
- A fast connection never sees a flash of skeleton.
</quality_bar>
<output_format>
A short spec table, the code, then how you stop the entrance from re-running.
</output_format>
<process>
Ask me up to 3 questions first if anything missing would change your answer; otherwise go on.
</process>Lottie or Rive brief
Stage: produce · Use when: an illustration, icon or mascot will be animated by a designer, and you need to brief them and the engineers who trigger it.
Quick
Write a brief for a [format: Lottie | Rive] animation of [subject, e.g. an empty-inbox illustration]: size, frame rate, duration, loop or play once, the segments or states the app triggers, a file-size budget, and the still frame for reduced motion.
Check that every state the app needs is named.Full
<materials>
[paste the palette and illustration style rules]
</materials>
<context>
Where it appears: [placement, e.g. the inbox empty state, 240 x 240 px on mobile]
What it should do: [purpose: feedback | status | delight | onboarding]
Format: [format: Lottie | Rive]
Behavior: [behavior: plays once | loops | segments triggered by the app | a state machine reacting to input]
Platforms: [platforms, e.g. web, iOS and Android]
</context>
<examples>
[1-3 animations you like and why, e.g. "a checkmark that draws in, then settles, in under 1 s"; 1 you don't, and why]
</examples>
<task>
A brief an animator can work from and an engineer can integrate against, for this animation.
</task>
<requirements>
- Specs: artboard size, frame rate, duration, loop or play once, and either the named segments (Lottie) or the state machine's inputs and states (Rive) the app will trigger.
- Motion direction in plain words and numbers: easing family, amount of overshoot, and which principles carry the feel (anticipation, follow-through, squash and stretch).
- Performance: a file-size budget, a layer limit, no features the target players don't support (check the player's supported-features list), and a test on a low-end phone.
- Reduced motion: the still frame to show, and whether it may play once on request.
- Anything that loops for more than 5 seconds needs a pause control (WCAG 2.2.2); nothing flashes more than 3 times a second (2.3.1).
- Never invent numbers, names, quotes or sources: write [NEEDS SOURCE] where support is missing.
- End with a list of the assumptions you made.
</requirements>
<quality_bar>
Before answering, check your draft against:
- An animator could start without asking a question, and the engineer knows exactly which segments or inputs to call.
- Every state the app needs (idle, hover, pressed, success, error, loading) is named or explicitly ruled out.
- The motion direction uses numbers, not "fun" or "smooth".
</quality_bar>
<output_format>
A one-page brief: purpose, a specs table, a states or segments table (name | trigger | frames or behavior), motion direction, performance budget, accessibility, deliverables (source file, exported file, still frame).
</output_format>
<process>
Ask me up to 3 questions first if anything missing would change your answer; otherwise go on.
</process>Translate a feeling into numbers
Stage: options · Use when: a stakeholder says "snappier" or "more playful" and you need concrete parameters to try.
Quick
Our [component, e.g. sheet-open] animation uses [values, e.g. 400 ms, cubic-bezier(0.4, 0, 0.2, 1)]. It should feel [direction: snappier | heavier | calmer | more playful].
Give 3 variants, each with duration or spring parameters and one sentence on how it will feel; no overshoot on color or opacity. Stop after the options.Full
<materials>
Current values: [paste, e.g. 400 ms, cubic-bezier(0.4, 0, 0.2, 1), or the code]
Tokens: [paste, or write "none"]
</materials>
<context>
Component: [component, e.g. the bottom sheet opening]
Feedback we got: [quote, e.g. "it feels sluggish"]
Direction: [direction: snappier | heavier | calmer | more playful | more premium]
Stack: [stack: CSS | Motion for React | SwiftUI | Jetpack Compose]
</context>
<examples>
[optional: 1-2 interactions that already have the feel we want and why; 1 that goes too far]
</examples>
<task>
Three variants that move this animation in the requested direction, so we can try them side by side and pick one.
</task>
<requirements>
- Each variant: duration and easing, or spring parameters as damping ratio and stiffness, plus the equivalent in my stack's own parameters.
- Vary them materially, e.g. a shorter duration, a different curve, a spring with slight overshoot.
- One sentence each on how it will feel different and what it costs (overshoot reads as playful but distracts on frequent actions).
- No overshoot on color or opacity; stay inside our tokens or say which new token a variant needs.
- Never invent numbers, names, quotes or sources: write [NEEDS SOURCE] where support is missing.
- End with a list of the assumptions you made.
</requirements>
<quality_bar>
Before answering, check your draft against:
- The three variants would look different side by side, not 10 ms apart.
- Each one stays inside the duration range for this kind of motion.
- The recommendation says what to test it against, e.g. the 50th repetition and a low-end phone.
</quality_bar>
<output_format>
A table: variant | values | stack equivalent | how it feels | risk. Then a recommendation in two sentences.
</output_format>
<process>
Give the options and the recommendation, then stop and wait for my choice before writing any code.
</process>Motion audit
Stage: review · Use when: an existing app feels slow, janky or inconsistent, or before a release with significant animation.
Quick
Audit the motion in this [input: code | screen recording described frame by frame | list of animations]: for each animation, its duration, easing, purpose and problems (too long, wrong easing direction, layout properties animated, not interruptible, no reduced-motion path). Rate severity 1-3, list the top 5 fixes, and don't rewrite anything.
[paste]Full
<materials>
[paste the code, or describe the screen recording frame by frame with timestamps]
</materials>
<context>
App: [app, e.g. a React web app using Motion, used all day by support agents]
Complaint: [complaint: feels slow | janky on some devices | inconsistent between screens | an accessibility complaint]
Target devices: [devices, e.g. mid-range Android phones and 5-year-old laptops]
</context>
<task>
A motion audit of this app, covering purpose, performance, accessibility and consistency. Do not rewrite the code.
</task>
<requirements>
- For each animation: what animates, its duration and easing, its purpose (feedback, orientation, continuity, hierarchy, status, delight or none), and its problems.
- Check: durations against the usual ranges; easing direction; linear movement; layout properties animated (width, height, top, left, margin); large blurs or shadows; will-change left on permanently; animations that can't be interrupted; competing movements; focus or input blocked during animation.
- Accessibility: a reduced-motion path for each animation; anything moving on its own for more than 5 seconds without a pause (WCAG 2.2.2); anything that could flash more than 3 times a second (2.3.1); parallax, zooms or large slides (vestibular triggers); drags without a tap alternative (2.5.7).
- Quote the line or timestamp behind every finding.
- Never invent numbers, names, quotes or sources: write [NEEDS SOURCE] where support is missing.
- End with a list of the assumptions you made.
</requirements>
<quality_bar>
Before answering, check your draft against:
- Every finding names the rule it breaks and where.
- Severity reflects frequency: a slow animation on a frequent action outranks a slow one on a rare screen.
- The fixes converge on one shared set of tokens, not a new value per component.
</quality_bar>
<output_format>
A table: issue | where | rule (WCAG criterion, performance or guideline) | severity 1-3 | fix. Then the top 5 changes by impact, and a proposed shared token set in one short table.
</output_format>
<process>
Ask me up to 3 questions first if anything missing would change your answer; otherwise go on. List the fixes; don't apply them.
</process>Worked example
Weak prompt
Make the modal animate nicely.What's wrong
- No platform, trigger, purpose or frequency, so the model can't choose durations: a dialog seen 20 times a day needs different motion from a one-time welcome screen.
- "Nicely" is a feeling, not a spec: no durations, easing or tokens to match.
- Nothing about interruption, focus or reduced motion, so none of them will be handled.
- No output shape: you get code with no spec to check it against.
- Typical result: a 600 ms bounce-in with
ease-in-out, scaling from 0, animatingtopandheight, the same animation reversed for exit, focus moved only after it finishes, no reduced-motion version, and code that snaps back at the end.
Strong prompt
<materials>
Motion tokens: durations 150 / 250 / 400 ms; enter easing cubic-bezier(0, 0, 0, 1); exit easing cubic-bezier(0.3, 0, 1, 1).
</materials>
<context>
A confirmation dialog in a React app using Motion for React. Trigger: the user clicks "Delete project". Power users see it several times a day, so it must feel quick.
Purpose: focus attention on the decision and show that the dialog sits above the page.
</context>
<task>
The open and close motion for this dialog: a spec table, then the component code.
</task>
<requirements>
- Open: the backdrop fades in; the dialog fades in and scales from 0.96 to 1, never from 0. Close: faster than open.
- Transform and opacity only.
- Focus moves into the dialog as soon as it opens, not when the animation ends; Esc and a backdrop click close it; closing mid-open reverses smoothly from where it is.
- Reduced motion: opacity only, 150 ms.
- Never invent numbers, names, quotes or sources: write [NEEDS SOURCE] where support is missing, and [CHECK DOCS] next to any Motion API you are not sure exists.
- End with a list of the assumptions you made.
</requirements>
<quality_bar>
Before answering, check your draft against:
- Durations fit a frequent dialog: open about 250 ms or less, close shorter.
- Enter decelerates, exit accelerates.
- The code matches the spec table value for value, including when focus moves and the reduced-motion path.
</quality_bar>
<output_format>
A spec table (element | property | from | to | duration | delay | easing), then one component file.
</output_format>
<process>
Ask me up to 3 questions first if anything missing would change your answer; otherwise go on. Write the spec first and check it against the quality bar before writing code.
</process>What came back
Illustrative, abridged:
element property from to duration delay easing
backdrop opacity 0 1 250 ms 0 cubic-bezier(0, 0, 0, 1)
dialog opacity 0 1 250 ms 0 cubic-bezier(0, 0, 0, 1)
dialog scale 0.96 1 250 ms 0 cubic-bezier(0, 0, 0, 1)
close all rows reversed 150 ms 0 cubic-bezier(0.3, 0, 1, 1)
Reduced motion: opacity only, 150 ms. Focus: moves to Cancel on open.
<motion.div role="alertdialog" aria-modal="true"
initial={{ opacity: 0, scale: reduce ? 1 : 0.96 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: reduce ? 1 : 0.96,
transition: { duration: 0.15, ease: [0.3, 0, 1, 1] } }}
transition={{ duration: reduce ? 0.15 : 0.25, ease: [0, 0, 0, 1] }}
onAnimationComplete={() => cancelRef.current?.focus()}>
…
Assumptions: Cancel is the safer default focus; the backdrop is a sibling motion.div inside AnimatePresence.How an expert judged it
- Durations and easing: pass. 250 ms open with a decelerating curve, 150 ms close with an accelerating one, and a scale from 0.96 suit a dialog seen several times a day.
- Reduced motion: pass. Scale is pinned to 1, so only opacity changes.
- Code matches the spec: fail. The spec says focus moves on open; the code moves it in
onAnimationComplete, 250 ms later. Keyboard and screen-reader users wait, and a quick Enter press lands on the page behind the dialog. The demo looks perfect, which is why this is caught by reading the code against the spec row by row, the third check in the quality bar. - Interruption: not shown. The output doesn't say what happens when Esc is pressed 100 ms into the open; ask for a test rather than trust the assumption.
The follow-up
The spec says focus moves into the dialog on open, but the code focuses Cancel in onAnimationComplete, which runs 250 ms later. Fix only that: move focus to Cancel as soon as the dialog mounts, return focus to the "Delete project" button on close, and leave every motion value unchanged. Then show a test that presses Esc 100 ms after opening and checks that the dialog reverses from where it is and focus returns to the button.Judge the output
| Criterion | Good | Red flag |
|---|---|---|
| Purpose | each animation explains something | motion for its own sake on a frequent action |
| Duration | within the ranges above; frequent actions near-instant | 600 ms+ for a menu or dialog |
| Easing direction | enter decelerates, exit accelerates, on-screen moves use standard | linear movement; ease-in on entry |
| Choreography | one lead movement; staggers capped | everything moves at once; a 2-second stagger |
| Interruption | reverses from the current state; springs for gestures | must finish before the next tap works |
| Performance | transform and opacity only | animating height, top, box-shadow or large blurs |
| Reduced motion | a defined alternative per animation | none, or all feedback removed |
| Consistency | tokens reused across components | a new duration and curve per component |
| Accessibility | focus moves immediately; input never blocked; no flashing | focus waits for the animation; content hidden until animation ends |
| End state | correct without the animation (no snap-back) | relies on fill: forwards or a final callback |
The same checks are built into every <quality_bar> on this sheet, so the model applies them before you see a draft;
use the table to check what comes back.
Check your spec against these ranges: feedback 50-150 ms,
menus 150-250 ms, dialogs and sheets 250-400 ms, screens
300-500 ms. Flag anything outside them and justify or fix it.
Then list every property you animate that isn't transform or
opacity.Pitfalls
| Pitfall | Why it happens | Fix |
|---|---|---|
| Too slow | long motion looks smooth in a demo | use the duration table; test the 50th repetition, not the first |
| Linear or wrong-direction easing | defaults | name the easing token per movement |
| Scaling from 0 | "pop in" requests | start at 0.9–0.97 scale plus opacity |
| Animating layout properties | the obvious property is height | transform, FLIP, or the View Transitions API |
| Same animation for exit | reversing is easy | exits shorter and accelerating |
| Not interruptible | fixed-duration animations queued | springs; retarget from the current value |
| Blocking input or focus | waiting for finished | move focus and accept input immediately |
| No reduced-motion version | not requested | ask for it in every prompt |
| Everything bounces | "make it playful" | overshoot only on spatial springs and only where it means something |
| Vague words | "snappy", "smooth", "subtle" | ask for milliseconds, cubic-bézier values or spring parameters |
| Trusting API details | libraries change fast | check current docs for Motion, GSAP, SwiftUI or Compose |
| Heavy Lottie files | complex After Effects exports | simplify layers; test on low-end devices; consider CSS for simple icons |
References
- Material Design 3: Motion overview (opens in a new tab) and Easing and duration (opens in a new tab): Google's current motion system, tokens and springs
- Material Design 3: Transition patterns (opens in a new tab): container transform, shared axis, fade through
- Material Design (2014 guidelines): Duration and easing (opens in a new tab): the original enter/exit and device-size duration guidance
- Nielsen Norman Group: Executing UX animations: duration and motion characteristics (opens in a new tab): the 100–500 ms guidance
- Nielsen Norman Group: The role of animation and motion in UX (opens in a new tab) and Skeleton screens 101 (opens in a new tab): purpose of motion and loading indicators
- Apple Human Interface Guidelines: Motion (opens in a new tab): iOS conventions and reduced motion
- Apple Developer: SwiftUI Animation (opens in a new tab) and CASpringAnimation (opens in a new tab): spring APIs on Apple platforms
- Android Developers: Animations in Compose (opens in a new tab), Shared elements in Compose (opens in a new tab) and Spring animation (Views) (opens in a new tab)
- Jetpack Compose Material 3 source (
MotionTokens.kt,StandardMotionTokens.kt,ExpressiveMotionTokens.ktin the androidx repository): the exact easing, duration and spring values quoted above - MDN: Web Animations API (opens in a new tab), View Transition API (opens in a new tab), prefers-reduced-motion (opens in a new tab) and will-change (opens in a new tab)
- Chrome for Developers: Smooth transitions with the View Transition API (opens in a new tab): same- and cross-document transitions
- web.dev: How to create high-performance CSS animations (opens in a new tab) and Rendering performance (opens in a new tab): which properties are cheap, and the frame budget
- web.dev: prefers-reduced-motion (opens in a new tab): designing reduced-motion alternatives
- W3C: Understanding 2.3.3 Animation from interactions (opens in a new tab), 2.2.2 Pause, stop, hide (opens in a new tab) and 2.3.1 Three flashes (opens in a new tab)
- The A11Y Project: A primer to vestibular disorders (opens in a new tab) and A List Apart: Designing safer web animation for motion sensitivity (opens in a new tab)
- Motion docs (opens in a new tab), GSAP docs (opens in a new tab), Lottie docs (opens in a new tab) and Rive docs (opens in a new tab): the main animation libraries and formats
- Wikipedia: Twelve basic principles of animation (opens in a new tab): summary of Thomas and Johnston's principles
- Frank Thomas and Ollie Johnston, The Illusion of Life: Disney Animation (1981): the source of the 12 principles