Communitygithub.com

nkurunziza-saddy/motion

Builds motion for product UI on the web. It decides whether something should move at all, then why, which tool (CSS transition, WAAPI, Motion, spring), which properties, curve, duration, interruption, exit and reduced-motion path, and it writes the code. Also covers stagger and choreography, gestures (drag, flick, sheets), motion tokens and animation performance.

What is motion?

motion is a Claude Code agent skill that builds motion for product UI on the web. It decides whether something should move at all, then why, which tool (CSS transition, WAAPI, Motion, spring), which properties, curve, duration, interruption, exit and reduced-motion path, and it writes the code. Also covers stagger and choreography, gestures (drag, flick, sheets), motion tokens and animation performance.

Works with~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/nkurunziza-saddy/hmm-interfaces/tree/HEAD/motion

Ask in your favorite AI

Open a new chat with this agent skill pre-loaded.

Documentation

What does motion do?

This skill builds motion. It extends the motion section of product-ui/references/values.md, which holds the settled numbers (easing curves, press scale, bounce, stagger, the duration table, toast and layout durations, travel distances, the exit-to-entrance ratio). Those numbers are not repeated here; this skill adds the order of decisions, the tokens, the recipes and the failure modes.

Before any decision, apply product-ui §1 (the taste journal and precedence) and §5 (which skill owns what).

The decision order

Walk these top to bottom. Each step can end the job. Zero lines of animation is a valid result, and you say so.

1. Should it move? The frequency gate

Ask how many times a day one user sees this. Tiers are in values.md. In practice:

  • Initiated by keyboard, or seen 100+ times a day: no motion. Why: repeated motion turns into delay.
  • Seen tens of times a day (hover, tab switch, row select): none, or an instant colour change.
  • Data the user is reading or acting on (a number in a table, a chart in a live dashboard) never moves for style. Why: motion pulls the eye off the thing being read.
  • If the gate says no, write no animation. Say which tier it hit, and offer the static alternative (an instant state change).

2. Name the purpose

Pick one word: spatial (where it came from or went), state (this changed), feedback (I heard you), continuity (prevents a jump), explanation (rare moments only). Cannot name one: stop. Why: "it looks nice" on a frequent element is the main source of slow-feeling UI.

3. Pick the tool: the cheapest that works

NeedTool
A state toggled by class, attribute, hover or pressCSS transition
Entering from display: none, popover or dialog@starting-style + transition-behavior: allow-discrete
A one-shot sequence, stagger, or motion that must run while the page is busyCSS @keyframes
JS decides when; you need cancel, reverse, finished, or FLIPWAAPI el.animate()
Springs, velocity handoff, exit of an unmounting React node, shared layoutMotion (motion/react)

If the thing is a whole component (menu, toast, drawer), use a library that ships focus handling, and ask pick-library. Never install Motion for a fade. Why: CSS runs off the main thread and costs no bundle.

4. Properties

  • Animate transform and opacity; filter only for a ≤ 2px swap blur. Name every property; never transition: all.
  • Height and layout changes: see references/recipes.md (grid rows, FLIP). Never animate width, top, left or margin to move something. Why: each forces layout on every frame.
  • Never animate box-shadow. Fade a pseudo-element's opacity instead.
  • translate percentages are relative to the element's own size, so translateY(100%) fits drawers and toasts of any height.

5. Curve and duration, matched on usage

Never pick the number nearest to your guess. State the usage ("menu close", "icon swap"), then take that row from references/tokens.md. If no row matches the usage, add a new token named for the usage; don't bend an existing one. Why: a token is a statement about what the motion is for, so a near number with the wrong usage is the wrong token.

  • Entering or exiting: --ease-out. Moving on screen: --ease-in-out. Drawers: --ease-drawer. Colour: ease. Spinners: linear. Never ease-in.
  • Anything draggable or interruptible mid-flight uses a spring, not a curve. Why: a curve cannot accept the velocity the user's hand had.
  • Slow feeling: cut duration first, then the distance, and only then touch the curve.

6. Interruption

  • Anything the user can trigger twice in a second is a transition or a spring, never @keyframes. Why: a transition retargets from the current value; keyframes restart and jump.
  • WAAPI: cancel the old animation after reading the current value, and start the new one from it (references/recipes.md).
  • A toggle spammed 10 times must end in the right state with no jump. Test it.

7. Exit

  • Default exit is a fade, shorter than the entrance (the tokens file has each pair).
  • Anything with a direction (drawer, sheet, swiped toast) leaves the way it came.
  • The exiting element stops taking up space at once, so siblings close the gap while it fades.
  • Exits are simultaneous and never staggered. Why: the user already decided to leave.

8. Reduced motion, shipped with the animation

Opacity and colour stay. Slides, scale, blur and bounce go; slides become fades, bounce becomes 0. Never use motion as the only cue. Pattern in references/recipes.md.

Choreography

Cast every moving element before timing it:

RoleTreatment
Lead (the one thing to notice)Moves first, travels most
SupportFollows, travels less
DecorationFades in place, or nothing
Static (containers, chrome)Never moves
  • One entrance per container. If the panel slides in, its content is already there. Why: two layered entrances make the user wait twice.
  • Order by importance, not DOM order. Delay stays at the values.md steps (40ms between items, 100ms between title, text and actions). Vary distance and duration by role rather than the delay.
  • Cap a list stagger at index 6 (a 240ms last delay); items past it share that delay. Input works while it plays.
  • Never stagger a menu, command palette, tab content or any repeat open.
  • Stay still: anything the user is about to click, anything already on screen when the page loads again, and an update that lands within 150ms of the last one (jump to the end state).
  • Direction follows meaning: next enters from the right, back from the left; things the user triggered grow outward from the trigger.

Load when needed

  • references/tokens.md: load for any exact duration, distance, scale, blur, easing or spring value, and for tokenising an existing codebase.
  • references/recipes.md: load when building a menu, popover, dialog, drawer, toast, list entrance, icon swap, height change, tab indicator or number change.
  • references/gestures.md: load for drag, swipe, flick, sheets, or anything that follows a pointer.
  • references/performance.md: load when motion drops frames, before reaching for will-change, and when updates arrive fast.

Pairs

BrokenFixedWhy
.menu { transition: all 300ms ease-in }transition: opacity 200ms, transform 200ms var(--ease-out)Named properties; ease-in is sluggish; menus ≤ 200ms
scale(0) to scale(1)scale(0.95) and opacity: 0 to 1Nothing appears from nothing
Popover scales from its centretransform-origin: var(--transform-origin)It should grow out of its trigger
.open { animation: slide-in 300ms }transition: transform on the toggled stateKeyframes snap when interrupted
Every row animation-delay: i * 120msmin(i, 6) * 40msThe last row arrives under 300ms
Drawer fades out after sliding inIt slides back out the way it cameDirection is the explanation
Drag release on a fixed ease-outA spring seeded with the release velocityThe hand's momentum carries on
setY(y) in pointermoveel.style.transform = ...No re-render per frame

Before you finish

Search the diff, then verify.

Search forFix
transition: all, will-change on *, ease-inNamed properties, remove, --ease-out
scale(0), transform-origin: center on a popover0.95, the trigger origin (dialogs stay centred)
@keyframes on a class that togglesA transition
A duration over its row in tokens.md, or a number not in tokens.mdThe usage's token
Motion on keydown handlers or a paletteRemove it
width, height, top, left, box-shadow in a transitiontransform, opacity, or the recipe
No prefers-reduced-motion pathAdd it

Verify in the running app: play it at 4× (DevTools Animations panel, or multiply durations), spam any toggle, test with reduced motion on, and throttle the CPU 4×. Say which of these you could not run.

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

nkurunziza-saddy/break

Stress-tests a piece of UI with worst-case data and every reachable state, then reports what broke with a numbered fix for each. Use it when the user types /break, or asks to "break this", "stress-test", "try the worst case", "what happens with long names", "check the empty and error states" or "find the edge cases" on a component or screen.

nkurunziza-saddy/layout

Structure and space in product UI on the web. It sets the spacing scale, grouping, alignment, density (compact or comfortable), hit areas, app shells (sidebar, toolbar, content, inspector), max widths, container-query responsiveness, dropdown and popover sizing, room for long and translated text, layout stability (no shift on load) and sticky elements that must not cover focus.

nkurunziza-saddy/pick-library

Picks the right library for a frontend task from a curated, status-checked list, instead of hand-rolling it or installing an abandoned package. Use it when the user asks "what should I use for…", "is there a library for…", or names a package, and when you are about to build a toast, command menu, drawer, date picker, table, virtual list, drag and drop, chart, OTP input, animated number, theme switch, form validation…

nkurunziza-saddy/surfaces

What product UI is made of on the web. It sets elevation levels with exact shadow recipes for light and dark mode, when to use a shadow and when a border, the transparent border for forced-colours mode, a small radius scale with concentric nesting, image outlines, every interactive state as a surface change (hover, pressed, selected, disabled, focus-visible), icons (one set, currentColor, outline and fill, stroke tha…

Related Skills