# Motion Opportunities

> Use when a UI feels static and you need to find where motion is missing. Proposes candidates and rejects most of them.

- Skill: `agentsorg/motion-opportunities` (Agent Skill)
- Install (CLI): `npx skillmds@latest add agentsorg/motion-opportunities`
- Raw SKILL.md: https://api.skillmd.com/api/skills/agentsorg/motion-opportunities/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: agentsorg (https://skillmd.com/u/agentsorg)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/agentsorg/motion-opportunities

---



# Finding Missing Motion

This is a filter that happens to also be a finder. The default posture is rejection: assume the interface needs no more motion than it has, and make every candidate earn its place by surviving a four-question gate in order. An opportunity finder with a low bar is actively harmful — it produces the over-animated, sluggish interfaces the rest of this pack exists to prevent — so reject more than you propose, cap the output at five to seven for a whole app, and treat "this already has the right amount of motion" as a successful result.

The sibling is `motion-audit`, and the split is whether the motion exists yet: `motion-audit` sweeps animations that are present and wrong and returns a prioritized fix plan, while this skill only proposes motion that is absent and never returns a verdict or a plan. It also never implements — hand a surviving row to `motion-audit plan <candidate>` and let that skill specify it. One narrower boundary: `delight` decides whether a rare moment deserves extra craft at all; this skill locates the seam and names the recipe once that question is settled.

**Write every proposal in the vocabulary the project already has.** Before hunting, read the repo's easing and duration tokens, its motion library (`motion`/`framer-motion`, React Spring, GSAP, WAAPI, or plain CSS), and which curve family it uses — the settling one that lands on `1` or the overshooting one that passes it. A proposal naming a curve the repo does not own, or requiring a library to do what a CSS transition already does, fails its own gate and never reaches the table.

## The Gate

Every candidate answers all four, in order. Record the answers — they go in the report.

**1. Frequency.** The frequency gate run backwards: the tiers that forbid animation also forbid proposing it.

| Frequency | Verdict |
| --- | --- |
| 100+ times/day — shortcuts, command palette, core navigation | **Reject. No animation. Ever.** |
| Tens of times/day — hover states, list navigation, frequent toggles | Reject, or propose only near-imperceptible motion |
| Occasional — modals, drawers, toasts, settings | Eligible, standard animation |
| Rare or first-time — onboarding, empty states, success, celebration | Eligible, and this is where the delight budget lives |

Keyboard-initiated actions are a disqualifier, not a judgment call.


**2. Purpose.** Name it in one of these words or reject: feedback, spatial consistency, state indication, preventing a jarring change, explanation (marketing and onboarding only), or delight (rare tier only). "It looks cool" is not on the list.

**3. Speed.** The proposal must fit the budget — press `100–160ms`, tooltips `125–200ms`, dropdowns `150–250ms`, modals and drawers `200–500ms`, product UI under `300ms`. A moment that only works as a slow, showy animation fails.

**4. Function.** Data the user is reading or acting on must not move for style — decoration helps on a marketing page and hinders on a banking app's graph.

## Core Principles

1. **Sweep for seams, not for surfaces.** Scanning component by component produces a wishlist, because every component *can* be made to move; scanning known seam classes produces evidence. The sweep is done when each of the seven classes below has either yielded a candidate with `file:line` evidence or been explicitly cleared. Exception: a single-view request, where the seam list holds but the cap drops to two or three.

2. **Reject at the first failed question and stop evaluating.** A candidate that fails frequency cannot be rescued by a great purpose, and reasoning on past the failure is how a rejection becomes a proposal. Exception: none — the gate is ordered because the order carries the decision.

3. **Never propose motion on a keyboard-initiated action, at any tier.** These fire hundreds of times a day, and animation makes them feel disconnected from the keystroke; Raycast ships no palette open/close animation, which is the right answer rather than an oversight. Exception: none — treat a request to animate a shortcut as a rejection to report, not a candidate to refine.

4. **Carry exact values in every proposal.** A row saying "add a subtle entrance" cannot be executed and will be implemented as a 400ms fade; a row saying `@starting-style` from `opacity: 0; translateY(100%)` over `400ms`, exiting by the same edge, can. Use the repo's token names. Exception: a value that genuinely depends on measured travel distance — give the range plus the rule for picking inside it.

5. **Propose `transform` and `opacity` only.** They skip layout and paint, so proposing an animated `height` or `margin` is asking for a jank finding later; expansion uses grid-rows or `clip-path: inset()` instead. Exception: a rare, one-shot moment on an isolated element with nothing else competing for the main thread — state the tradeoff in the row.

6. **Attach the accessibility path to the proposal, not to a follow-up.** Motion proposed without a reduced-motion path arrives incomplete and usually ships that way. Every row that moves something carries a gentler variant — keep opacity and color, drop movement — and every hover proposal carries `@media (hover: hover) and (pointer: fine)`. Exception: opacity-only cross-fades, which are already the reduced-motion path.

7. **Publish the rejections.** The rejected list is what separates this skill from a wishlist, and it is the part a reader can check your judgment against. List two to five, each with the gate question that killed it. Exception: a request too small to have produced five candidates — list what you actually considered.

8. **Report "nothing survived" plainly when nothing survives.** Daily use argues for less motion, not more, and a restrained interface is the goal state. Exception: if the sweep found nothing because the surfaces animate already but animate badly, that is `motion-audit` — output a one-line route.

## Where to Hunt

| Seam | What it looks like today | Candidate |
| --- | --- | --- |
| Feedback gap | Pressable element with no `:active` state | `transform: scale(0.97)` on `:active`, `transition: transform 160ms ease-out` |
| Destructive slip | Delete confirmed by a single plain click | Hold-to-confirm: `clip-path: inset(0 100% 0 0)` overlay, `2s linear` on press, `200ms ease-out` snap-back |
| Teleporting state | Conditional render, route content, or section that swaps instantly | Enter from `scale(0.95–0.97)` + `opacity: 0`, `ease-out`, via `@starting-style`; never `scale(0)` |
| Missing spatial story | Panel, popover, or menu appearing with no link to its trigger | Scale in from `transform-origin: var(--transform-origin)`; modals stay centered |
| Asymmetric exit | A surface that leaves by a different path than it arrived | Same edge both ways, using percentage translates such as `translateY(100%)` |
| Flat group entrance | Grid or list popping in all at once on an occasional surface | `30–50ms` stagger, `80ms` ceiling for long lists, never blocking interaction |
| Physics-free gesture | Draggable or swipeable element that snaps | Spring `{ type: "spring", duration: 0.5, bounce: 0.2 }`, dismissal at velocity `> 0.11`, rubber-banding instead of a hard stop |

Useful sweeps: `{isOpen &&`, `display: none` toggles, `onClick` with no `:active` rule, `details`/accordion markup, drag handlers, `.map(` renders of entering lists, empty-state and success components.

## Smell / Fix

| Smell | Fix |
| --- | --- |
| A proposal on a command palette or shortcut | Move it to the rejected list with the frequency reason |
| A row whose purpose is "it feels flat" | Name one of the six purposes or drop the row |
| "Add a smooth transition here" | The exact property, duration, curve token, and origin |
| A proposal that needs a new animation library | Do it with a CSS transition, or drop it |
| Animated line-drawing on a chart the user is reading | Reject on question 4 and say so |
| A rejected list that is empty | You were not filtering, you were listing |
| The skill wrote the animation | It reports only; route to `motion-audit plan <candidate>` |

## Output Format

**Part 1 — Opportunities**, ordered by leverage, one row each:
`| # | Location | Today | Purpose | Frequency | Suggested motion |`
The `Suggested motion` cell carries exact properties, durations, and the repo's own curve token, plus the reduced-motion and hover-gating notes where they apply.

**Part 2 — Rejected candidates (required).** Two to five places you considered and deliberately did not propose, each with the gate question that killed it — `CommandMenu.tsx:12`, rejected as keyboard-initiated and 100+/day; `Chart.tsx:88`, rejected as functional data the user is reading.

**Part 3 — Verdict.** One short paragraph: how much motion this interface actually needs, whether it is already close to right, and which single row has the highest leverage. Close by naming the handoff — `motion-audit plan <candidate>`.

## Checklist

- [ ] Read the repo's motion tokens, library, and curve family before hunting
- [ ] All seven seam classes either yielded evidence or were explicitly cleared
- [ ] Every candidate ran the four gate questions in order, stopping at the first failure
- [ ] No proposal touches a keyboard-initiated or 100+/day surface
- [ ] Every proposal names one of the six valid purposes and stays under `300ms` on product UI
- [ ] Every proposal animates `transform` and `opacity` only, in existing tokens
- [ ] Reduced-motion path and hover gating attached to the rows that need them
- [ ] Output capped at 5–7 for an app, fewer for one view
- [ ] Rejected candidates published with the killing gate question
- [ ] Nothing implemented; handoff to `motion-audit plan <candidate>` named

