Bubble Transition
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.
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.
| Prop | Type | Default | Description |
|---|---|---|---|
children* | React.ReactNode | — | Your app. |
color | string | "#7EACB5" | Colour of the glass that sweeps across. |
entryDuration | number | 1.1 | Seconds 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. |
exitDuration | number | 1.1 | Seconds it takes to clear again, once the new page is ready. |
disableAdaptiveQuality | boolean | false | Renders 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.
| Prop | Type | Default | Description |
|---|---|---|---|
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 />
hookAccess 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.
| Prop | Type | Default | Description |
|---|---|---|---|
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. |