UI Motion skill for Claude Code
Fast, purposeful motion with real numbers. Claude gives each animation a job and a budget: 100 to 150 milliseconds for feedback, up to 350 for modals, exits faster than entrances, ease-out on the way in and ease-in on the way out. It animates only transform and opacity, prefers interruptible transitions, @starting-style and View Transitions over heavy libraries, and adds a reduced-motion version that keeps feedback but drops travel.
- name
ui-motion - size188 lines · ≈2,150 tokens
- files1
- licenseMIT
- updated
What Claude sees before loading it
This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.
description: Designs purposeful interface motion with concrete numbers — duration by distance and size, easing per direction, exit-faster-than-enter, stagger limits, interruptible transitions, transform/opacity-only performance, View Transitions, @starting-style entry, springs, and prefers-reduced-motion alternatives. Use when adding micro-interactions, animating modals, menus, toasts, lists, or page changes, when motion feels slow, floaty, or janky, or when reviewing animation for accessibility. Try asking
- Add tasteful animations to our modal, dropdown and toast components
- Everything on this page fades in slowly and it feels sluggish. Fix the motion
- Make our animations respect prefers-reduced-motion without losing feedback
Use it when
- Adding micro-interactions or component transitions
- Motion feels slow, floaty, bouncy, or janky
- Animating page changes or list reorders
- Reviewing animations for accessibility
Skip it when
- You need GSAP timelines or scroll-driven storytelling implementation, use web-animations instead
- The jank comes from heavy React re-renders rather than animation, use react-rerenders instead
How to check the result
A 4x CPU-throttled performance trace shows no long frames, rapid toggling reverses smoothly from the current position, and reduced-motion emulation removes all travel.
The common mistake it prevents
Writing transition: all 0.3s ease everywhere, which animates unintended properties like theme colors and uses the wrong easing for both entering and exiting.
Motion is done when every animation has a job (feedback, orientation, continuity, or attention), runs within the duration budget for its size, uses easing that matches its direction, can be interrupted mid-flight, stays at 60fps on a throttled CPU, and has a reduced-motion alternative. If removing an animation loses no information, remove it.
Ground rules
- Motion explains change. It shows where something came from, where it went, or that an action registered. Decoration-only motion is a cost.
- Fast by default. Most UI motion lives between 120 and 300ms; anything over 500ms in product UI feels like waiting.
- Animate
transformandopacityonly. Layout properties (width,height,top,margin) trigger layout every frame. - Exits are faster than entrances (about 70–80% of the enter duration): users have already decided to move on.
- State-change animations must be interruptible: use CSS transitions or spring/physics libraries that retarget, not fixed keyframe sequences.
Avoid the generic look
| Default reflex | Better |
|---|---|
| Every section fades up 40px on scroll | One reveal per section at most, 8–16px travel, or none for content-heavy pages |
hover: scale(1.05) on every card | Subtle elevation or border change; scale only for media thumbnails |
| Bouncy springs on everything | Critically damped (no bounce) for UI; small bounce (0.1–0.25) only for playful, direct-manipulation moments |
| 600–1000ms durations | 150–300ms; the larger the element and the farther it moves, the longer |
transition: all 0.3s ease | Name properties; pick an easing for the direction |
| Spinner for every wait | Skeletons for content, inline progress for actions; no indicator under ~300ms |
Duration budget
| What moves | Duration (enter) | Notes |
|---|---|---|
| Color/opacity change, hover, press | 100–150ms | Press feedback ≤ 100ms |
| Small elements: tooltip, checkbox, toggle, chip | 150–200ms | Tooltip show delay 300–500ms, hide immediately |
| Medium: dropdown, popover, toast | 200–250ms | Exit 150ms |
| Large: modal, drawer, bottom sheet | 250–350ms | Exit 200ms; backdrop fades with it |
| Page / route transition, shared element | 300–450ms | Keep under 500ms |
| Stagger between list items | 20–50ms each | Cap total stagger at ~300ms (animate first 6–8 items, rest instantly) |
On mobile, durations can be ~20% shorter for small elements; on large screens, large movements may need ~20% longer to avoid feeling abrupt.
Easing
| Direction | Easing | CSS |
|---|---|---|
| Entering (appear, expand) | Decelerate (ease-out) | cubic-bezier(0.16, 1, 0.3, 1) (strong) or cubic-bezier(0.2, 0, 0, 1) |
| Exiting (dismiss, collapse) | Accelerate (ease-in) | cubic-bezier(0.4, 0, 1, 1) |
| Moving on screen (A → B) | Standard (ease-in-out) | cubic-bezier(0.4, 0, 0.2, 1) |
| Continuous (progress, spinners) | Linear | linear |
| Direct manipulation release (drag, swipe) | Spring | Library spring, velocity-aware |
Store these as tokens (--ease-enter, --ease-exit, --ease-move,
--duration-fast: 150ms, --duration-base: 250ms, --duration-slow: 350ms).
Workflow
- [ ] 1. List state changes in the feature; mark which need motion and why
- [ ] 2. Assign duration and easing from the tables
- [ ] 3. Choose the mechanism (CSS transition, @starting-style, View Transition, library)
- [ ] 4. Implement with transform/opacity; make it interruptible
- [ ] 5. Add reduced-motion alternative
- [ ] 6. Verify timing, jank, and interruption
3. Choose the mechanism
| Need | Mechanism |
|---|---|
| Hover, focus, toggle, state classes | CSS transition on named properties |
Element entering the DOM or leaving display:none | CSS @starting-style + transition-behavior: allow-discrete |
| Dialog / popover open-close | Native <dialog> / popover + @starting-style and ::backdrop |
| Route change or list reorder in one document | View Transitions API document.startViewTransition() |
| Multi-page site navigation | Cross-document view transitions @view-transition { navigation: auto; } |
| Gestures, springs, layout animation in React | Motion (motion/react) |
| Complex timelines, scroll storytelling | GSAP or CSS scroll-driven animations (see web-animations) |
Entry animation without JS:
.toast {
transition: opacity 200ms var(--ease-enter), transform 200ms var(--ease-enter),
display 200ms allow-discrete, overlay 200ms allow-discrete;
opacity: 1; transform: translateY(0);
}
@starting-style { .toast { opacity: 0; transform: translateY(8px); } }
.toast[hidden] { display: none; opacity: 0; transform: translateY(4px);
transition-duration: 150ms; transition-timing-function: var(--ease-exit); }
View transition for a state update:
function update(mutateDom) {
if (!document.startViewTransition) return mutateDom();
document.startViewTransition(mutateDom);
}
::view-transition-old(root), ::view-transition-new(root) { animation-duration: 250ms; }
.product-image { view-transition-name: product-hero; } /* unique per page */
Motion (React) spring that settles in a predictable time:
import { motion } from "motion/react";
<motion.div layout transition={{ type: "spring", visualDuration: 0.3, bounce: 0.15 }} />
5. Reduced motion
Reduce, do not delete: keep feedback, remove travel, parallax, zoom, and autoplay.
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 1ms !important; animation-iteration-count: 1 !important;
transition-duration: 1ms !important; scroll-behavior: auto !important;
}
.toast { transition: opacity 150ms linear; } /* keep a gentle fade where it helps */
}
import { useReducedMotion } from "motion/react";
const reduce = useReducedMotion();
const variants = { hidden: { opacity: 0, y: reduce ? 0 : 12 }, shown: { opacity: 1, y: 0 } };
Accessibility requirements:
- Anything that moves, blinks, or scrolls automatically for more than 5 seconds needs a pause/stop control (WCAG 2.2.2).
- Nothing flashes more than three times per second (WCAG 2.3.1).
- Motion triggered by interaction can be disabled (WCAG 2.3.3, AAA; honoring
prefers-reduced-motioncovers it in practice).
Micro-interaction recipes
| Interaction | Spec |
|---|---|
| Button press | transform: scale(0.97) on :active, 100ms ease-out; no scale on hover |
| Toggle switch | Thumb translateX, 150ms standard easing; color crossfade same timing |
| Dropdown | opacity 0→1 + translateY(-4px → 0) + scale(0.98 → 1), origin at trigger, 180ms enter / 120ms exit |
| Modal | Backdrop fade 200ms; panel opacity + translateY(8px) or scale(0.96 → 1) 250ms enter / 180ms exit |
| Drawer | translateX(100% → 0) 300ms enter / 220ms exit |
| Toast | Slide 8–16px from its edge + fade, 200ms; auto-dismiss ≥ 5s, pause on hover/focus |
| Accordion | Animate a grid row grid-template-rows: 0fr → 1fr (or interpolate-size: allow-keywords where supported) 200–250ms |
| Success check | Stroke draw 250–300ms after the action resolves; not on every save |
| Skeleton shimmer | 1.2–1.6s linear loop, low contrast; respect reduced motion (static) |
Gotchas
transition: allanimates properties you did not intend (including color on theme switch) and costs performance.- Animating
height: autodirectly does not work in most browsers; use the grid-rows trick, FLIP, or a library layout animation. will-changeon many elements wastes GPU memory; add it just before an animation and remove it after, or skip it.- Keyframe animations restart from the beginning when re-triggered; toggling quickly produces jumps. Use transitions for reversible states.
- Animating on page load delays LCP-visible content: never hide the hero behind an entrance animation.
- Each
view-transition-namemust be unique on the page at transition time.
Verification
- DevTools Performance with 4× CPU throttle: no long frames (> 16.7ms) during the animation; no Layout/Recalculate Style per frame for transform-only animations.
- DevTools Animations panel: inspect and slow down to 10% to check easing and choreography.
- Interrupt test: toggle every animated state rapidly; it reverses smoothly from the current position.
- Emulate
prefers-reduced-motion: reduce(DevTools Rendering): no travel, parallax, or autoplay; feedback still visible. - Inventory: every animation in the feature has a stated purpose in the PR description.
About this skill
Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.