React
Vite, react-router, TanStack, or no router at all.
The router adapter
cairnkit is not a Next library. Everything route-related — resumeAt, handoffRoutes, pauseRoutes, the route advance rule — goes through one two-method interface.
type RouterAdapter = {
usePathname(): string;
navigate(href: string): void;
};That is the entire surface. Any router is about ten lines.
react-router
// v7: import from "react-router". v6: from "react-router-dom".
import { useLocation, useNavigate } from "react-router";
import type { RouterAdapter } from "@cairnkit/react";
export function useReactRouterAdapter(): RouterAdapter {
const navigate = useNavigate();
return {
usePathname: () => useLocation().pathname,
navigate: (href) => navigate(href),
};
}TanStack Router
import { useRouterState, useNavigate } from "@tanstack/react-router";
import type { RouterAdapter } from "@cairnkit/react";
export function useTanStackAdapter(): RouterAdapter {
const navigate = useNavigate();
return {
usePathname: () => useRouterState({ select: (s) => s.location.pathname }),
navigate: (href) => navigate({ to: href }),
};
}No router at all
A single-page app with no routing still works, and you do not have to write an adapter for it. memoryRouter ships in the package and is the same object the test suite runs on.
import { CairnProvider, memoryRouter } from "@cairnkit/react";
<CairnProvider flows={flows} router={memoryRouter}>It reads window.location.pathname on every render and navigates with window.location.assign. Every route feature is driven off that one value, so all of them work: the guide navigates, the browser loads the page, and the engine reads the new path and picks up where it should.
Mounting
import { BrowserRouter } from "react-router"; // v6: react-router-dom
import { CairnProvider } from "@cairnkit/react";
import { CairnOverlay, TourLauncher } from "@cairnkit/ui";
import "@cairnkit/ui/styles.css";
import { useReactRouterAdapter } from "./router-adapter";
import { flows } from "./walkthrough/flows";
function Shell() {
return (
<CairnProvider flows={flows} router={useReactRouterAdapter()}>
<Routes>{/* ... */}</Routes>
<CairnOverlay />
<TourLauncher flowId="onboarding" />
</CairnProvider>
);
}
export const App = () => (
<BrowserRouter>
<Shell />
</BrowserRouter>
);Vite notes
- Import the stylesheet once, anywhere:
import "@cairnkit/ui/styles.css". - If you develop cairnkit alongside your app via a workspace link, add
resolve.dedupe: ["react", "react-dom"]— symlinked packages can otherwise resolve a second copy of React and hooks break.
A complete working example lives in examples/react-vite.