API reference
Everything is imported from "hintbeam" unless noted. It picks the web or React Native build for
you.
Naming
Hintbeam lives next to your UI kit, your router and your own code, so its names never clash with theirs:
- Every export is a hook (
use…) or says "tour":TourProvider,createTourTheme,TourStep,TOUR_STYLES. You can importcreateThemefrom MUI or Mantine in the same file. - The few exceptions are already specific:
defineTargets,TargetName,GuideOrb,createManualRouter. - Groups of built-ins share one shape:
TOUR_STYLES,TOUR_THEMES,TOUR_PATHS,TOUR_LABELS,TOUR_LIMITS. "hintbeam"is the app API. The engine underneath it (player, registry, geometry, path maths) is inhintbeam/core, for building renderers, light styles and plugins.
A check in CI keeps it this way: a new export with a generic name fails the build.
Defining
defineTargets(definitions, options?)
const targets = defineTargets(
{ search: { screen: "/food", about: "The search box" }, helpIcon: {} },
{ events: ["meal.logged"] },
);definitions[name].screen?: string | null— the screen it lives on; omit for every screen.definitions[name].about?: string— a note for tour authors.options.events?: string[]— event names steps may wait for (catches typos).
Returns TourTargets with names, events, has(name), screenOf(name), about(name).
defineTour(targets, tour)
Typed to your targets; validates immediately and throws a readable error if anything is wrong.
parseTour(json, targets) → { ok: true, tour } | { ok: false, problems }
For tours you load. Never throws. problems are { path, message }, e.g.
{ path: "steps[1].target", message: '"serach" is not a declared target. Did you mean "search"?' }.
validateTour(input, targets) → TourProblem[] · formatTourProblems(id, problems) → string
Types
interface Tour { id: string; steps: TourStep[]; meta?: TourMeta }
interface TourStep { target: string; title?: string; text: string; advanceOn?: AdvanceOn; meta?: Record<string, unknown> }
type AdvanceOn = "next" | "tap" | "arrive" | { event: string; timeout?: number };Limits (TOUR_LIMITS): 1–8 steps; title 2–60 characters; text 4–160; timeout 5–300 seconds, default 30.
<TourProvider>
| Prop | Type | |
|---|---|---|
targets |
TourTargets |
Required. From defineTargets |
router |
TourRouter |
From a router adapter. Default: one screen |
storage |
TourStorage |
Where progress is kept: browserTourStorage() (web), createTourStorage(AsyncStorage) (native). Default: in memory |
labels |
Partial<TourLabels> |
Every word users read |
theme |
TourThemeInput |
From createTourTheme(…), or any TourTheme values. Default: TOUR_THEMES.light |
path |
"wave" | "strands" | "straight" | "elbow" | "arc" | string |
Path style. Default "wave" |
tours |
Tour[] |
Tours startable by id: start("first-meal") |
user |
{ id?, traits? } |
Who is using the app — handed to plugins, never sent anywhere |
plugins |
TourPlugin[] |
Add-ons |
onEvent |
(event: TourEvent) => void |
Every start, step, complete, skip, stop |
renderStep |
(step: TourStepView) => ReactNode | null |
Your own card; null for headless |
Hooks
useTarget(name | names, { radius? }) → ref
Attach to the element. radius makes the highlight ring follow rounded corners. Typed to your targets
once you register them.
useScreenLink(screen, { radius? }) → ref
Attach to the link, tab or menu item that leads to screen.
useTourScroll(screen)
- Web: returns a
reffor an element that scrolls its own content. Not needed for the page itself. - React Native: returns props to spread onto the screen's
ScrollView:<ScrollView {...useTourScroll("/food")}>.
useTour()
start(tour | id) |
Play from the first step. Resolves to a TourStartResult: "started", "already-playing", "blocked" (by a plugin) or "unknown-tour" |
resume(tour) |
Play from where the user skipped, or from the start. Also "resumed" |
startOnce(tour) |
Play only if this user has never finished or skipped it; waits for saved progress first. Also "seen" |
next(), back() |
|
skip() |
Close and remember the step for resume |
stop() |
Close without remembering |
emit(event) |
Ends a step waiting for that event |
isActive, tour, step, index, total, isFirst, isLast, waitedTooLong |
Current state |
hasCompleted(tour), canResume(tour) |
useTours() → Tour[]
Every tour that can be started by id: those passed to TourProvider tours and those provided by
plugins. Useful for a help menu.
useTourStep() → TourStepView
Everything the built-in card uses: the state above plus where (visible / offscreen /
otherScreen / notFound), highlight ({ box, radius }), primary ("next", "done",
"showMe", "takeMeThere", "wait"), labels, and next, back, skip, showMe,
takeMeThere, refresh, and for custom UIs that follow the page: track() (measure the highlighted
element this instant, without finding it again) and key (changes with each step, for keying animations).
Routers
useNextRouter() (hintbeam/next) · useReactRouter() (hintbeam/react-router) ·
useExpoRouter() (hintbeam/expo-router) · useReactNavigation(ref) (hintbeam/react-navigation) ·
useRouterAdapter(screen, navigate) for your own router · createManualRouter(initial) for tests and
apps that track the screen themselves.
interface TourRouter {
currentScreen(): string | null;
navigate(screen: string): void | Promise<void>;
subscribe(listener: (screen: string | null) => void): () => void;
}Storage
browserTourStorage(key?) // web: localStorage, safe in server rendering (Next.js)
createTourStorage(store, key?) // any getItem/setItem store: AsyncStorage, MMKV, your API; never throws
mergeTourProgress(a, b) // join two devices' copies safely
interface TourStorage { load(): Promise<TourProgressState | null>; save(state: TourProgressState): Promise<void> }Without storage, progress is kept in memory until the page or app reloads.
Light styles
Pick one by name: path="wave" on TourProvider, or path in a theme. TOUR_PATHS holds the
built-ins: wave (default), strands, straight, elbow, arc. Their names are TourPathName.
Add your own with defineTourPath and give it to a plugin's paths. The geometry helpers come from
hintbeam/core:
import { defineTourPath } from "hintbeam";
import { center, routeThrough } from "hintbeam/core";
// A straight line to the middle of the target.
const direct = defineTourPath((from, to, options) => {
const end = center(to);
return routeThrough(from, [[from, end, end]], options?.samples);
});
// type TourPath = (from: Point, to: Box, options?: TourPathOptions) => PathRouteComponents
GuideOrb — the guide on its own: { mood?: "point" | "wait" | "search" | "done"; angle?; size?; accent?; accent2? }.
On React Native it takes { accent, accent2, angle, waiting, searching, reduceMotion, size? }.
Plugins
interface TourPlugin {
name: string;
apiVersion?: number; // TOUR_PLUGIN_API_VERSION, currently 1
paths?: Record<string, TourPath>;
tours?(context: TourPluginContext): unknown[] | Promise<unknown[]>; // validated before use
canStart?(check: { tour; completed; user; platform }): boolean | Promise<boolean>;
onEvent?(event: TourEvent, context: TourPluginContext): void;
setup?(api: TourPluginApi): void | (() => void); // start, stop, getState, addTours, drawnTargets…
}
interface TourPluginContext { user: { id?: string; traits?: Record<string, unknown> } | null; platform: string }Keyboard (web)
While a step shows: Esc skips, → next, ← back. Ignored while typing in a field.
Typing useTarget
Register your targets once for autocomplete everywhere:
declare module "hintbeam" {
interface Register {
targets: typeof targets;
}
}hintbeam/core
The engine without React. It has everything above that isn't a component, hook, theme or label, and adds the pieces underneath:
- Engine:
TourPlayer,ElementRegistry,resolveTarget,memoryTourStorage(the default),noRouter. - Geometry:
Box,Point,PathRoute,center,contains,inflate,edgeNormal,meetPoint,nearEdge,visibilityOf,scrollToShow. - Path maths:
routeThrough(start, segments, samples?). - Card placement:
placeStep(viewport, target, cardHeight, options)returns{ left, top, width, side, edge, guide }.edgeBeacon(viewport, target, "up" | "down", band?)returns where the light ends for a target scrolled away.
Use it to validate tours on a server or in CI, or to build a renderer, a light style or a new platform (see Architecture). It has no React import, so it is safe anywhere.