Open almost any native mobile app and tap into a list item. The detail screen does not just appear; it slides in from the right while the list slides away, or the tapped card expands smoothly into a full page. Hit back and the motion reverses, telling your eyes exactly where you came from. Now do the same thing on a typical website. The old page vanishes, the new page pops in, and you are left to figure out what happened. Pages on the web swap. They do not transition.
@ssgoi/react is one answer to that gap. SSGOI (pronounced like a quick swipe) is a page transition library that turns ordinary route changes into fluid, app-like animations: iOS-style drill-ins, bottom sheets, shared-element morphs, Material shared-axis slides, and more. The headline trick is that it does this in every browser including Safari, with server-side rendering intact, and without caring which router you use. You wrap your app once, tag your pages, and pick a transition. That is the whole setup.
A quick note on naming before we go further, because npm is confusing here. The bare ssgoi package is an old, Svelte-only release that has not moved since 2024. The library you actually want lives under scoped packages: @ssgoi/react for React and Next.js, @ssgoi/svelte, @ssgoi/vue, @ssgoi/solid, and @ssgoi/angular, all sharing a framework-agnostic @ssgoi/core engine. They sit at version 6.x and are updated regularly. This article uses the React package, but the concepts carry across all of them.
What Makes It Different
Plenty of tools can animate things on a page. The interesting question is why you would reach for SSGOI instead of the browser's own View Transitions API or a general-purpose animation library. A few things set it apart.
It works everywhere. The native View Transitions API is genuinely nice, but its full feature set landed in Chromium first and the cross-browser story is still uneven, especially across different framework setups. SSGOI does not depend on it. It implements its own transition mechanism on top of the Web Animations API, so the same code behaves identically in Chrome, Firefox, and Safari.
It uses spring physics, not just easing curves. Transitions feel mechanical when they follow a fixed cubic-bezier. SSGOI models motion with springs, which gives that slightly organic, weighted feel native platforms use. Crucially, it pre-computes those springs into Web Animations keyframes up front rather than running a spring solver on every frame. The animation then runs on the compositor, GPU-accelerated, leaving your JavaScript main thread free. That is how it can claim a smooth 60fps even while React is busy hydrating the incoming page.
It is router agnostic and direction aware. SSGOI does not hook into a specific router's internals. It matches route paths you declare and figures out forward versus backward navigation on its own, preserving scroll position and back/forward state. So a drill-in animates one way going into a detail page and reverses cleanly when you hit back, with no extra wiring.
Getting It Installed
Add the package for your framework. For React and Next.js:
npm install @ssgoi/react
Or with yarn:
yarn add @ssgoi/react
The React package pulls in @ssgoi/core automatically, so there is nothing else to install. Swap in @ssgoi/svelte, @ssgoi/vue, @ssgoi/solid, or @ssgoi/angular if you are on a different framework; the API mirrors what you see below.
Wiring Up Your First Transition
SSGOI needs two things: a single wrapper near the root of your app that holds the transition config, and a marker on each page so it knows where the page boundaries are.
Start with the wrapper. In a Next.js App Router project this goes in your root layout:
"use client";
import { Ssgoi } from "@ssgoi/react";
import { drill, fade } from "@ssgoi/react/view-transitions";
const config = {
transitions: [
// iOS-style drill-in when entering a post, ease back out
drill({ enter: "/post/*", exit: "*" }),
// Calm cross-fade between the top-level pages
fade({ paths: ["/", "/about"] }),
],
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html>
<body>
<Ssgoi config={config}>
{/* relative + z-0 matters: see the note below */}
<div className="relative z-0">{children}</div>
</Ssgoi>
</body>
</html>
);
}
Then mark each page with a data-ssgoi-transition attribute whose value is the path. The path is what SSGOI matches against the rules in your config:
// app/page.tsx
export default function HomePage() {
return (
<main data-ssgoi-transition="/">
<h1>Home</h1>
</main>
);
}
// app/post/[id]/page.tsx
export default function PostPage({
params,
}: {
params: { id: string };
}) {
return (
<main data-ssgoi-transition={`/post/${params.id}`}>
<h1>Post Detail</h1>
</main>
);
}
That is genuinely the entire setup. Navigate from / to /post/42 and you get an iOS-style drill-in; navigate back and it reverses; move between / and /about and you get a cross-fade.
One detail worth understanding, because it explains the relative z-0 wrapper. When you leave a page, SSGOI clones the outgoing page back into the DOM with position: absolute so it can animate out while the new page animates in over the same layout space. That absolutely positioned clone needs a positioned ancestor to anchor to, which is exactly what the relative z-0 container provides. Skip that wrapper and the outgoing page can jump to the corner of the screen during the animation. Add it once and forget about it.
Picking the Right Motion
The real value of SSGOI is its catalog of ready-made transitions, each modeled on a motion pattern you already recognize from native apps. They import from @ssgoi/react/view-transitions, and they fall into three flavors based on how they match routes.
import {
fade,
drill,
slide,
scroll,
axis,
sheet,
hero,
zoom,
} from "@ssgoi/react/view-transitions";
// Symmetric: both directions animate the same ({ paths })
fade({ paths: ["/", "/about"] });
// Directional: enter and exit get different physics ({ enter, exit })
drill({ enter: "/post/*", exit: "*" }); // list to detail, iOS push
sheet({ enter: "/compose", exit: "*" }); // bottom sheet slides up
// Ordered: order in the array decides forward vs back ({ paths })
slide({ paths: ["/tabs/a", "/tabs/b"] }); // horizontal tabs
scroll({ paths: ["/step-1", "/step-2"] }); // vertical onboarding
axis({ paths: ["/feed", "/profile"] }); // Material shared axis
The mapping to real UI is the fun part. drill is the iOS push you feel tapping into a settings row. sheet is the bottom sheet that slides up for a compose or filter screen. slide suits a horizontal tab bar where left and right have meaning. axis is Google's Material shared-axis motion. scroll reads like a vertical onboarding flow. And there are more beyond these: the full set includes strip, blind, film, rotate, and jaemin, so you have plenty to audition against your own navigation. Because each factory returns a path group and SSGOI flattens nested arrays, you can compose as many rules as your app needs in that one transitions array.
Morphing Elements Across Pages
Page-level transitions move whole screens. Sometimes you want a single element to travel between pages instead, the move where a thumbnail in a grid grows into the hero image of a detail page. That is a shared-element transition, and SSGOI handles it with the hero and zoom presets.
These work a little differently from page transitions because SSGOI needs to know which element on the source page corresponds to which element on the destination. You tell it with matching data attributes. On the list side, tag the source element; on the detail side, tag the destination with the same key:
// In the config
hero({ paths: ["/products", "/products/*"] });
// On the products list page
<img
src={product.thumb}
data-hero-exit-key={`product-${product.id}`}
/>;
// On the product detail page
<img
src={product.image}
data-hero-enter-key={`product-${product.id}`}
/>;
When you navigate, SSGOI lifts a clone of the matched element above both pages and smoothly morphs it from its old position and size to its new one. You can refine the morph with data-hero-aspect-ratio="16/9" for elements that need letterboxing and data-hero-radius="12" so rounded corners animate cleanly rather than snapping. The hero transition also comes in a couple of variants: one where the surrounding page chrome snaps while the clone morphs, and a smoother one where the pages cross-fade while the shared element floats above the whole thing.
The zoom transition is the close cousin for card-to-detail expansions, where a card visibly grows to fill the screen:
zoom({ paths: ["/gallery", "/photo/*"], type: "expand" });
Pair it with data-zoom-exit-key on the card and data-zoom-enter-key on the detail view, the same matching idea as hero.
Adapting to the Device
A motion that feels great on a phone can feel heavy on a wide desktop monitor. Recent versions of SSGOI let your transition config be a function rather than a static object, receiving context such as whether the current device is mobile. That lets you serve a drill-in on touch devices and a gentler fade on desktop from the same codebase:
const config = ({ isMobile }: { isMobile: boolean }) => ({
transitions: isMobile
? [drill({ enter: "/post/*", exit: "*" })]
: [fade({ paths: ["*"] })],
});
On the React side, the library has also kept pace with the framework's direction, adding support for transitions that cooperate with React's Activity component and Next.js cache-components page transitions, so you are not fighting the framework's own rendering model to get smooth motion.
Should You Reach for It
SSGOI occupies a specific and useful niche. If you want general-purpose, fine-grained control over individual element animations, a library like Motion is still the broader toolbox, and if you only need a basic cross-fade in a Chromium-first audience, the native View Transitions API may be all you need. But if your goal is "I want my web app to feel like a native app when navigating between pages, with drill-ins and shared elements and proper back-button behavior, in every browser, on whatever framework I already use," SSGOI hits that target with remarkably little code.
The honest caveats are small. Remember to use the scoped @ssgoi/* package and not the stale bare ssgoi one, keep the positioned wrapper around your content, and make sure your hero and zoom keys actually match across pages. Get those right and you are mostly choosing presets and watching your routes come to life. For an app where navigation is the experience, that is a very good trade.