phase

Use when building, reviewing, or optimizing web animations OR rendering performance (frame loops, scroll/viewport reveals, mount/unmount transitions,…

npx skills add https://github.com/vercel-labs/phase --skill phase

Prerequisite: ensure phase is installed

Before recommending phase imports, check the consumer project's package.json for "phase" in dependencies. If it is missing, install phase as a production dependency in that project. Do not install it in the phase repo itself (where phase is the package being developed). Skip this check when the task is auditing or advising without code changes.

phase

This skill teaches you to implement phase primitives correctly, preserve performance guarantees, and audit animation code. Phase is the lifecycle-aware performance layer for the web: it composes visibility, reduced motion, and frame budget signals so animations pause when unseen, respect user preferences, and never force a reflow.

The animation ladder

Always prefer the cheapest tier that satisfies the requirement. Never recommend phase where CSS suffices; never recommend an external library where phase suffices.

TierWhenTools
CSS-onlyEnter/exit, hover, state toggles, opacity and transform togglestransition, @starting-style, animation, View Transitions API
Minimal JSOne value into React render, no per-frame DOM writesuseTween (or CSS if render cost is trivial)
phasePer-frame JS, visibility pausing, canvas, lifecycle-aware loops, render gatinguseLoop, useCanvas, useLifecycle, Presence, Swap, WhenVisible, WhenIdle, Defer
External librarySpring physics, gesture systems, declarative keyframe orchestrationmotion, GSAP, etc.

For the full decision tree, read references/decision-guide.md. This ladder ranks animation cost; rendering work runs on a parallel track.

When to render, not only when to animate

phase is the when layer (when to animate, render, and pause) from one set of signals. Three helpers skip increasing amounts of work for off-screen content:

HelperDefersIn DOM?In SSR HTML?Reach for it when
Deferbrowser render (style/layout/paint)yesyescontent must stay crawlable but need not paint yet
WhenIdleReact mount until idlenononon-critical UI that shouldn't block first paint
WhenVisibleReact mount until near viewportnonoviewport-gated lazy loading / reveals

Defer is the cheapest and safest (keeps content, skips paint) and never causes a hard layout shift; its children stay in the DOM at true size. When* save the most (no DOM until triggered) but will shift layout / cause CLS unless the fallback reserves the exact final content height, so always size it (see references/rendering-recipes.md).

Two idle hooks defer work off the critical path: useIdle gates rendering with a boolean once the browser is idle, and useWhenIdle runs a side effect (prefetch, import()) once idle. useRenderState(ref) reads a Defer subtree's render-skip state to pause raw, non-phase work (a hand-written rAF loop, setInterval); phase's own loops already self-pause off-screen.

Choosing a primitive

The ladder picks a tier; this table picks the primitive once phase is the right tier.

NeedUse
Know if it's on screen?useSight
Want phase to run your frame loop?useLoop (DOM) / useCanvas (canvas)
You own the loop (WebGL, three.js, Web Worker)?useLifecycle (active/paused signal)
Animating one value into render?useTween
Mount/unmount transitions?Presence / Swap / WhenVisible
Skip painting off-screen content (keep in DOM)?Defer
Mount non-critical UI when idle?WhenIdle / useIdle
Run a side effect (prefetch, import()) when idle?useWhenIdle
Pause raw work inside a Defer subtree?useRenderState
React to DOM mutations without reflow?useMutation
Reactive scroll/size/media values?useScrollProgress / useSize / useContainerQuery / useMediaQuery
Scroll/size/visibility without re-renders?Same hooks with a callback (onProgress / onResize / onVisibilityChange), read via ref
Reactive reduced-motion check for non-phase code?usePrefersReducedMotion
Need reactive devicePixelRatio for buffer sizing?useDevicePixelRatio
Visibility-aware timed sequences (do X, wait, do Y)?useLoop with fps: 1–2 and frame.elapsed-based steps

React first

In React components, prefer the React hooks (useLoop, useCanvas, useLifecycle, useSight, etc.) over the core API (createLoop, createTicker, createLifecycle, createSight). Hooks manage refs, teardown, and React lifecycle automatically. Using createLoop inside a useEffect when useLoop would work is a bug waiting to happen (manual cleanup, stale refs, no enabled prop).

Reach for core primitives in React when the hook doesn't fit, such as building a custom hook on top of createLoop, composing multiple primitives via a shared AbortController, or wiring up an imperative manager that owns its own lifecycle. In those cases you own the teardown. Call stop() or abort the signal in the effect cleanup.

Non-negotiable invariants

Tests enforce these guarantees for animation hot paths. Violating them in consumer code is always a bug. (Rendering helpers carry one rule of their own: reserve fallback height so WhenVisible / WhenIdle don't shift layout, see references/rendering-recipes.md.)

  1. Zero per-frame allocations. No objects, arrays, closures, template literals, or spreads in onTick/draw.
  2. Never setState inside onTick. Write to refs or the DOM directly. Only phase changes trigger re-renders.
  3. No forced reflows. Never call getBoundingClientRect(), offsetWidth, getComputedStyle() in animation paths. Use useSize / ResizeObserver.
  4. Strong pause. cancelAnimationFrame() stops scheduling entirely. Zero callbacks, zero CPU when paused.
  5. Reduced motion by default. All primitives respect prefers-reduced-motion: reduce automatically. Bypassing requires explicit reducedMotion: 'ignore'.
  6. Frame-locked shared clock. One performance.now() per rAF frame. Multiple animations stay in sync.

For the full performance ruleset, read references/performance.md.

Export taxonomy

Every export belongs to a category. The choosing table above picks the primitive; this table shows the organizational structure.

CategoryWhat it coversExports
TimingFrame clocks and animation loopscreateTicker, createLoop, useLoop, useCanvas, useTween
ObservationReactive wrappers around browser observerscreateSight, createScrollProgress, createRenderState, createDevicePixelRatio, createMutation, useSight, useScrollProgress, useSize, useContainerQuery, useMediaQuery, useRenderState, useDevicePixelRatio, usePrefersReducedMotion, useMutation, prefersReducedMotion
LifecycleActivation signals composed from IO+MQL+rICcreateLifecycle, useLifecycle, whenIdle, useIdle, useWhenIdle
CompositionMount/unmount orchestration with transitionsPresence, usePresence, Swap, WhenVisible, WhenIdle, Defer
MathPure easing and interpolation functionsclamp, clamp01, lerp, inverseLerp, remap, easeOutCubic, easeOutQuart, easeOutBack, easeInOutCubic, linear
UtilityReact ref/callback patterns for phase usersuseSyncedRef, useStableCallback

Audit

When you review, optimize, or audit animation code, follow references/audit.md. It provides a repeatable procedure backed by a deterministic scanner (scripts/scan.mjs) that surfaces anti-pattern candidates before judgment.

API reference index

Each export has its own reference file. Read the relevant file when implementing or advising on that export.

Core (phase)

ExportUse whenReference
createLoopBuilding a lifecycle-aware rAF animation loopcreate-loop.md
createTickerNeed a raw frame clock without visibility managementcreate-ticker.md
createSightObserving element visibility (viewport + document)create-sight.md
createLifecycleProviding active/paused signal to your own renderercreate-lifecycle.md
createScrollProgressTracking intersection ratio (0–1) for revealscreate-scroll-progress.md
createRenderStateObserving content-visibility render-skip statecreate-render-state.md
createDevicePixelRatioTracking DPR changes in framework-free codecreate-device-pixel-ratio.md
whenIdleRunning a one-off callback when the browser is idlewhen-idle.md
prefersReducedMotionGating expensive setup or conditional importsprefers-reduced-motion.md
createMutationLifecycle-aware MutationObserver with rAF batchingcreate-mutation.md
PhaseError / isPhaseErrorHandling or classifying phase errorserrors.md

React (phase/react)

ExportUse whenReference
useLoopAnimating DOM elements in a per-frame loopuse-loop.md
useCanvasCanvas/WebGL animation with DPR + resize handlinguse-canvas.md
useLifecycleGating a renderer you own (three.js, Pixi, WebGL)use-lifecycle.md
useSightTracking visibility as a reactive phaseuse-sight.md
useTweenAnimating a single number into React render outputuse-tween.md
usePresenceCustom mount/unmount transitions (full control)use-presence.md
useScrollProgressDriving opacity/reveals from intersection ratiouse-scroll-progress.md
useMutationLifecycle-aware MutationObserver with rAF batchinguse-mutation.md
useRenderStatePausing raw work when a Defer subtree is skippeduse-render-state.md
useIdleBoolean that flips true once the browser is idleuse-idle.md
useWhenIdleRun a side effect (prefetch, import()) once idleuse-when-idle.md
useSizeReading element dimensions without reflowsuse-size.md
useContainerQueryBreakpoint matching against element width/heightuse-container-query.md
useMediaQueryReactive CSS media query subscriptionuse-media-query.md
usePrefersReducedMotionReactive reduced-motion boolean for non-phase animationsuse-prefers-reduced-motion.md
useDevicePixelRatioReactive DPR for buffer sizing outside useCanvasuse-device-pixel-ratio.md
useSyncedRefKeeping a ref always in sync with latest valueuse-synced-ref.md
useStableCallbackStable-identity function for memo'd childrenuse-stable-callback.md
PresenceShow/hide with enter/exit transitionspresence.md
WhenVisibleViewport-gated lazy mount (one-shot)when-visible.md
WhenIdleIdle-gated lazy mount for non-critical UIwhen-idle.md
DeferSkip painting off-screen content (keep in DOM)defer.md
SwapCoordinated exit-then-enter between N statesswap.md

Ease (phase/ease)

ExportUse whenReference
All easing + mathComputing animated values (lerp, clamp, easing curves)ease.md

Search across references

For concepts that span multiple references, grep is faster than guessing which file to open.

grep -ri "reduced motion" skills/phase/references/   # every export's motion behavior
grep -ri "data-phase" skills/phase/references/        # which components stamp phase attributes
grep -ri "cleanup\|unmount\|stop()" skills/phase/references/  # teardown behavior across hooks
grep -ri "pooled\|observer" skills/phase/references/  # which exports use shared observer pools
grep -ri "will-change" skills/phase/references/       # GPU layer guidance across contexts
grep -ri "FrameState\|frame\.delta\|frame\.elapsed" skills/phase/references/  # frame timing across loop primitives
grep -ri "starting:opacity\|data-\[phase=exiting\]" skills/phase/references/  # the canonical CSS transition pattern

Cross-cutting references

ReferenceUse when
decision-guide.mdChoosing between CSS, phase, or an external library
rendering-recipes.mdComposing Defer / WhenIdle / WhenVisible / useRenderState
performance.mdWriting or reviewing hot-path animation code
audit.mdAuditing existing animations for optimization opportunities
abort-signals.mdTearing down core primitives with an AbortSignal (signal option)
timed-sequences.mdBuilding multi-step timed animation sequences with useLoop

Full compiled document

For all references expanded inline: AGENTS.md

More skills from vercel