npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ssgoi/react

v7.4.1

Published

Native app-like page transitions for React and Next.js. Make a web app, PWA or WebView app feel native, with Remix, React Router and TanStack Router support.

Readme

@ssgoi/react

Native app-like page transitions for React and Next.js. Make a web app, PWA or WebView app (Capacitor, Tauri, Ionic) feel like a native app without replacing your router.

SSGOI live showcase

Live demos · Hero, Zoom, Film, and Sheet in motion

npm install @ssgoi/react

Agent setup guide: https://ssgoi.dev/llms.txt

Router helpers

Install this framework package once; router helpers are optional subpaths. Next.js is the only helper not marked experimental. The other router helpers are experimental APIs.

| Router | Import | API | | ----------------------- | ------------------------------ | -------------------- | | Next.js | @ssgoi/react/nextjs | SsgoiRouteBoundary | | Remix 2 | @ssgoi/react/remix | SsgoiRouteBoundary | | React Router | @ssgoi/react/react-router | SsgoiRouteBoundary | | TanStack Router / Start | @ssgoi/react/tanstack-router | SsgoiRouteBoundary |

See the framework guide for wiring and limits.

Contents

  • Next.js
  • Persistent layouts and sliding tabs
  • React Router
  • TanStack Router
  • Config
  • Effect index

Tune or define motion

Built-in presets accept { override: { forward, backward } } as their second argument. Callbacks receive { animation, context }, preserve the preset's concrete animation type, and edit named children with animation.select("in").set({ integrator }). Use animation.set({ integrator }) to retune the whole group. Springs use stiffness/damping; custom integrator instances are accepted.

Motion overrides · Custom definitions and integrators

Next.js

Keep one config and one <Ssgoi> above route boundaries.

// app/ssgoi-provider.tsx
"use client";

import { Ssgoi } from "@ssgoi/react";
import { drill, slide } from "@ssgoi/react/view-transitions";

const config = {
  transitions: [
    { on: "/posts/**", except: "/posts", transition: drill() },
    {
      ordered: ["/products/all", "/products/electronics", "/products/fashion"],
      transition: slide(),
    },
  ],
};

export function SsgoiProvider({ children }) {
  return <Ssgoi config={config}>{children}</Ssgoi>;
}

Build the layout shell next:

// app/layout.tsx
import { SsgoiProvider } from "./ssgoi-provider";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <main className="relative z-0 min-h-dvh overflow-x-clip">
          <SsgoiProvider>{children}</SsgoiProvider>
        </main>
      </body>
    </html>
  );
}

When React removes an outgoing route, SSGOI temporarily reinserts that detached DOM node with position: absolute. relative gives it a containing block, z-0 creates the local stacking context, and overflow-x-clip prevents horizontal transition flashes. These classes belong on the shell around <Ssgoi>, not on route boundaries.

Route boundary

A React route boundary is a keyed element marked with a route id:

<div key={boundary.key} data-ssgoi-transition={boundary.id}>
  {children}
</div>
  • Changing key makes React unmount the old region and mount the new one.
  • The attribute identifies those regions for SSGOI config matching.
  • <Ssgoi> observes that lifecycle and runs OUT/IN; it does not navigate or change the key.

The legacy <SsgoiTransition> wrapper only added this attribute and is deprecated. Set data-ssgoi-transition directly on the keyed application boundary.

Use @ssgoi/react/nextjs for the ready-made boundary. If the application needs its own policy layer, the following manual example keeps route logic behind semantic names so layouts cannot invent inconsistent keys:

// app/ssgoi-route-boundary.tsx
"use client";

import { type ReactNode } from "react";
import { usePathname, useSelectedLayoutSegments } from "next/navigation";

type BoundaryName = "app-shell" | "main-content" | "project-content";
type BoundaryIdentity = { id: string; key: string };

const INTERCEPTION_PREFIX = /^(?:\(\.\.\.\)|\(\.\.\)|\(\.\))+/;

function isRouteGroup(segment: string) {
  return segment.startsWith("(") && segment.endsWith(")");
}

function pathFromSegments(segments: string[]) {
  const path = segments
    .filter((segment) => !isRouteGroup(segment))
    .map((segment) => segment.replace(INTERCEPTION_PREFIX, ""))
    .filter(Boolean)
    .join("/");

  return path ? `/${path}` : null;
}

function resolveBoundary(
  name: BoundaryName,
  pathname: string,
  segments: string[],
): BoundaryIdentity {
  const ownedRoute = pathFromSegments(segments) ?? pathname;

  switch (name) {
    case "app-shell": {
      const routeGroup = segments.find(isRouteGroup);
      if (routeGroup === "(main)") {
        return { id: ownedRoute, key: "main-shell" };
      }

      const project = ownedRoute.match(/^\/projects\/[^/]+/)?.[0];
      return { id: ownedRoute, key: project ?? ownedRoute };
    }
    case "project-content": {
      const project = pathname.match(/^\/projects\/[^/]+/)?.[0];
      const child = pathFromSegments(segments);
      const id = project
        ? child
          ? `${project}${child}`
          : project
        : ownedRoute;
      return { id, key: id };
    }
    case "main-content":
      return { id: ownedRoute, key: ownedRoute };
  }
}

export function SsgoiRouteBoundary({
  children,
  name,
}: {
  children: ReactNode;
  name: BoundaryName;
}) {
  const pathname = usePathname();
  const segments = useSelectedLayoutSegments("children");
  const boundary = resolveBoundary(name, pathname, segments);

  return (
    <div key={boundary.key} data-ssgoi-transition={boundary.id}>
      {children}
    </div>
  );
}

Persistent layouts

Place one named boundary in the common app layout, then add child boundaries only around routed content that changes while its parent remains mounted.

// app/layout.tsx — replace the provider line from the shell example.
<SsgoiProvider>
  <SsgoiRouteBoundary name="app-shell">{children}</SsgoiRouteBoundary>
</SsgoiProvider>
// app/(main)/layout.tsx
export default function MainLayout({ children }) {
  return (
    <>
      <SsgoiRouteBoundary name="main-content">{children}</SsgoiRouteBoundary>
      <BottomNav />
    </>
  );
}
  • Main → main: only the inner content boundary changes; the nav stays.
  • Main → detail: the common app-shell key changes; the nav leaves with it.
  • Project tab → project tab: the current project base-path key stays; a project content boundary can change below its header and tabs.

Use Next.js route groups to describe layout ownership, not to duplicate the outer boundary:

app/
  layout.tsx                 # one <Ssgoi> + one named app-shell boundary
  (main)/layout.tsx
  (main)/page.tsx
  (main)/search/page.tsx
  (detail)/post/[id]/page.tsx

For Next.js parallel and intercepting routes, the browser pathname may point at a modal while the background children slot has not changed. Resolve that boundary’s id/key from useSelectedLayoutSegments("children"). In the layout that owns @modal, useSelectedLayoutSegment("modal") !== null is an explicit active-modal check. A soft intercept keeps the background key; direct entry to the same URL resolves to the detail slot and receives a detail key.

Full pattern: https://ssgoi.dev/llms/bottom-nav.txt

React Router

Replace usePathname() with useLocation() and render the boundary in a nested layout route:

const { pathname } = useLocation();

return (
  <div key={pathname} data-ssgoi-transition={pathname}>
    <Outlet />
  </div>
);

TanStack Router

Read the pathname from router state and place the boundary in a parent route:

const pathname = useRouterState({
  select: (state) => state.location.pathname,
});

Config

import { drill, slide, zoom } from "@ssgoi/react/view-transitions";

const config = {
  transitions: [
    {
      on: "/posts/**",
      except: "/posts",
      transition: drill(),
    },
    {
      from: "/gallery",
      to: "/gallery/*",
      transition: zoom(),
    },
    {
      ordered: ["/tabs/a", "/tabs/b"],
      transition: slide(),
    },
  ],
};
  • on: route family.
  • from/to: precise pair.
  • ordered: directional sequence.
  • Scroll is automatic: on and from/to restore from and reset to; ordered restores both. Override with preserveScroll: { from: boolean, to: boolean }, or share one position across the rule's pages with preserveScroll: "shared".
  • Patterns support exact paths, a * path segment, and suffix **.
  • priority overrides path specificity.

Effect index

  • fade: unrelated pages.
  • drill: list → detail.
  • slide: ordered tabs.
  • axis: sibling destinations.
  • sheet: modal-like routes.
  • zoom: card/image → detail.
  • hero: shared elements and page chrome.
  • scroll: vertical sequences.
  • strip, film, rotate, blind, jaemin: expressive transitions.

References: https://ssgoi.dev/llms.txt#7-transition-index

Low-level APIs

transition() and the auto-key plugins remain available for element-level mount/unmount animations; they are separate from route boundaries.

License

MIT