Headless usage
Build your own overlay with useTour().
When to go headless
@cairnkit/ui is one opinion about how a tour should look. If you have a design system and want the tour to be part of it, drop the package and drive the engine yourself.
@cairnkit/react ships no CSS and renders nothing on its own — everything below works without ui installed.
useTour()
| Prop | Type | Default | Description |
|---|---|---|---|
flow | TourFlow | null | — | The running flow, or null. |
step | TourStep | null | — | The current step. |
stepIndex | number | — | Zero-based. |
element | HTMLElement | null | — | The resolved DOM node. |
rect | TargetRect | null | — | Live viewport rect, tracked on rAF. |
status | "resolving" | "ready" | "missing" | — | Anchor resolution state. |
isPaused | boolean | — | On a route this flow does not cover. |
isLastStep | boolean | — | Whether Next should read as Done. |
showNext | boolean | — | False when the step needs a real action. |
showBeacon | boolean | — | Whether to draw the pulsing dot. |
advance / back / skip | () => void | — | Step controls. |
start / stop | (flowId?) => void | — | Start a flow, or end the current one. |
A minimal overlay
"use client";
import { useTour, useStepCopy } from "@cairnkit/react";
export function MyOverlay() {
const tour = useTour();
const { title, body } = useStepCopy(tour.flow, tour.step);
if (!tour.flow || !tour.step || tour.isPaused) return null;
return (
<div style={{ position: "fixed", inset: 0, pointerEvents: "none" }}>
{tour.rect && (
<div
style={{
position: "absolute",
top: tour.rect.top - 8,
left: tour.rect.left - 8,
width: tour.rect.width + 16,
height: tour.rect.height + 16,
boxShadow: "0 0 0 2px #4f46e5, 0 0 0 9999px rgb(0 0 0 / .6)",
borderRadius: tour.rect.radius + 4,
}}
/>
)}
<div style={{ pointerEvents: "auto" /* your card */ }}>
<h2>{title}</h2>
<p>{body}</p>
<button onClick={tour.skip}>Skip</button>
{tour.showNext && <button onClick={tour.advance}>Next</button>}
</div>
</div>
);
}Reusing individual pieces
@cairnkit/ui also exports its parts, so you can keep the spotlight and replace only the card.
import { Spotlight, StepCard, ProgressRail, Launcher } from "@cairnkit/ui";