Implementation-agnostic motion judgment for product UI. Load when you are adding, reviewing, or tuning animation in any framework - durations, easings, springs, staggers, entrances/exits, gesture handoff, reduced motion - when a spec says "smooth" or "delightful" with no numbers attached, or when you notice animation being judged by feel instead of against a budget. NOT for debugging one animation library's API (use its docs), for static visual decisions (ui-design-craft), or for running a de...
Scanned 10/1/2026
npx -y skills add F-e-u-e-r/opus-pack --skill motion-craft --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Motion Craft?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/f-e-u-e-r-motion-craft)More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.
---
name: motion-craft
description: Implementation-agnostic motion judgment for product UI. Load when you are adding, reviewing, or tuning animation in any framework - durations, easings, springs, staggers, entrances/exits, gesture handoff, reduced motion - when a spec says "smooth" or "delightful" with no numbers attached, or when you notice animation being judged by feel instead of against a budget. NOT for debugging one animation library's API (use its docs), for static visual decisions (ui-design-craft), or for running a design review end-to-end (design-review-gate).
---
# Motion Craft
Every animation must serve **feedback** ("it worked"), **continuity** ("that's
where it went"), or **hierarchy** ("look here"). An animation serving none of
the three is removed, not tuned. When in doubt: shorter, subtler, or none.
## Corpus-derived semantic references (authoritative)
Motion semantics are bridged to the Phase-B design corpus. Load
[motion rules](references/generated/motion.md) and, when a rule is selector- or
dial-gated, [controls](references/generated/controls.md) — only when a task needs
them; they stay out of context until then. **Where a rule below overlaps a
generated rule, the shard is the authoritative semantic source.** Authority order:
**canonical corpus (shard) > pack-local extensions (this file's ms budgets, easing
curves, and spring formulas) > orchestration prose**; an extension may add or
tighten, never weaken a corpus rule. Extension registry / classification:
`design-pack/generated/local-extensions.json`,
`design-pack/normative-classification.json`. Generated shards are never
hand-edited — regenerate via `design-pack/tools/project_corpus.py`.
## 1. Duration budgets
Numbers first; taste second. Defaults for product UI:
| Surface | Budget |
|---|---|
| Hover / press feedback | 90-160ms |
| Tooltip, micro popover | 80-200ms |
| Dropdown, select, small panel | 150-250ms |
| Accordion, tab switch | 160-240ms |
| Card / list item entrance | 180-350ms |
| Modal, drawer, sheet | 200-400ms |
| Route / page transition | 240-500ms |
- **Standard interactions stay under 300ms; nothing in product UI exceeds
500ms.** A 180ms dropdown reads as more responsive than a 400ms one.
- **Exit = 65-75% of entrance.** Leaving is confirmation, not an event.
The rows above bound ENTRANCES; a rule-derived exit may drop below its
row - that is the intent, not a violation.
- **Distance scales duration:** 100px is the base; 200px ~1.3x, 400px ~1.6x,
full-screen ~1.8-2.0x. Small moves at long durations read as lag.
- **Frequency scales duration DOWN.** An action done 100 times a day
approaches instant. Hard sub-rule: **never animate keyboard-initiated
actions** - they repeat hundreds of times daily and animation makes them
feel disconnected from the keystroke.
❌ "the command palette fades in over 250ms - it looks polished in the demo."
✅ keyboard-opened palette appears instantly; its pointer-opened twin may
animate.
- Trigger for this section: any duration you are about to write, or any
existing duration outside the table. Done when every duration in the diff
sits inside its row's budget or carries a written justification.
## 2. Easing
| Direction | Curve | Why |
|---|---|---|
| Enter | ease-out | fast start = instant feedback, soft landing |
| Exit | ease-in | soft start, fast leave |
| On-screen move / morph | ease-in-out | smooth both ends |
| Spinners, progress, marquee | linear | mechanical processes read linear |
- **Never linear for finite point-to-point movement; never ease-in for
entrances.** Continuous mechanical motion - spinners, progress bars,
marquee loops - is the linear exception the table names, not a
contradiction of it. An ease-in dropdown at 300ms feels slower than
ease-out at the same 300ms: it delays movement in the exact frames the
user watches most closely.
- Named defaults: Material 3 standard `cubic-bezier(0.2, 0, 0, 1)`; M3
emphasized-decelerate `cubic-bezier(0.05, 0.7, 0.1, 1)`; Core Animation's
`kCAMediaTimingFunctionDefault` / CSS `ease` is `cubic-bezier(0.25, 0.1,
0.25, 1)` - Apple's HIG publishes principles, not bezier tokens, so any
"Apple HIG curve" value circulating online is reverse-engineered, not
vendor-published. Two mislabels to refuse: `(0.4, 0, 0.2, 1)` is
Material *2*'s standard routinely pasted as "M3", and `(0.25, 0.1, 0.25,
1)` labeled "the Apple HIG default" is the CSS `ease` curve wearing a
vendor costume.
## 3. Springs and gesture physics
- Spring parameters below are **Framer Motion-convention coefficients**
(stiffness + damping). Two unit traps: React Spring parameterizes as
tension/friction, not these; and a damping *ratio* (where <1.0
oscillates, 1.0 is critical) is a different unit again - an error that
has reached published tables (this pack's own LottieFiles source
conflates the two). Name which convention you are using.
- **Critical damping (no overshoot) for mass 1 is `damping = 2 *
sqrt(stiffness)`** - stiffness 400 needs damping 40, stiffness 300
needs ~35. The presets below are deliberately UNDERDAMPED (they
overshoot a little), which per the settle rule places them on
momentum-driven interactions only:
| Feel | Stiffness | Damping | Where it belongs |
|---|---|---|---|
| Snappy (mild overshoot) | ~400 | 25-30 | flick-driven UI |
| Standard (visible settle) | 250-350 | 18-24 | drag release |
| Bouncy (sparingly) | 150-250 | 10-15 | playful, earned |
- **Springs must settle.** Click-triggered UI defaults to critically
damped (use the formula above); overshoot is earned by a flick, never
by a click, and never appears on error paths. Prolonged wobble
("jello") is a defect, not personality.
- **Momentum projection** (where a flick should land): Apple ships the
exponential-decay form `projectedDistance = (velocity / 1000) * d / (1 - d)`
with `d ~= 0.998`, velocity in px per SECOND (the /1000 folds it onto the
per-millisecond decay step; the dismissal rule below uses px/ms - mixing
the two units is a 1000x error) - NOT the physics-textbook
`v^2 / (2 * deceleration)`.
- **Rubber-band overscroll:** `(overshoot * dimension * c) / (dimension + c *
|overshoot|)` with `c = 0.55` - resistance grows the further past the bound.
- **Velocity handoff:** when a gesture ends, the animation continues at the
finger's velocity: `relativeVelocity = gestureVelocity / (target -
current)` (libraries taking raw px/s can be handed the gesture velocity
directly). Guard the zero/near-zero displacement case: when `target -
current` is ~0 there is nothing to animate - skip the spring instead of
dividing by it. A visible seam between drag and animation is the tell
this rule was skipped.
- **Momentum dismissal: velocity OR distance - either alone suffices.** A
flick dismisses on velocity above ~0.11 px/ms (~110 px/s; the source's cheap
approximation is average `|distance| / elapsedMs`; prefer the gesture's
actual release velocity where the framework exposes it). A slow drag
past the distance threshold still dismisses - velocity-only would snap
a deliberate full drag back. The failure this rule exists to prevent is
requiring distance from a fast flick.
## 4. Choreography
- **Stagger:** 30-80ms between items; total sequence under 500ms; stagger is
decorative - it must never block interaction while playing.
- **One thing leads.** Everything animating at once with equal weight is
noise; pick the hero element, let the rest follow or fade.
- **Overshoot budget by context:** success 5-10%; generic feedback 2-5%;
celebration up to 25%; **errors and destructive confirmations 0%** - play
nothing bouncy on the worst moment of the user's session.
- **Asymmetric deliberate/response timing:** where the user is deciding
(press-and-hold, destructive confirm) the animation may run slow; the
system's response snaps. Symmetric timing on a press-and-release
interaction is a finding, not a style choice.
## 5. Performance traps
- `transform` and `opacity` are the workhorses; paint-only properties
(color, background-color, border-color) are acceptable for state
feedback. What is BANNED is animating layout-triggering properties
(width, height, top, left, margin, padding) - those are rewritten, not
accepted.
- **`transition: all` is banned.**
❌ `transition: all 300ms ease` - it animates every property that
happens to change, layout-affecting ones you never intended included,
plus whichever property a future edit adds; the failure signature is
surprise animations and layout work appearing when unrelated styles
change.
✅ `transition: transform 200ms ease-out, opacity 200ms ease-out`.
- Framer Motion's shorthand props (`x`, `y`, `scale`) animate on the main
thread via rAF rather than as compositor-run transforms (Kowalski's
production finding; the observable symptom is dropped frames while the
page is busy). Under load, use the full `transform` string.
- Updating an inheriting CSS variable on a parent recalculates style for
its children - the default for unregistered custom properties (a
registered `@property` with `inherits: false` sidesteps it) - so in a
drawer with many items, drive `transform` on the moving element directly
instead of `--offset` on the container.
- Animations are interruptible: new input retargets mid-flight (CSS
transitions and springs retarget; keyframes restart from zero - wrong for
toasts, toggles, anything triggered rapidly); no fill-mode or delay may
block interaction.
- **Never enter from `scale(0)`** - nothing real appears from nothing; enter
from `scale(0.9-0.97)` plus opacity.
- Set `transform-origin` for scale/rotate: popovers, dropdowns, and tooltips
scale from their trigger, not center (modals are the exemption - they stay
centered); SVG needs `transform-box: fill-box` on a wrapping `<g>`.
- Gate hover motion behind `@media (hover: hover) and (pointer: fine)` -
touch devices fire false hovers on tap.
## 6. Reduced motion is a floor, not a feature
- Every animation ships a `prefers-reduced-motion` variant. Reduced means
**fewer and gentler, not zero**: keep opacity/color transitions that aid
comprehension; drop movement - slide+fade -> fade; scale+fade -> fade;
spring -> short tween; parallax and auto-playing loops -> removed.
Transform-based full-screen motion is the highest-cost vestibular
trigger - it is exactly what this media query exists for.
- Motion is never the only signal of a state change: pair it with a static
affordance (color, position, label) or reduced-motion users miss the
change entirely.
## 7. Misquoted research - do not cite these wrong
_Change notice (Apache-2.0 section 4(b)): the correction items in this section
are adapted from `nexu-io/open-design` (`craft/animation-discipline.md`,
Apache-2.0) and were modified for Design Pack. License text:
`LICENSE-APACHE-2.0`; notices: `THIRD_PARTY_NOTICES.md` (both at the plugin
root, `design-pack/`)._
These corrections are adapted from open-design's primary-literature review
(Apache-2.0; adaptation notice in THIRD-PARTY-NOTICES). The papers were
NOT re-opened for this pack - each line below is that review's checked
claim, restated, not this pack's own reading of the source:
- "Skeleton screens feel 11% faster" - per that review, Harrison/Yeo/
Hudson (CHI 2010) measured backwards-decelerating ribbed *progress
bars* (n=16), not skeletons; the mechanism does not transfer.
- "Doherty threshold = 400ms" - per that review, the source contains no
400ms figure; the lowest measured threshold is 300ms. And cite the
source itself correctly: Doherty & Thadhani, "The Economic Value of
Rapid Response Time", IBM technical report GE20-0752 (1982) - the
"IBM Systems Journal 1982" venue and the "Thadani" spelling circulating
online (and in the review this pack adapted) are themselves
miscitations, corrected here.
- "Heer & Robertson recommend 300-1000ms transitions" - per that review,
they tested 1.25s and 2s only, and recommend roughly one second per
animated stage.
- Motion *confirms* state changes; it must not *perform* them. Optimistic
UI first, animation second.
## 8. Pre-ship gate
Run against the built surface, not the source: trigger each interaction and
watch it. Severity-tiered; every CRITICAL verified by observation before
"done".
- **CRITICAL:** reduced-motion variant exists and was toggled on and
observed; nothing exceeds 500ms (unless the When-NOT spectacle
exception was invoked in writing - the purpose test and reduced-motion
floor still bind there); no spring fails to settle; no
keyboard-initiated action animates; no `transition: all`.
- **HIGH:** durations inside §1 budgets; easing direction per §2; exits
faster than entrances; layout-property animation absent.
- **MEDIUM:** stagger within budget; `transform-origin` set for scale;
consistent timings across sibling components; overshoot within §4 context
budget.
Done when: every CRITICAL item observed passing, HIGH items pass or carry a
written exception, and any failure names the rule it broke (not "feels off").
## When NOT to use this skill
- Debugging one library's API surface -> that library's docs; this file owns
budgets and judgment, not APIs.
- Static layout/type/color decisions -> `ui-design-craft`.
- Running a full design review with measurement and fix loop ->
`design-review-gate` (its motion pass applies this file's budgets).
- Marketing hero choreography on a page whose brief explicitly buys
spectacle: §1 caps may be exceeded there, deliberately and in writing -
the purpose test and reduced-motion floor still apply.
## Provenance
Composed 2026-07-19 for design-pack 0.1.0. Sources, all verified in their
repos at composition time: Emil Kowalski's `emilkowalski/skills` (MIT) -
duration budgets, keyboard rule, stagger numbers, Framer shorthand and CSS
variable performance traps, Apple-derived projection / rubber-band / velocity
handoff forms (his distillation of Apple WWDC material; formulas quoted from
his files); LottieFiles `motion-design-skill` (MIT) - element duration table,
distance-duration multipliers, exit ratio, stagger and overshoot budgets,
severity-tier gate shape (their damping-units conflation is corrected here,
not copied; §1's duration rows are envelope syntheses across the three
motion sources, not quotes of any single table); `referodesign/refero_skill` (MIT) - purpose triad, easing
direction table, reduced-motion mapping, `transition: all` ban. §7's
corrections and the M2/M3 mislabel are adapted from nexu-io/open-design's
`craft/animation-discipline.md` primary-source review (Apache-2.0; the
correction items keep that review's selection, figures, and framing - an
ADAPTATION carried with notice in THIRD-PARTY-NOTICES, not an ideas-only
borrow). The primary papers were not independently re-opened for this
pack, with one exception: the Doherty citation itself (report GE20-0752,
"Thadhani") is this pack's own correction of the review's miscite. greensock/gsap-skills was
surveyed and deliberately not mined: library-usage doctrine, out of scope
here. Numeric values (beziers, spring pairs, budgets) are the volatile
facts - re-verify beziers against the published Material 3 motion tokens
(m3.material.io) and, for Apple, the SwiftUI Animation API's actual values
(the HIG publishes no numeric curves); spring pairs against the Framer
Motion / Motion docs - on major vendor releases. Probe status: probe-tested 2026-07-19 on a
private over-budget motion-spec fixture (fresh weak-tier agent, n=1 per
arm, smoke grade): the bare arm caught ~3 of 7 planted violations, loosely
phrased - though it did catch the missing reduced-motion variant, recorded
honestly - while the ruled arm caught 7 of 7 with budget citations,
including the keyboard rule and the 500ms total-stagger cap the bare arm
lacked as concepts. A round-0 run was voided for a leaked in-fixture
answer key. Trail in the design-pack PR.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!