segue
Documentation

Documentation

Build your first Route Transition

Connect a destination, Preset, Provider, and Link for coordinated navigation.

This guide shows how the pieces of a Route Transition connect. Use the example as a starting point, then adapt the temporary UI and animation to your application.

Before you begin

You need a Next.js App Router project that meets Segue's exact requirements and has @humaan/segue installed. The example uses the browser's Web Animations API, but you can use another animation library.

How the pieces connect

The manifest assigns a Preset to the destination. The Preset defines the Cover. The Provider makes both available across route changes. Segue.Link starts coordinated navigation.

1. Define the destination

Keep the Preset, rendition, and Transition Image prop types in one server-safe module. Both the manifest and client API will use these types, which prevents their generic parameters from drifting apart.

// lib/segue-types.ts
export type PresetName = "project-cover";
 
export type RenditionData = {
  readonly src: string;
  readonly width: number;
  readonly height: number;
};
 
export type TransitionImageProps = {
  readonly alt: string;
  readonly className?: string;
};

Create the destination route:

// app/projects/field-study/page.tsx
export default function FieldStudyPage() {
  return <h1>Field study</h1>;
}

Then assign the project-cover Preset to its exact path. The required imagesByRole object is empty because this Cover doesn't use a Transition Image.

// lib/segue-manifest.ts
import { defineManifest } from "@humaan/segue";
import type { PresetName, RenditionData } from "./segue-types";
 
export const segueManifest = defineManifest<PresetName, RenditionData>({
  routes: [
    {
      path: "/projects/field-study",
      preset: "project-cover",
      imagesByRole: {},
    },
  ],
});

2. Create your Segue API

Call createSegue once with the same shared types. Export this single object for the rest of your client code.

// components/segue.tsx
"use client";
 
import { createSegue } from "@humaan/segue/client";
import type {
  PresetName,
  RenditionData,
  TransitionImageProps,
} from "@/lib/segue-types";
 
export const Segue = createSegue<
  PresetName,
  RenditionData,
  TransitionImageProps
>();

3. Define a Preset

Define the temporary UI and Cover animation. Every Route-Transition Preset has four required properties:

  • imageRoles lists the Transition Image roles the Preset needs.
  • reducedMotion tells Segue whether to run or skip the Preset when the visitor prefers reduced motion.
  • render returns the temporary UI.
  • animate runs the Cover after Segue adds that UI to the document.

The animate callback receives root, the wrapper around the rendered UI, and navigation.signal, which aborts when Segue stops the transition. Pass the animation and signal to waitForAnimation; it handles native animation cancellation, including an already-aborted signal.

// components/route-transitions.tsx
"use client";
 
import type { ComponentProps, ReactNode } from "react";
import { waitForAnimation } from "@humaan/segue/client";
import { segueManifest } from "@/lib/segue-manifest";
import { Segue } from "./segue";
 
const presets = {
  "project-cover": Segue.definePreset({
    imageRoles: [],
    reducedMotion: "skip",
    render: () => <div className="project-cover" />,
    animate: async ({ root, navigation }) => {
      const cover = root.querySelector<HTMLElement>(".project-cover");
      if (!cover) return;
 
      const animation = cover.animate(
        [
          { transform: "translateX(-100%)" },
          { transform: "translateX(0)" },
        ],
        {
          duration: 600,
          easing: "cubic-bezier(.76, 0, .24, 1)",
          fill: "forwards",
        },
      );
      await waitForAnimation(animation, navigation.signal);
    },
  }),
} satisfies ComponentProps<typeof Segue.Provider>["presets"];

The fill: "forwards" option makes the Cover's final state durable while Segue waits for route commit. Without it, the panel can return to its initial off-screen style after the animation finishes but before the destination appears.

4. Mount the Provider

Append this component to components/route-transitions.tsx. Mount only one Provider per browser window in a layout that remains mounted across the source and destination routes.

export function RouteTransitions({ children }: { readonly children: ReactNode }) {
  return (
    <Segue.Provider manifest={segueManifest} presets={presets}>
      {children}
    </Segue.Provider>
  );
}

Wrap your application in the root layout:

// app/layout.tsx
import type { ReactNode } from "react";
import { RouteTransitions } from "@/components/route-transitions";
 
export default function RootLayout({ children }: { readonly children: ReactNode }) {
  return (
    <html lang="en">
      <body>
        <RouteTransitions>{children}</RouteTransitions>
      </body>
    </html>
  );
}

Segue throws an error if another Provider mounts in the same browser window.

Render Segue.Link on the source page for the coordinated navigation. It accepts the normal Next.js Link and anchor props.

// app/page.tsx
"use client";
 
import { Segue } from "@/components/segue";
 
export default function HomePage() {
  return (
    <main>
      <h1>Projects</h1>
      <Segue.Link href="/projects/field-study">
        View the field study
      </Segue.Link>
    </main>
  );
}

Because the manifest assigns project-cover to /projects/field-study, Segue runs that Preset before following this link. A Segue.Link to a path outside the manifest still navigates without a Preset.

6. Style the Cover

Make the Cover fill the viewport and appear above the current route:

.project-cover {
  position: fixed;
  inset: 0;
  z-index: 100;
  pointer-events: none;
  background: #d7ff64;
  transform: translateX(-100%);
}

Start your application and select View the field study. The green panel crosses the current route before /projects/field-study appears.

Next steps

initial