mcpField
M
motion

motion

github.com/paragonhqDesign & UI044 installsUpdated 5w ago
Repository

About motion

A design skill for choosing purposeful interface motion, with guidance on timing, properties, easing, accessibility, and performance. It is for people building product interfaces who need to decide when animation helps and how to implement it with CSS, Motion, or GSAP.

Key features

  1. 01Frequency-based motion guidance
  2. 02Rules for animation purpose and timing
  3. 03CSS, Motion, and GSAP selection
  4. 04Property and easing recommendations
  5. 05Reduced-motion guidance
  6. 06Implementation recipes and anti-patterns

Use cases

  1. 01Choosing motion for menus and popovers
  2. 02Setting transitions for dialogs and toasts
  3. 03Animating layout changes and tab indicators
  4. 04Making interface motion respond to reduced-motion settings

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/paragonhq/stealth-skills --skill motion

After installing, type/motionin Claude Code to confirm it loaded.

Details

From the project README

motion

Motion is the most abused tool in interface work because it is the most visible. This skill exists to make you write less of it, and to make what remains correct.

Before writing a line

Answer these in order. Each gate can end the task with zero code, and that is a good outcome.

1. How often will they see it?
SeenRule
Every few seconds (typing, list navigation, hover on rows)Nothing, or under 120ms and barely visible
Many times a day (menus, palettes, tabs, toggles)Fast feedback only, 120–200ms, no delight
A few times a day (dialogs, drawers, sheets, toasts)Full entrance and exit, 180–320ms
Rarely (onboarding, success, first run)The only place a longer, expressive animation belongs

Keyboard-initiated actions are instant. ⌘K, Escape, j/k, tab switching by shortcut: render the new state on the same frame. Motion here reads as latency.

2. What is it for?

Name one. If you cannot, stop.

  • Acknowledge — the press, the toggle, the drop. Proof the interface heard them.
  • Locate — where a thing came from or went. A popover grows from its trigger; a toast leaves the way it came.
  • Continue — bridge two layouts so content does not teleport. Reordering, expanding, tab indicators.
  • Signal — a state changed while they were not looking. A saved badge, a counter roll.
  • Explain — show how something works. Marketing and onboarding only.

"Looks alive" is not a purpose. "Everything else has an animation" is not a purpose.

3. Which tool?

Cheapest that does the job, in this order:

NeedUse
Hover, focus, press, colour, simple enterCSS transition
Looping or multi-step, no interruption neededCSS @keyframes
Enter and exit of mounted React elements, layout changes, springs, interruptionMotion (motion/react)
Timelines, scrub, pin, split text, sequenced scenesGSAP
Scroll-linkedCSS scroll-driven animations first, ScrollTrigger when they cannot

Do not install a library for a fade. Do not hand-roll exit animations with setTimeout when Motion's AnimatePresence exists in the project.

4. Which properties?

transform and opacity. Both are composited; nothing else is reliably.

WantDoNot
Appearopacity 0→1 + scale .96→1 or translateY 6px→0scale(0), display swap alone
Grow to contentMeasure and animate height with overflow: hidden, or grid-template-rows: 0fr→1franimating max-height: 9999px
Reveal along an edgeclip-path: inset(...)width
Blur infilter: blur(6px→0) paired with opacity, shortblur alone, blur on large areas
Move between positionstransform via layout animation (layout in Motion, FLIP in GSAP)animating top/left
Colourbackground-color, color, border-color with transitionfilter: hue-rotate

Never transition: all. List the properties. It is faster and it stops surprises.

5. Which curve?
SituationCurve
Entering the screenstrong ease-out: cubic-bezier(0.16, 1, 0.3, 1)
Leaving the screenease-in is acceptable here only, or the same ease-out at 70% duration
Moving while on screen (reorder, tab indicator, resize)ease-in-out: cubic-bezier(0.76, 0, 0.24, 1)
Hover and colourease or a soft ease-out, never expo
Continuous (marquee, spinner, progress)linear
Follows a gesture or a pointera spring, not a curve
iOS-style sheet or drawercubic-bezier(0.32, 0.72, 0, 1)

Built-in ease-in-out and ease-out are too weak to feel intentional. Use the custom curves above and store them as tokens: --ease-out-expo, --ease-in-out-quart.

Never ease-in on an entrance. The user watches the first 50ms hardest; ease-in wastes them.

6. How long?
ElementDuration
Press feedback100–140ms
Hover state120–180ms
Tooltip, small popover140–200ms
Menu, dropdown, select160–220ms
Tab indicator, toggle thumb180–260ms
Dialog, sheet, drawer220–320ms
Toast enter240–320ms; exit 180ms
Page or view transition300–450ms
Marketing reveal600–1200ms, once

Exits run at roughly 70% of the entrance. Under 100ms is invisible; over 400ms in product UI is a wait. When in doubt, shorten by 20%.

7. Springs

For anything the user drags, throws, or that must feel physical:

FeelMotion config
Snappy UI (indicators, chips, small moves){ type: "spring", stiffness: 520, damping: 38, mass: 0.7 }
Soft (cards, panels, list reorder){ type: "spring", stiffness: 260, damping: 28, mass: 0.9 }
Following the pointer (hover highlight that glides){ type: "spring", stiffness: 650, damping: 45, mass: 0.5 }
Sheet settle after release{ type: "spring", stiffness: 300, damping: 30 } or bounce: 0 with duration: 0.4

Bounce is almost never right in product UI. A spring should overshoot by a pixel or two at most; if it wobbles, raise damping.

8. Where from?

Set transform-origin to the trigger. A popover from a button in the top-right grows from its top-right corner. Dialogs are the exception: they are centred and scale from centre. Toasts come from the edge they will leave by.

9. Staggers

Stagger only lists that arrive together, once. 12–25ms between items, capped: after the eighth item, no more delay, or the tail arrives after the user has started reading. Never stagger on re-render or filter changes.

10. Interruption

A user who closes a menu while it is still opening must see it reverse from where it is, not finish opening then close. CSS transitions do this by default. Keyframes do not. Motion does. If you used keyframes for something interruptible, you used the wrong tool.

11. Exit

Exits are deliberate: shorter, simpler, often just opacity. A dialog that fades and scales in can fade only on the way out. Nothing should be visible for even a frame after the user dismissed it and then start disappearing; the first frame after the click is already changed.

12. Reduced motion

Ship it in the same change:

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

In Motion, useReducedMotion() and swap transforms for opacity. Smooth scrolling libraries are disabled entirely. Everything that would have animated in is simply visible.

Recipes

Menu / popover
.menu {
  transform-origin: var(--origin, top left);
  transition: opacity 160ms var(--ease-out-expo), transform 160ms var(--ease-out-expo);
}
.menu[data-state="closed"] { opacity: 0; transform: scale(0.96) translateY(-2px); }
.menu[data-state="open"] { opacity: 1; transform: none; }
Dialog
<motion.div
  initial={{ opacity: 0, scale: 0.97, y: 6 }}
  animate={{ opacity: 1, scale: 1, y: 0 }}
  exit={{ opacity: 0, transition: { duration: 0.15 } }}
  transition={{ duration: 0.26, ease: [0.16, 1, 0.3, 1] }}
/>

Backdrop fades separately, slightly longer on the way in, shorter on the way out.

Toast

Enter from the edge it lives on, ease-out, ~280ms. Stack older toasts behind with scale(0.95) and translateY(-8px), dimmed. Exit toward the same edge at ~180ms. Pause timers on hover and when the tab is hidden.

Height to content
.grid { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 240ms var(--ease-in-out-quart); }
.grid[data-open] { grid-template-rows: 1fr; }
.grid > * { overflow: hidden; min-height: 0; }
Tab indicator

One element with layoutId (Motion) sprung between tabs; never animate left/width on each tab.

Hover highlight that glides between rows

One absolutely positioned pill, position updated on pointerenter, animated with the "following the pointer" spring. Remove on pointerleave with a 150ms fade.

Number change

Animate digits rolling, not the whole number fading. Use a library that handles digit columns (NumberFlow) rather than writing it.

Text reveal (marketing only)

Split into words, opacity 0→1, y 6→0, filter blur(6px→0), 30ms between words, once, on first view.

Anti-patterns

Seen in generated UIInstead
transition: all 0.3s easenamed properties, 160–220ms, custom ease-out
animate-bounce on an iconnothing
Every card lifting -4px on hover with a shadowone of: border brightens, background lifts one step
Skeleton shimmer at 1.5sstatic skeleton, or a slow 2.5s pulse
Page-load entrance on every elementat most the title and one region, once
Spinner for everythingskeleton for content, spinner only for actions
duration: 0.5 for a dropdown0.18
Parallax on product UInever

Performance

will-change: transform only on elements about to animate, removed after. Avoid animating anything with backdrop-filter. Large blurs are expensive; keep radius under 8px and area small. Test on a throttled CPU: if it drops frames, cut the property, not the duration.

From the catalogue

Comments

Markdown supported