Files
playbook/antigravity-awesome-skills/skills/design-system/references/motion-choreography.md
T
2026-07-01 16:02:41 +00:00

112 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Motion choreography — a generalizable animation guideline
When to animate, which transitions earn their keep, how long, and the craft that
separates intentional motion from jitter. **Framework-agnostic** — the rules hold for
plain CSS, the Web Animations API, the browser View Transition API, a JS motion library,
or React's `<ViewTransition>`. Load from design-system's Motion section when a UI has
state changes, navigation, list changes, or reveals worth animating.
> Adapted & generalized from the Web Interface Guidelines (`vercel-labs/web-interface-guidelines` @ `4e799d4`, 2026-04-06) and the React View Transitions skill (`vercel-labs/agent-skills` @ `f8a72b9`, 2026-06-10). Their API specifics are React/Next-bound; the *choreography* below is portable.
## 1. When to animate — earn every transition
Animate only when the motion **communicates** one of: a spatial relationship, continuity
("same thing, new place"), cause→effect, that data arrived, or deliberate delight.
**If you can't articulate in one sentence what a transition communicates, cut it.** Motion
with no message is noise that costs performance and attention.
- **Input-driven, never autoplay.** Animate in response to a user action or a state change
they caused — not on a timer that plays at them.
- This is the motion-specific case of restraint-rule: the default is *no* animation; a clear
communicative purpose is what forces a yes.
## 2. Which transitions earn their keep — priority order
When several kinds of change happen, implement every one that *applies* (not "pick one"),
in roughly this order of value:
1. **Shared element** — the same object persists across views (thumbnail → hero). Says "this
is the same thing, going deeper." Highest value; most worth the effort.
2. **Reveal** — skeleton/loading → real content. Says "data loaded."
3. **List identity** — items keep identity as the set reorders/filters. Says "same items,
new arrangement" (animate position, not a wholesale fade).
4. **State change** — something enters/exits (panel, toast, row). Says "this appeared/left."
5. **Route/section change** — moving to a new place.
Skip a level only when the UI has no such change. A background refresh / silent
revalidation should animate **nothing**.
## 3. Style by the *kind* of navigation — direction must be honest
The animation style must not imply a spatial relationship that isn't there:
| Navigation kind | Animation | Why |
|---|---|---|
| **Hierarchical** (list → detail, parent → child) | directional slide (in from the side, out the other) | direction encodes depth |
| **Ordered sequence** (prev/next photo, carousel, paginated) | directional slide; "next" from the right, "prev" from the left | direction encodes position |
| **Lateral / sibling** (tab ↔ tab, unordered) | **fade / cross-fade** — NOT a slide | there's no depth; a slide lies about it |
| **Reveal** (skeleton → content) | fade or short slide-up | content arriving |
| **Background refresh / revalidation** | none | nothing happened the user must track |
The single most common motion mistake is a directional slide on lateral navigation — it
falsely implies forward/back depth between peers.
## 4. Timing & easing (starting points, not law)
| Interaction | Duration |
|---|---|
| Direct toggle (expand/collapse, switch) | 100200 ms |
| Route / section transition (slide) | 150250 ms |
| Reveal (skeleton → content) | 200400 ms |
| Shared-element morph | 300500 ms |
- **Easing matches the change:** entrances `ease-out` (decelerate in), exits `ease-in`
(accelerate away), positional moves `ease-in-out`. Choose by what's changing (size,
distance, trigger) — bigger/further → a touch longer.
- A playful/toy direction (design-thinking) may stretch these and add spring/bounce; a
restrained/professional one keeps them tight. Match the feel to the chosen direction
rather than defaulting to the numbers.
## 5. Mechanics — what to animate, and how (any stack)
- **Compositor-friendly only:** animate `transform` and `opacity`. **Never** animate layout
properties (`width`, `height`, `top`, `left`) — they trigger reflow and jank.
- **Never `transition: all`** — list the exact properties; `all` silently animates
layout-affecting props and janks.
- **Correct `transform-origin`** — anchor motion where it "physically" starts (a menu from
its trigger, not screen-center).
- **Interruptible** — a new user input cancels/redirects the in-flight animation; motion is
never a modal wait.
- **SVG transforms** — apply to a `<g>` wrapper with `transform-box: fill-box;
transform-origin: center;` (avoids Safari origin bugs).
- **Stack preference:** CSS > Web Animations API > JS library. Prefer the platform; reach for
a library only when the platform can't express it.
## 6. Craft details that separate polished from amateur
- **Motion blur on morphs.** A shared-element morph reads as fast and physical with a brief
blur at mid-transition (e.g. `filter: blur(3px)` around the 30% mark), clearing to sharp.
- **Don't raster-scale text.** A shared-element morph between different text sizes (`h3 → h1`)
scales a *bitmap* of the small text up → a blurry ghost. Instead hold/cross-fade the text
(show the new text at full resolution, hide the old snapshot) rather than scaling it.
- **Isolate persistent chrome.** Headers, navs, sidebars, sticky toolbars that stay on screen
across a transition must NOT slide with the page content — give them their own transition
identity (or exclude them) so they stay put while content moves beneath.
- **Reveal anti-flicker.** If you show a spinner/skeleton, add a short show-delay (~150300 ms)
and a minimum visible time (~300500 ms) so a fast response doesn't flash the loader. (See
design-ux interaction add-ons.)
## 7. Accessibility — reduced motion
Honor `prefers-reduced-motion` for **public / multi-user** builds: drop the tweens (keep the
end state / a 0s cross-fade), don't remove the information the motion conveyed. For
**personal/single-user** projects this repo deliberately overrides that flag — see
`motion-preference-rule`. Either way, avoid parallax, large viewport-spanning transforms, and
strobing regardless of the flag.
## The one-line test
Before adding a transition: *what does this communicate, and is the style honest about it?*
If you can't answer the first, cut it; if the style implies depth/position that isn't there
(a slide between peers), change the style.