Skip to content
Back to skills

Transitions

ASecurity

Give a Next.js 16 App Router app ONE motion system: a token layer (durations, easings, springs) plus a library of tokenized micro-interactions — entrance/exit, stagger, layout, modal/dropdown/toast/accordion, hover, feedback and route/page transitions. Four modes: Setup (scaffold `lib/motion/` from the DESIGN.md motion block), Apply, Audit (find ad-hoc or hardcoded motion), Refine (swap hardcoded values for tokens). Use when the user says "add a transition", "animate this", "aggiungi una tran...

  • 6 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 19, 2026
developmentpythonrustgoreactnextjsnodespringapi

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 10 files and shows the line behind each finding

Scanned October 19, 2026

npx -y skills add lukedj78/dev-flow --skill transitions --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Transitions?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Transitions
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/lukedj78-transitions/badge)](https://www.skillsdirectory.com/skills/lukedj78-transitions)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: transitions
description: 'Give a Next.js 16 App Router app ONE motion system: a token layer (durations, easings, springs) plus a library of tokenized micro-interactions — entrance/exit, stagger, layout, modal/dropdown/toast/accordion, hover, feedback and route/page transitions. Four modes: Setup (scaffold `lib/motion/` from the DESIGN.md motion block), Apply, Audit (find ad-hoc or hardcoded motion), Refine (swap hardcoded values for tokens). Use when the user says "add a transition", "animate this", "aggiungi una transizione / animazione", "page transition", "stagger these cards", "make the modal open nicely", or "audit the motion in this codebase". Refuses outside Next.js 16 web (`stack.framework` next/monorepo). Not for: React Native motion (use `rn-animations-gestures`), installing the Motion runtime itself (use `module-add motion`), or defining the visual palette/type (that is DESIGN.md + design-md-to-app).'
---

# transitions — one tokenized motion system for a Next.js 16 app

This skill governs **how a web app moves**. Like `state-discipline` for state and `forms` for forms, it routes every animation through one shared, token-driven system so the product has a single, consistent motion feel — instead of scattered `duration-[237ms]` magic numbers and hand-tuned cubic-beziers that drift page to page.

> **Inspired by [transitions.dev](https://transitions.dev/) (Jakub Antalík)** — a curated library that teaches agents about product motion. This is **our own** take: token-driven, wired to the DESIGN.md contract and our Next.js 16 + Motion + `tw-animate-css` stack. Not a fork, not an install of their package — we credit the idea and build our own.

## When this skill applies

- The user says "add a transition / animation", "animate this", "aggiungi una transizione", "make the modal open nicely", "stagger these cards", "page transition between routes".
- You're about to write an inline `transition:` / `animate={{…}}` / `@keyframes` / a Tailwind `duration-[Xms]` arbitrary value.
- The user asks to **audit** a codebase for ad-hoc motion.
- A component needs entrance/exit, hover, feedback, layout, or route motion.

If the effect genuinely needs JS physics (spring, layout/shared-element, drag/gesture, scroll choreography) and the Motion runtime isn't wired, this skill **routes to `module-add motion`** first, then applies the tokenized pattern on top.

## Contract

Follows the dev-flow contract — see `references/contracts.md`. Key facts:

- Reads `meta.json#stack.framework`, `stack.nextjs_version`, `stack.motion`. For monorepo, reads `stack.monorepo.web.*` and operates in `apps/web/`.
- **Refuses** if `stack.framework ∉ {"next", "monorepo"}` or `stack.nextjs_version != "16"`. Principles transfer to other React setups but the View-Transitions/RSC rungs don't — refuse rather than mis-apply.
- Records `meta.json#stack.motion` (see block below) + appends `history` per run.
- Does **not** bump `phase`. Horizontal capability — invoke any time.

## Companion skills — what this owns vs reuses

- **`module-add motion`** — installs the **Motion** runtime (`motion/react`, `components/motion/*`, `lib/motion-config.ts`). This skill sits *above* it: it owns the token layer + the curated library + the discipline; it never re-installs the runtime, it routes there when Tier 3 is needed.
- **`tw-animate-css`** — already shipped by shadcn; it's this skill's **Tier 0** engine (fade/slide/scale, zero JS; it ships no shimmer — `references/tw-animate-css.md`). Reuse it, don't reinvent.
- **`design-md-to-app`** — owns DESIGN.md and the visual tokens. This skill reads the **`motion` block** of DESIGN.md for the *values*; if absent, it scaffolds sane defaults and writes them back (DESIGN.md stays the source of truth for values).
- **anti-slop fallbacks** (`design-md-to-app/references/anti-slop-fallbacks.md`) — the "animate only transform/opacity", hardware-acceleration, content-shaped-skeleton, CSS-stagger rules live there. This skill enforces them; it doesn't duplicate them.
- **`rn-animations-gestures`** — the **mobile** counterpart (Reanimated + Gesture Handler). This skill is web-only.

## Step 0 — should it animate at all? (run this before the ladder)

The ladder's lowest rung is Tier 0, but **the cheapest answer is no animation**, and the question has
a table: **100+ times/day — a keyboard shortcut, the command palette, core navigation — gets no
animation, ever.** Tens of times a day gets near-imperceptible or nothing. Occasional (modals,
drawers, toasts) is where the ladder applies. Rare and first-time moments are where the delight
budget lives. Then name the purpose — feedback, spatial consistency, state indication, preventing a
jarring change, explanation, or delight at the rare tier — or don't build it.

The full gate, the exact curves and durations, the physicality and gesture rules, and the checks to
name when feel cannot be judged from code are in **`references/animation-standards.md`**. Read it
before writing a transition, and before approving one.

## The technique ladder — cheapest tier that does the job

Reach for the **lowest** tier that achieves the effect. Higher tiers cost bundle size, force `"use client"`, or add JS the interaction doesn't need.

| Tier | Engine | Use for | Cost |
|---|---|---|---|
| **0** | Tailwind + `tw-animate-css` | enter/exit fade·slide·scale, hover lift, CSS stagger (`animationDelay`), skeleton pulse (Tailwind's `animate-pulse`, opacity only — never a moving gradient sweep) | zero JS, RSC-safe |
| **1** | Hand-written CSS `transition` / `@keyframes` (tokened) | bespoke feedback (success check draw, error shake, number pop), state-swap crossfades | zero JS, RSC-safe |
| **2** | **View Transitions API** | route/page transitions (Next 16 App Router), same-document DOM swaps (list reorder, tab underline) | tiny JS, mostly RSC-safe |
| **3** | **Motion** (`motion/react`) | spring physics, layout / shared-element, drag & gesture, scroll-driven choreography (`useScroll`/`useTransform`) | ~40kb, forces `"use client"` → routes to `module-add motion` |

| **4** | **WebGPU shaders** (`vgpu`) | raymarching, fluid, volumetric light, real-time generative texture — when the effect **is** the artefact | a GPU device, a compile step, a render loop and a battery — see `vgpu-shaders` |

**On the Tier-2/Tier-3 seam sit Motion's two view-transition APIs** — imperative `animateView(update)` and,
since `motion@13.4.0`, the `<AnimateView>` component (`motion/react-animate-view`). Both choreograph the
browser's View Transitions instead of replacing them, so they stay cheaper than Tier 3 layout animation.
⚠️ `<AnimateView>` renders React's `ViewTransition` and **throws at runtime** — *"AnimateView requires
React 19.3 or later."* — while `motion`'s peer range still accepts React 18/19, so npm installs it on a
19.2 project and it breaks in the browser. Check `react`'s version first; ours are on 19.2.4. Details,
props and the reduced-motion duty (the docs never mention it): `references/motion-library.md`.

**Borrowing motion instead of inventing it — [React Bits](https://reactbits.dev).** 202 animated components as a
shadcn registry (`@react-bits`, MIT + Commons Clause: usable in a product, **never republished as components**).
Its `/c/micro` set (30 micro-interactions) is Motion-based and calls `useReducedMotion()` itself. Read it as a
**cookbook**: most of those items animate a pattern shadcn already ships (Spring Check → `Checkbox`, Squish Switch
→ `Switch`, Swipe Toast → `Sonner`, four buttons → `Button`), so golden rule 3 says port the timings and gestures
onto **our** primitive through `lib/motion/tokens.ts` — not install a second one. For the few with no primitive
behind them, install through `registry-intake` (never a bare `shadcn add`), take the `TS-TW` variant, and expect
two findings: the items declare `motion@^12` against our 13, and their long arbitrary-value class lists raise the
design-lint cap. Details, the full micro list and the primitive map: `references/react-bits.md`. Other catalogues worth *looking* at under the same two rules — a MIT page of ~24 named loaders, Watermelon UI's MIT `watermelon` registry (1,178 items, and **always the `-base` twin**, which is the same component written in theme tokens instead of hex and `text-[15px]`), and unlumen UI's paid `@unlumen-ui` registry: `references/motion-catalogues.md`.

**Never jump to Tier 3 for a fade.** A `<Suspense>` fallback, a hover lift, a dropdown open — all Tier 0/1. Reserve Motion for interactions that genuinely need physics or layout animation.

**And Tier 4 is not the next step up from Tier 3 — it is a different order of cost.** A shader is right
when the visual *is* the product (a hero that exists to be looked at); it is wrong as decoration on a
screen someone keeps open all day. ⚠️ vgpu's own docs never mention `prefers-reduced-motion`, so rule 2
below is entirely yours to honour there — freeze the clock (`advance(0)`) and hold the first frame
rather than hiding the canvas. Route to **`vgpu-shaders`** before writing any WGSL. Once a project is
already on both Motion and vgpu, `motion/vgpu`'s `vgpuEffect` (Motion 13.2.0+) drives shader uniforms
and scene params with Motion's spring/gesture engine instead of a hand-rolled render loop — see
`vgpu-shaders` §Animating once you're here. That is an implementation detail *inside* Tier 4, not a
reason to reach for it sooner.

## Non-negotiables (the discipline)

1. **Tokens, not magic numbers.** Every duration/easing/spring/distance comes from `lib/motion/tokens.ts`. No inline `duration-[237ms]`, no ad-hoc `cubic-bezier(...)` in components.
2. **`prefers-reduced-motion` always.** Every transition ships a reduced-motion fallback (opacity-only or instant). Tier 0/1 use the `motion-reduce:` Tailwind variant or a media query; Tier 3 uses `useReducedMotion()`. A transition without a reduced-motion path is incomplete. **This rule is machine-checkable**: `shadscan`'s `animations-respect-reduced-motion` verifies it against the built app — run that gate before shipping instead of trusting a grep.
3. **Gate hover motion on capability.** Touch has no hover, so the browser fakes one: the first tap applies `:hover` and **leaves it stuck** until the user taps elsewhere — a button that scales on hover stays scaled after a tap. Tailwind v4's `hover:` variant already compiles to `@media (hover: hover)`, so utility classes are covered; **hand-written `:hover` CSS is not** and needs `@media (hover: hover) and (pointer: fine)`. Touch gets its feedback from `:active`, which needs no gating.
4. **Animate only `transform` and `opacity`** (+ `filter` sparingly). Never animate `width`/`height`/`top`/`left`/`box-shadow` — layout thrash. Use `transform: scale/translate` and, for layout, Tier 2/3.
5. **Don't force `"use client"` for motion that doesn't need it.** Prefer Tiers 0–2 to keep Server Components server-rendered.
6. **Motion has meaning, and Step 0 is how you check.** Entrance ≠ decoration: it should clarify hierarchy, direction, or causality. Name the purpose in one word or cut it — and if the element is seen 100+ times a day, cut it regardless.
7. **Primitives keep their own motion (golden rule 3).** `Dialog`, `Sheet`, `Popover`, `Tooltip`, `Accordion`, `Sonner` already animate open/close with their state. Before writing a transition, search `components/ui/` for the primitive that owns the pattern and use it; tune its motion through tokens and `cva`/class variants, never by re-implementing the component or forking its behaviour to hang an animation on it.

## `lib/motion/tokens.ts` — the token layer

Setup mode scaffolds this from the DESIGN.md `motion` block (or defaults). Illustrative shape:

```ts
// Durations (ms) — the only durations the app uses.
export const duration = { instant: 0, fast: 120, base: 200, slow: 320, slower: 480 } as const;
// Easings — named curves; components reference these, never raw beziers.
export const ease = {
  standard: "cubic-bezier(0.2, 0, 0, 1)",   // enter AND exit — both are ease-out
  emphasized: "cubic-bezier(0.3, 0, 0, 1)", // hero, page
  inOut: "cubic-bezier(0.77, 0, 0.175, 1)", // movement *on* screen: a morph, A→B
} as const;
// There is deliberately no `exit` curve. It used to be `cubic-bezier(0.4, 0, 1, 1)`,
// an ease-in, and an exit on the critical path (a dialog the user closed to reach
// what is behind it) is latency — an accelerate curve holds it at near-full opacity
// through the half of the duration that matters. See references/animation-standards.md.
// Distances (px) — how far things slide in.
export const distance = { sm: 4, md: 8, lg: 16 } as const;
// Spring presets (Tier 3 only) — mirrors module-add motion's lib/motion-config.ts if present.
export const spring = {
  soft: { type: "spring", stiffness: 300, damping: 30 },
  snappy: { type: "spring", stiffness: 500, damping: 32 },
} as const;
```

CSS consumers get the same values as custom properties (`--motion-duration-base`, `--motion-ease-standard`, …) so Tailwind arbitrary values reference `var(--motion-*)` instead of literals. The full token→CSS-var bridge and the reduced-motion wiring are in `references/motion-library.md`.

## Modes

Read state, then pick a mode:

1. Read `meta.json#stack`. Refuse if not Next 16 (web). Check whether `lib/motion/tokens.ts` exists.
2. If the requested effect is Tier 3 and Motion isn't installed (`package.json` has no `motion`/`framer-motion`), **route to `module-add motion`**, then continue.
3. Choose the mode.

### Setup (first run)
Scaffold `lib/motion/` : `tokens.ts` (from DESIGN.md `motion` block or defaults), the CSS-var bridge in the global stylesheet, and `transitions.ts` (the tokenized variant/classname library — the curated set from `references/motion-library.md`). Idempotent: detect existing files, offer to extend, never double-write. Record `stack.motion`.

### Apply (add a transition)
Pick the **best-fit** transition from the library for the component/context, at the **lowest viable tier**, propose it in one line with the rationale (which tier + why), then wire it using the tokens. Always include the reduced-motion fallback. Never introduce a new magic number — if the library lacks a fit, add a *tokenized* entry to `transitions.ts` rather than inlining.

### Audit (find ad-hoc motion)
Run `python scripts/scan_motion.py <root>` for a first-pass signal, then verify each hit in code. The scan reads
source; to prove non-negotiable ② on the running page, run `app-verification`'s render probe
(`node <skills>/app-verification/scripts/render_probe.mjs <url>`): its `reduced-hidden` finding lists text that is
visible normally and stays invisible with reduced motion — an entrance with no reduced-motion path. Report un-tokenized durations/easings, `duration-[Xms]`/`ease-[…]` Tailwind arbitraries, inline `cubic-bezier`, `@keyframes` animating layout props, transitions with no `prefers-reduced-motion` path, and Tier-3 usage where Tier 0/1 would do. See `references/audit-recipe.md`. The scan is a **signal, not a verdict**.

### Refine (swap to tokens)
In a given file, replace hardcoded durations/easings with the nearest token (round to the token scale) and add any missing reduced-motion fallback. Every change is a reviewable diff.

## `meta.json#stack.motion` block

```jsonc
"motion": {
  "runtime": "tw-animate-css" | "motion" | "both",   // Tier 0 only, or Motion also wired
  "library": "motion" | "framer-motion" | null,       // the Tier-3 package, when one is wired
  "tokens": true,                                      // lib/motion/tokens.ts scaffolded
  "view_transitions": true | false,                   // route transitions enabled
  "last_audit_at": "<ISO>" | null
}
```

⚠️ **This key is shared with `module-add motion`, and the two used to write
different *shapes* into it** — that skill wrote the bare string `"motion"`, this
one writes the object above, so whichever ran second silently destroyed what the
first recorded. Settled in favour of the object, because it can carry everything
the string said: `module-add motion` now writes `stack.motion.library` and
`runtime`, leaving `tokens` / `view_transitions` to this skill. If you find a
`meta.json` with `"motion": "motion"`, it predates the fix — replace it with
`{ "runtime": "motion", "library": "motion", "tokens": false, "view_transitions": false, "last_audit_at": null }`.

## Definition of Done

- **Setup**: `lib/motion/tokens.ts` + CSS-var bridge + `transitions.ts` exist; `stack.motion.tokens = true`; values trace to the DESIGN.md `motion` block.
- **Apply**: the effect uses the lowest viable tier, references tokens (no magic numbers), and has a `prefers-reduced-motion` fallback.
- **Audit/Refine**: report lists only verified hits; refines land as diffs; a re-run is a no-op for already-tokenized code.
- Script green: `cd transitions/scripts && python3 -m unittest test_scan_motion`.

## What this skill does NOT do

- **Doesn't install the Motion runtime** — that's `module-add motion` (this skill routes there for Tier 3).
- **Doesn't define the palette/type** — DESIGN.md + `design-md-to-app` own visual tokens; this owns *motion* tokens.
- **Doesn't do React Native** — use `rn-animations-gestures`.
- **Doesn't bump `phase`.**

## Reference files

- `references/motion-catalogues.md` — where to look for motion and what looking may turn into: the loader vocabulary at loading.daniasyrofi.com (MIT, and why a skeleton still beats a spinner for content), and unlumen UI (`@unlumen-ui` shadcn registry, Free/Pro, **$119 one-time** or $69/year — with its second component namespace and the routing to `vgpu-shaders` / `animated-icons`).
- `references/react-bits.md` — React Bits as a motion source: licence, the `TS-TW` rule, the micro→primitive map golden rule 3 imposes, the Motion-12 and arbitrary-value traps, and where its other categories land on the ladder.

- `references/motion-library.md` — the curated, tokenized transition library: each entry → tier, tokens used, code snippet, and its `prefers-reduced-motion` fallback. Grouped enter/exit · toggle · hover · feedback · layout · route.
- `references/tw-animate-css.md` — the **Tier-0 engine how-to** (doc-grounded): the `animate-in`/`animate-out` class set and modifiers, `data-[state]` composition, the CSS variables to point at `lib/motion/tokens.ts`, and the `motion-reduce:` pattern. Read it before writing Tier-0 classes — don't guess class names.
- `references/audit-recipe.md` — "audit my codebase for ad-hoc motion" recipe (patterns, verify steps, refine order).
- `references/contracts.md` — the `.workflow/` dev-flow contract (vendored).

Files in this skill

  • SKILL.md18.4 KB
  • references/animation-standards.md15 KB
  • references/audit-recipe.md4.2 KB
  • references/contracts.md80.5 KB
  • references/motion-catalogues.md12.3 KB
  • references/motion-library.md16.4 KB
  • references/react-bits.md7.7 KB
  • references/tw-animate-css.md8.6 KB
  • scripts/scan_motion.py6.5 KB
  • scripts/test_scan_motion.py8.1 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.