Best for
- Use when implementing notification badges, dropdowns, modals, panel reveals, page transitions, card resizes, number pop-ins, text swaps, icon swaps, success checks, avatar group hovers, or error state shakes.
wibus-wee/cradle-app/.agents/skills/transitions-dev/SKILL.md
Production-ready CSS transitions for web apps. Use when implementing notification badges, dropdowns, modals, panel reveals, page transitions, card resizes, number pop-ins, text swaps, icon swaps, success checks, avatar group hovers, or error state shakes. Triggers on "add a transition", "animate the dropdown", "make the modal open smoothly", "swap icon", "page slide", "stagger animation", "open / close transition", "make it animate", "tween the size", "fade between", "smooth open", "smooth close
Decision brief
Twelve portable CSS transitions, each namespaced under t- selectors with semantic CSS custom properties. Drop-in: paste the snippet, wire the documented HTML hooks, done. No framework dependencies, no demo-specific markup, and every snippet ships a prefers-reduced-motion guard.
Compatibility matrix
| Platform | Status | Evidence | What to check |
|---|---|---|---|
| Codex | Not declared | No explicit evidence | Portability before use |
| Claude Code | Not declared | No explicit evidence | Portability before use |
| Cursor | Not declared | No explicit evidence | Portability before use |
| Gemini CLI | Not declared | No explicit evidence | Portability before use |
Installation
The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.
npx skills add https://github.com/wibus-wee/cradle-app --skill ".agents/skills/transitions-dev"Inspect the Agent Skill "transitions-dev" from https://github.com/wibus-wee/cradle-app/blob/f80cd2ce640a8fc5eb5c6110b1a0978d254c2e79/.agents/skills/transitions-dev/SKILL.md at commit f80cd2ce640a8fc5eb5c6110b1a0978d254c2e79. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.
Workflow
Review the “Quick reference” section in the pinned source before continuing.
When the user asks for a transition, match against the visible UI element first, then the verb:
Drop this :root block into your project once. Every transition snippet reads from these semantic names — there are no per-component values to chase down later.
When inserting a transition into the user's project:
Stripping the close-state class cleanup on dropdown/modal — without the setTimeout that removes .is-closing, the next open jumps from the closing scale instead of the resting pre-open scale.
Permission review
No configured static risk pattern was detected
This is not proof of safety. Runtime behavior, indirect dependencies, and hidden external systems are outside the static scan.
Evidence record
| Signal | Value | Evidence type | Meaning |
|---|---|---|---|
| Quality score | 87/100 | Computed | Documentation, specificity, maintenance, and trust rules |
| Repository stars | 52 | Source | Repository attention, not individual Skill quality |
| Compatibility | 0 platforms | Source | Declared in the catalog source record |
| Usage guide | automated source guide | Editorial | Generated or reviewed according to the visible evidence level |
Pinned source
Twelve portable CSS transitions, each namespaced under t-* selectors with semantic CSS custom properties. Drop-in: paste the snippet, wire the documented HTML hooks, done. No framework dependencies, no demo-specific markup, and every snippet ships a prefers-reduced-motion guard.
| Transition | When to use | Reference |
|---|---|---|
| Card resize | Tween a container's width or height when its layout state changes. | 01-card-resize.md |
| Number pop-in | Re-enter each digit with a blurred slide when a number updates. | 02-number-pop-in.md |
| Notification badge | Slide a small badge onto a trigger and pop the dot. | 03-notification-badge.md |
| Text states swap | Swap text in place with a blurred up-and-down transition. | 04-text-states-swap.md |
| Menu dropdown | Open an origin-aware dropdown that grows from its trigger. | 05-menu-dropdown.md |
| Modal open / close | Scale-up modal dialog with a softer scale-down on close. | 06-modal.md |
| Panel reveal | Slide a panel into a region with a cross-blur. | 07-panel-reveal.md |
| Page side-by-side | Slide between two side-by-side pages (list ↔ detail, step 1 ↔ step 2). | 08-page-side-by-side.md |
| Icon swap | Cross-fade two icons in the same slot with blur and scale. | 09-icon-swap.md |
| Success check | Compose fade + rotate + Y-bob + path stroke-draw to celebrate a completed action. | 10-success-check.md |
| Avatar group hover | Distance-falloff lift on a row of items with a bouncy spring on return. | 11-avatar-group-hover.md |
| Error state shake | Per-segment cubic-bezier shake with auto-reverting border + message. | 12-error-state-shake.md |
When the user asks for a transition, match against the visible UI element first, then the verb:
If two transitions could fit, prefer the lower-overhead one (card resize over panel reveal, dropdown over modal, success check over a full modal celebration) unless the design clearly calls for the heavier surface. The success check is animation-only — if you also need to swap from a spinner to the check, pair it with icon swap.
Drop this :root block into your project once. Every transition snippet reads from these semantic names — there are no per-component values to chase down later.
/* transitions-dev — copy this :root block into your project once.
Every transition snippet reads from these semantic names. */
:root {
/* Card resize */
--resize-dur: 300ms;
--resize-ease: cubic-bezier(0.22, 1, 0.36, 1);
/* Number pop-in */
--digit-dur: 500ms;
--digit-distance: 8px;
--digit-stagger: 70ms;
--digit-blur: 2px;
--digit-ease: cubic-bezier(0.34, 1.45, 0.64, 1);
--digit-dir-x: 0;
--digit-dir-y: 1;
/* Notification badge */
--badge-slide-dur: 260ms;
--badge-pop-dur: 500ms;
--badge-pop-close-dur: 180ms;
--badge-fade-dur: 400ms;
--badge-fade-close-dur: 180ms;
--badge-blur: 2px;
--badge-offset-x: -8.2px;
--badge-offset-y: 12.4px;
--badge-slide-ease: cubic-bezier(0.22, 1, 0.36, 1);
--badge-pop-ease: cubic-bezier(0.34, 1.36, 0.64, 1);
--badge-close-ease: cubic-bezier(0.4, 0, 0.2, 1);
/* Text states swap */
--text-swap-dur: 200ms;
--text-swap-translate-y: 8px;
--text-swap-blur: 2px;
--text-swap-ease: ease-out;
/* Menu dropdown */
--dropdown-open-dur: 250ms;
--dropdown-close-dur: 150ms;
--dropdown-pre-scale: 0.97;
--dropdown-closing-scale: 0.99;
--dropdown-ease: cubic-bezier(0.22, 1, 0.36, 1);
/* Modal open / close */
--modal-open-dur: 250ms;
--modal-close-dur: 150ms;
--modal-scale: 0.96;
--modal-scale-close: 0.96;
--modal-ease: cubic-bezier(0.22, 1, 0.36, 1);
/* Panel reveal */
--panel-open-dur: 400ms;
--panel-close-dur: 350ms;
--panel-translate-y: 100px;
--panel-blur: 2px;
--panel-ease: cubic-bezier(0.22, 1, 0.36, 1);
/* Page side-by-side */
--page-slide-dur: 200ms;
--page-fade-dur: 200ms;
--page-slide-distance: 8px;
--page-blur: 3px;
--page-stagger: 0ms;
--page-exit-enabled: 1;
--page-slide-ease: cubic-bezier(0.22, 1, 0.36, 1);
--page-fade-ease: cubic-bezier(0.22, 1, 0.36, 1);
/* Icon swap */
--icon-swap-dur: 200ms;
--icon-swap-blur: 2px;
--icon-swap-start-scale: 0.25;
--icon-swap-ease: ease-in-out;
/* Success check */
--check-opacity-dur: 550ms;
--check-rotate-dur: 550ms;
--check-rotate-from: 80deg;
--check-bob-dur: 450ms;
--check-y-amount: 40px;
--check-blur-dur: 500ms;
--check-blur-from: 10px;
--check-path-dur: 550ms;
--check-path-delay: 80ms;
--check-ease-out: cubic-bezier(0.22, 1, 0.36, 1);
--check-ease-opacity: cubic-bezier(0.22, 1, 0.36, 1);
--check-ease-rotate: cubic-bezier(0.22, 1, 0.36, 1);
--check-ease-bob: cubic-bezier(0.34, 1.35, 0.64, 1);
--check-ease-path: cubic-bezier(0.22, 1, 0.36, 1);
/* Avatar group hover */
--avatar-lift: -4px;
--avatar-dur: 320ms;
--avatar-scale: 1.05;
--avatar-falloff: 0.45;
--avatar-ease-in: cubic-bezier(0.22, 1, 0.36, 1);
--avatar-ease-out: cubic-bezier(0.34, 3.85, 0.64, 1);
/* Error state shake */
--shake-distance: 6px;
--shake-overshoot: 4px;
--shake-dur-a: 80ms;
--shake-dur-b: 60ms;
--shake-ease: cubic-bezier(0.22, 1, 0.36, 1);
--revert-hold: 3000ms;
--revert-dur: 280ms;
}
The --pX-* source tokens used by the live demo at transitions.dev are intentionally not exported here. Tunable values are renamed to semantic names (--badge-*, --dropdown-*, --modal-*, …) so the user owns the design vocabulary.
When inserting a transition into the user's project:
:root block above to the user's global stylesheet, but only if it isn't already there. If the user already imported the universal install block once, do not duplicate it.will-change. The snippets are tuned and tested..t-dropdown, .t-modal, .t-success-check, .t-avatar, .t-input, …) and state attributes (data-open, data-state, data-page, .is-open, .is-closing, .is-exit, .is-enter-start, .is-animating, .is-error, .is-shaking).@media (prefers-reduced-motion: reduce) block. Every snippet ships one. Removing it makes the component fail accessibility audits.getComputedStyle(...)getPropertyValue("--…")) so durations stay in sync with the :root values.Keep the diff small: only edit the files needed to introduce the transition. Don't rename the user's existing variables, don't reformat unrelated CSS, don't pull in a motion library.
setTimeout that removes .is-closing, the next open jumps from the closing scale instead of the resting pre-open scale.void el.offsetWidth (or offsetHeight) between class/attribute removal and re-addition is what guarantees the animation replays.transition: … with transition: all — every snippet enumerates exact properties on purpose so unrelated style changes don't ride in for free.stroke-dasharray — the snippet ships 20 as a placeholder. Replace it with path.getTotalLength() rounded up by 1 for your path, otherwise the stroke pre-reveals or over-draws.transition-timing-function in CSS for the avatar group hover — it has to be set inline in JS before the --shift / --scale-active writes so the bouncy ease-out only applies on mouseleave..is-error and .is-shaking into one class for the error state shake — keeping them orthogonal is what allows the shake to replay (remove → reflow → re-add) without flickering the whole error treatment.