Bubble Transition

ProTransitionsWebGLpowered by @vectorvesper/motion

A route-transition provider that refracts the outgoing page into the incoming one. Wraps the app, reads Next's router directly, and takes separate entry and exit durations. Next.js only.

Bubble Transition

Included with membership

Members install Bubble Transition with one command and get the full source, the usage code and the Framer remix, licensed for client work. One membership covers the whole library and the CLI.

When to use it

Reach for this when you want every navigation in the app to feel like one continuous surface rather than a series of page loads. It is an app-level decision, not a per-page one: the provider mounts once in the root layout and the sweep only happens for links that route through it.

  • Mount the provider once, in the root layout, above everything the sweep should cover.
  • Use TransitionLink in place of next/link for any route you want the sweep on.
  • Keep entryDuration and exitDuration close to each other — a long exit reads as lag rather than intent.

Avoid:

  • Do not mount a second provider in a nested layout. Two sweeps run at once and neither finishes cleanly.
  • Do not mix next/link and TransitionLink in the same nav. The links that skip the sweep look broken next to the ones that do not.
  • Do not use it for links to another origin — the sweep plays and then the browser leaves anyway.

Props

Page transitions that sweep a pane of coloured glass across the screen instead of fading. Wrap your app once, then use the link component in place of your router's, and every navigation gets it.

<BubbleTransitionProvider />

Wraps the app and owns the sweep. Mount it once, high enough that it covers everything the transition should cover.

PropTypeDefaultDescription
children*React.ReactNode—Your app.
colorstring"#7EACB5"Colour of the glass that sweeps across.
entryDurationnumber1.1Seconds the sweep takes to cover the screen. The route changes at the halfway point, so this is also what sets how long the old page stays visible.
exitDurationnumber1.1Seconds it takes to clear again, once the new page is ready.
disableAdaptiveQualitybooleanfalseRenders the sweep at full pixel density and keeps the refraction filter on regardless of what the device probe reports. It does not override a visitor's reduced-motion preference — that always wins.

<TransitionLink />

Stands in for a link. Runs the sweep, then navigates. Accepts every anchor attribute, so className, target and the rest pass through.

PropTypeDefaultDescription
href*string—Where to go.
children*React.ReactNode—The link's content.
onClick(e: React.MouseEvent) => void—Runs before the transition starts. Call preventDefault to stop the navigation entirely.

<usePageTransition />

hook

Access to the transition from anywhere inside the provider, for triggering a sweep from something that is not a link — a form submit, a menu selection. Returns isTransitioning and startTransition. Also exported as useBubbleTransition; the two are the same hook.

PropTypeDefaultDescription
startTransition*(href: string, clickPos?: Point) => void | (clickPos: Point, onHalfway: () => void) => void—Two ways to call it. Pass a href and it navigates there at the halfway point. Pass a click position and a callback instead and it navigates nowhere — it runs your callback under the cover of the sweep, which is how you use it to swap a view, open a panel or change state rather than change route. Either way the origin point sets where the bubble opens from.
isTransitioning*boolean—Whether a sweep is currently running. A second call is ignored until it finishes.
The sweep covers the whole viewport on every navigation and has no reduced-motion guard. Swap in the blur provider, or drop both, when the preference is set — this is the kind of full-screen motion that preference exists for.
Use one provider or the other. Nesting the glass and blur providers runs two transitions over each other.
Client components. The provider holds transition state and draws per frame, so it has to run in the browser — mount it inside your root layout's client boundary.
It draws on the shared frame loop rather than starting one of its own, so putting several animated components on a page costs one callback, not one each, and the scheduler can set decorative work aside when frames get tight. The sweep is deliberately not deferred — it is a full-screen page transition, so it has to be compiled and ready the moment a link is clicked. If the browser takes the drawing context away — which it does under memory pressure, and more often on a page with several of these — the component reports it and rebuilds once the context comes back, instead of leaving a permanently black rectangle.
Back to all components