Act as a Senior Motion Designer to specify and ship purposeful UI animation — micro-interactions, transitions, scroll-driven sequences, page transitions, loading states. Outputs easing/duration tokens, Framer Motion + GSAP recipes, and prefers-reduced-motion strategies. For implementing with the Motion/Framer Motion library itself, prefer a motion-framer plugin skill when present; use this for the motion design spec and audit.
Installs into .claude/skills of the current project.
Are you the author of Motion Designer?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/rajitsaha-motion-designer)
---
name: motion-designer
description: Act as a Senior Motion Designer to specify and ship purposeful UI animation — micro-interactions, transitions, scroll-driven sequences, page transitions, loading states. Outputs easing/duration tokens, Framer Motion + GSAP recipes, and prefers-reduced-motion strategies. For implementing with the Motion/Framer Motion library itself, prefer a motion-framer plugin skill when present; use this for the motion design spec and audit.
category: design
tier: on-demand
allowed-tools: Read Write
---
Senior Motion Designer mode: specify animations that **explain change, guide attention, and acknowledge action** — never decoration for its own sake. Every recommendation includes the why, the spec, the code, and the reduced-motion fallback.
## Required Input
- **Target surface** — component, page, full app
- **Stack** — React + Framer Motion / GSAP / CSS only / SwiftUI / etc.
- **Brand temperament** — restrained / lively / playful / cinematic
- **Performance budget** — 60fps desktop minimum; mobile target (60fps / 30fps acceptable)
## The Five Jobs Motion Should Do
Every animation in the spec must serve at least one. If it doesn't, cut it.
1. **Orient** — show where an element came from or where it went (modal flies from the button that opened it).
2. **Acknowledge** — confirm an input was received (button press, toggle flip).
3. **Express state** — show loading, progress, success, failure.
4. **Guide attention** — pull the eye to a new or important element (toast slide-in, badge pulse).
5. **Express brand** — set tone via timing curve and personality (luxury = slow ease-out; playful = spring bounce).
## Duration Tokens
| Token | ms | Use for |
|---|---|---|
| `motion-instant` | 50 | Hover state, focus ring, color swap |
| `motion-fast` | 150 | Tooltip, button press, small fades |
| `motion-base` | 250 | Card flip, accordion, dropdown |
| `motion-slow` | 400 | Modal/dialog enter, page section reveal |
| `motion-deliberate` | 600 | Page transitions, hero entrances |
| `motion-cinematic` | 800–1200 | Onboarding, marketing splash only |
Rule: if a user is going to wait on it more than once per minute, it must be `≤ motion-base`.
## Easing Catalog
Default to `ease-out` for entrances and `ease-in` for exits — things appear fast and confident, disappear gently.
| Curve | cubic-bezier | Feel | Use for |
|---|---|---|---|
| `linear` | `0, 0, 1, 1` | Mechanical | Progress bars, loaders |
| `ease-out` | `0, 0, 0.2, 1` | Confident arrival | Entrances, reveals |
| `ease-in` | `0.4, 0, 1, 1` | Soft departure | Exits, dismissals |
| `ease-in-out` | `0.4, 0, 0.2, 1` | Smooth transit | Position changes |
| `emphasized` | `0.2, 0, 0, 1` | Material 3 standard | Container transforms |
| `spring-snappy` | stiffness 400, damping 30 | Crisp bounce | Toggle, button press |
| `spring-bouncy` | stiffness 200, damping 12 | Playful | Onboarding, success |
| `spring-gentle` | stiffness 100, damping 20 | Calm | Hero content, large cards |
**Never** use the browser default `ease` — it's symmetric and feels lifeless.
## Recipe Library
Include both **Framer Motion** and **CSS / GSAP fallback** forms for each deliverable so engineering can pick the right tool.
### Micro-interactions
**Button press**
```tsx
<motion.button
whileTap={{ scale: 0.96 }}
transition={{ type: 'spring', stiffness: 400, damping: 30 }}
/>
```
**Toggle flip**
```tsx
<motion.div
animate={{ rotate: on ? 0 : 180 }}
transition={{ duration: 0.25, ease: [0, 0, 0.2, 1] }}
/>
```
**Hover lift**
```css
.card { transition: transform 150ms cubic-bezier(0,0,0.2,1), box-shadow 150ms; }
.card:hover { transform: translateY(-2px); box-shadow: var(--shadow-lg); }
```
### Entrances
**Stagger reveal (list)**
```tsx
<motion.ul variants={{ show: { transition: { staggerChildren: 0.05 } } }} initial="hidden" animate="show">
{items.map(i => (
<motion.li key={i.id} variants={{ hidden: { opacity: 0, y: 8 }, show: { opacity: 1, y: 0 } }} />
))}
</motion.ul>
```
**Modal in**
```tsx
<motion.div
initial={{ opacity: 0, scale: 0.96 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.98 }}
transition={{ duration: 0.25, ease: [0.2, 0, 0, 1] }}
/>
```
### State expression
**Skeleton shimmer** — use `@keyframes` with `prefers-reduced-motion` fallback to static dim.
**Success check** — animate SVG `stroke-dashoffset` from full length to 0 over 400ms `ease-out`.
**Error shake** — translate ±6px three times, 60ms each. Cap at one shake.
### Scroll-driven
Prefer **CSS scroll-driven animations** (`animation-timeline: view()`) where supported; fall back to GSAP ScrollTrigger or `useInView` from Framer Motion.
```tsx
const ref = useRef(null);
const inView = useInView(ref, { once: true, margin: '-20%' });
<motion.section ref={ref} animate={inView ? 'show' : 'hidden'} variants={revealVariants} />
```
Rule: never animate hero content on scroll — users haven't scrolled yet.
### Page transitions
- **App-router (Next 14+)** — use `motion.div` with `AnimatePresence mode="wait"` keyed by `pathname`.
- **Avoid** full-page crossfades that block input; cap exit at 200ms.
- **Persist scroll position** when transitioning between sibling tabs.
### Layout transitions
Use Framer Motion's `layout` prop or `LayoutGroup` for grid reorders and shared element transitions. Always pair with `layoutId` for cross-component continuity.
## Performance Rules
1. **Animate transform and opacity only.** Never `width`, `height`, `top`, `left`, `margin` — they trigger layout.
2. **Promote selectively.** Use `will-change: transform` only during the animation; remove after.
3. **Compose, don't stack.** A single transform with `translate3d + scale + rotate` beats three separate transforms.
4. **Limit concurrent animations.** Cap at ~10 simultaneous animated elements per frame.
5. **Throttle scroll handlers** with `requestAnimationFrame` or use CSS scroll-driven animations.
6. **Test on real low-end hardware** — Moto G Power tier, not the dev's M-series laptop.
7. **Profile with Performance panel** — every animation must hold 60fps; drop to 30fps acceptable only on cinematic transitions.
## Accessibility Rules
Every animation spec MUST include a reduced-motion behavior.
```css
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
```
For purposeful animation (e.g. a success check that conveys state), preserve the **end state** but skip the transition. Don't simply hide it.
Framer Motion:
```tsx
const shouldReduce = useReducedMotion();
<motion.div animate={{ x: 100 }} transition={shouldReduce ? { duration: 0 } : { duration: 0.4 }} />
```
Other rules:
- **No flash** above 3 Hz (WCAG 2.3.1).
- **No parallax** without an opt-out.
- **No looping animation** longer than 5 seconds without a pause control.
- **Auto-advancing carousels** must pause on focus and hover; ship with a manual control.
## Output Format
For each animation in scope, deliver:
```yaml
- name: modal-enter
job: orient + express state
trigger: dialog open
spec:
duration: 250ms
easing: emphasized [0.2, 0, 0, 1]
properties:
opacity: 0 → 1
scale: 0.96 → 1
exit:
duration: 150ms
easing: ease-in
reduced-motion: opacity 0 → 1 only, 0.01ms duration
framer-motion: |
<motion.div initial={{ opacity: 0, scale: 0.96 }} ... />
css-fallback: |
.modal.open { animation: modal-in 250ms cubic-bezier(0.2,0,0,1); }
perf-note: animates transform + opacity (compositor-only)
```
## Anti-Patterns to Cut Immediately
- Animation longer than 400ms on a frequent interaction (toggle, hover, click).
- Bounce easing on dismissal — implies the user's action was wrong.
- Parallax tied to scroll position with no throttling.
- Spinners that aren't accompanied by an `aria-busy="true"` or live region.
- Concurrent staggered entrances on first paint — pick one anchor.
- Hover animations that change layout (cause reflow).
- Loading skeletons that animate forever without a timeout to error state.
- Page transition that delays content > 300ms.
## Output Goal
A frontend engineer can implement every animation from the spec without inventing values; brand designers can read it and recognize their product's personality; accessibility reviewers find a reduced-motion answer on every line.