@tour-kit/core API
API reference for @tour-kit/core: useTour, useStep, useFocusTrap, createTour, createStep, and all utility exports
Complete API reference for the core package. This package provides framework-agnostic logic for tours.
For prose-style documentation of each symbol, see the @tour-kit/core package overview, or jump to a specific hook, provider, type, or utility.
Providers
TourKitProvider
Global configuration provider for tour-kit.
import { TourKitProvider } from '@tour-kit/core';
<TourKitProvider config={config} dir="ltr">
{children}
</TourKitProvider>| Prop | Type | Description |
|---|---|---|
config | TourKitConfig | Global configuration for all tours |
dir | 'ltr' | 'rtl' | 'auto' | Text direction (auto detects from document) |
onTourStart | (tourId: string) => void | Called when any tour starts |
onTourComplete | (tourId: string) => void | Called when any tour completes |
onTourSkip | (tourId: string, stepIndex: number) => void | Called when any tour is skipped |
onStepView | (tourId: string, stepId: string, stepIndex: number) => void | Called when a step is viewed |
TourProvider
Provider for a single tour instance.
import { TourProvider } from '@tour-kit/core';
<TourProvider tours={tours} router={router}>
{children}
</TourProvider>| Prop | Type | Description |
|---|---|---|
tours | Tour[] | Array of tour definitions |
router | RouterAdapter | Router adapter for multi-page tours |
onStart | (tourId: string) => void | Called when tour starts |
onComplete | (tourId: string) => void | Called when tour completes |
onSkip | (tourId: string, stepIndex: number) => void | Called when tour is skipped |
onStepChange | (step: TourStep, index: number) => void | Called on step change |
Hooks
useTour
Main hook for tour control. Returns tour state and actions.
import { useTour } from '@tour-kit/core';
const {
isActive,
currentStep,
currentStepIndex,
totalSteps,
isFirstStep,
isLastStep,
progress,
isLoading,
isTransitioning,
start,
next,
prev,
goTo,
skip,
complete,
stop,
isStepActive,
getStep,
} = useTour(tourId?);| Return | Type | Description |
|---|---|---|
isActive | boolean | Whether a tour is currently active |
currentStep | TourStep | null | Current step data |
currentStepIndex | number | 0-based index of current step |
totalSteps | number | Total number of steps |
isFirstStep | boolean | Whether on first step |
isLastStep | boolean | Whether on last step |
progress | number | Progress percentage (0-100) |
isLoading | boolean | Whether tour is loading |
isTransitioning | boolean | Whether transitioning between steps |
start | (tourIdOrIndex?, stepIndex?) => void | Start a tour |
next | () => void | Go to next step |
prev | () => void | Go to previous step |
goTo | (stepIndex: number) => void | Go to specific step |
skip | () => void | Skip/exit the tour |
complete | () => void | Mark tour as complete |
stop | () => void | Stop without completion |
isStepActive | (stepId: string) => boolean | Check if step is active |
getStep | (stepId: string) => TourStep | undefined | Get step by ID |
useStep
Hook for individual step control.
import { useStep } from '@tour-kit/core';
const { isActive, isVisible, hasCompleted, targetElement, targetRect } = useStep(stepId);useSpotlight
Hook for spotlight overlay control.
import { useSpotlight } from '@tour-kit/core';
const { isVisible, targetRect, overlayStyle, cutoutStyle, show, hide, update } = useSpotlight();useElementPosition
Hook for tracking element position.
import { useElementPosition } from '@tour-kit/core';
const { element, rect, scrollParent, update } = useElementPosition(target);useKeyboardNavigation
Hook for keyboard navigation setup.
import { useKeyboardNavigation } from '@tour-kit/core';
useKeyboardNavigation(config?);useFocusTrap
Hook for focus trap management.
import { useFocusTrap } from '@tour-kit/core';
const { containerRef, activate, deactivate } = useFocusTrap(enabled?);usePersistence
Hook for tour state persistence.
import { usePersistence } from '@tour-kit/core';
const {
getCompletedTours,
getSkippedTours,
getDontShowAgain,
getLastStep,
markCompleted,
markSkipped,
setDontShowAgain,
saveStep,
reset,
} = usePersistence(config?);useRoutePersistence
Hook for multi-page tour persistence.
import { useRoutePersistence } from '@tour-kit/core';
const { save, load, clear, isStale } = useRoutePersistence(config);useMediaQuery
Hook for responsive media query tracking.
import { useMediaQuery } from '@tour-kit/core';
const matches = useMediaQuery('(min-width: 768px)');usePrefersReducedMotion
Hook for motion preference detection.
import { usePrefersReducedMotion } from '@tour-kit/core';
const prefersReducedMotion = usePrefersReducedMotion();useAdvanceOn
Hook for event-based step advancement.
import { useAdvanceOn } from '@tour-kit/core';
useAdvanceOn({
event: 'click',
selector: '#target',
onAdvance: () => next(),
});Utilities
Tour Creation
import { createTour, createNamedTour, createStep, createNamedStep } from '@tour-kit/core';
// Auto-generated ID
const tour = createTour(steps, options?);
// Named tour
const namedTour = createNamedTour('my-tour', steps, options?);
// Auto-generated step ID
const step = createStep(target, content, options?);
// Named step
const namedStep = createNamedStep('step-1', target, content, options?);Storage
import {
createStorageAdapter,
createNoopStorage,
createCookieStorage,
createPrefixedStorage,
safeJSONParse,
} from '@tour-kit/core';
// Create adapter
const adapter = createStorageAdapter('localStorage'); // or 'sessionStorage'
// Cookie storage
const cookies = createCookieStorage({ expires: 30 });
// Prefixed storage
const prefixed = createPrefixedStorage(adapter, 'myapp');
// SSR-safe storage
const noop = createNoopStorage();
// Safe JSON parsing
const data = safeJSONParse<MyType>(json, defaultValue);DOM Utilities
import {
getElement,
isElementVisible,
isElementPartiallyVisible,
waitForElement,
getFocusableElements,
getScrollParent,
} from '@tour-kit/core';
// Resolve element from selector, ref, or HTMLElement
const element = getElement('#my-element');
// Check visibility
const visible = isElementVisible(element);
const partial = isElementPartiallyVisible(element);
// Wait for element to appear
const el = await waitForElement('#dynamic', { timeout: 5000 });
// Get focusable elements for focus trap
const focusable = getFocusableElements(container);
// Find scroll parent
const scrollParent = getScrollParent(element);Position Utilities
import {
calculatePosition,
calculatePositionWithCollision,
getElementRect,
getViewportDimensions,
parsePlacement,
wouldOverflow,
getOppositeSide,
getFallbackPlacements,
mirrorPlacementForRTL,
} from '@tour-kit/core';
// Calculate tooltip position
const pos = calculatePosition(targetRect, tooltipSize, 'bottom', offset?);
// With collision detection
const result = calculatePositionWithCollision(targetRect, tooltipSize, 'bottom', options?);
// Parse placement string
const { side, alignment } = parsePlacement('bottom-start');
// RTL support
const rtlPlacement = mirrorPlacementForRTL('left-start', true);Scroll Utilities
import {
scrollIntoView,
scrollTo,
getScrollPosition,
lockScroll,
} from '@tour-kit/core';
// Scroll element into view
await scrollIntoView(element, { behavior: 'smooth', block: 'center' });
// Scroll to position
scrollTo(container, position, 'smooth');
// Get scroll position
const { x, y } = getScrollPosition(container?);
// Lock scroll
const unlock = lockScroll();
// ... later
unlock();Accessibility Utilities
import {
announce,
generateId,
prefersReducedMotion,
getStepAnnouncement,
} from '@tour-kit/core';
// Screen reader announcement
announce('Step 1 of 5: Welcome', 'polite');
// Generate unique ID
const id = generateId('tour'); // e.g., 'tour-abc123'
// Check motion preference (non-hook)
const reduced = prefersReducedMotion();
// Format step announcement
const text = getStepAnnouncement('Welcome', 1, 5); // "Step 1 of 5: Welcome"Logger
import { logger } from '@tour-kit/core';
// Configure
logger.configure({ level: 'warn', prefix: '[MyApp]' });
// Use
logger.debug('Debug message');
logger.info('Info message');
logger.warn('Warning message');
logger.error('Error message');Types
Configuration Types
interface TourKitConfig {
keyboard?: KeyboardConfig;
spotlight?: SpotlightConfig;
persistence?: PersistenceConfig;
a11y?: A11yConfig;
scroll?: ScrollConfig;
dir?: 'ltr' | 'rtl' | 'auto';
}
interface KeyboardConfig {
enabled?: boolean;
nextKeys?: string[];
prevKeys?: string[];
exitKeys?: string[];
trapFocus?: boolean;
}
interface SpotlightConfig {
enabled?: boolean;
color?: string;
padding?: number;
borderRadius?: number;
animate?: boolean;
animationDuration?: number;
clickToExit?: boolean;
}
interface PersistenceConfig {
enabled?: boolean;
storage?: 'localStorage' | 'sessionStorage' | Storage;
keyPrefix?: string;
rememberStep?: boolean;
trackCompleted?: boolean;
dontShowAgain?: boolean;
}
interface A11yConfig {
announceSteps?: boolean;
ariaLive?: 'polite' | 'assertive';
focusTrap?: boolean;
restoreFocus?: boolean;
reducedMotion?: 'respect' | 'force' | 'ignore';
}
interface ScrollConfig {
enabled?: boolean;
behavior?: 'smooth' | 'auto';
block?: 'start' | 'center' | 'end' | 'nearest';
offset?: number;
}Tour Types
interface Tour {
id: string;
steps: TourStep[];
autoStart?: boolean;
startAt?: number;
keyboard?: KeyboardConfig;
spotlight?: SpotlightConfig;
persistence?: PersistenceConfig;
a11y?: A11yConfig;
scroll?: ScrollConfig;
onStart?: () => void;
onComplete?: () => void;
onSkip?: (stepIndex: number) => void;
onStepChange?: (step: TourStep, index: number) => void;
}
interface TourStep {
id: string;
target: string | React.RefObject<HTMLElement>;
title?: string;
content?: React.ReactNode;
placement?: Placement;
offset?: [number, number];
route?: string;
routeMatch?: 'exact' | 'startsWith' | 'contains';
when?: (context: TourCallbackContext) => boolean | Promise<boolean>;
advanceOn?: AdvanceOnConfig;
waitForTarget?: boolean;
waitTimeout?: number;
spotlightPadding?: number;
spotlightRadius?: number;
interactive?: boolean;
showNavigation?: boolean;
showProgress?: boolean;
showClose?: boolean;
className?: string;
onBeforeShow?: (
context: TourCallbackContext
) => boolean | undefined | Promise<boolean | undefined>;
onEnter?: (context: TourCallbackContext) => void | Promise<void>;
onShow?: (context: TourCallbackContext) => void;
onBeforeHide?: (
context: TourCallbackContext
) => boolean | undefined | Promise<boolean | undefined>;
onHide?: (context: TourCallbackContext) => void;
}
type Placement =
| 'top' | 'top-start' | 'top-end' | 'top-center'
| 'bottom' | 'bottom-start' | 'bottom-end' | 'bottom-center'
| 'left' | 'left-start' | 'left-end' | 'left-center'
| 'right' | 'right-start' | 'right-end' | 'right-center';Step lifecycle
Every app-initiated transition — next(), prev(), goTo(), start() and a
branch to another tour — runs the step hooks in this order:
onBeforeHide(outgoing) → onBeforeShow(incoming) → onEnter(incoming)
→ [the step commits] →
onHide(outgoing) → onShow(incoming)The two onBefore* hooks run before the commit and can cancel it. The two
notifications run after, and cannot.
Cancelling. Return a literal false from onBeforeShow or onBeforeHide
to veto the transition. The tour stays exactly where it is, and the call that
triggered it resolves having changed nothing. Every other return value —
including undefined, null, 0 and '' — proceeds. Both guards are
awaited, so an async guard that resolves false cancels too.
{
id: 'confirm-step',
target: '#editor',
content: 'Save before you continue',
onBeforeHide: async ({ data }) => {
if (!data.saved) {
return false // stay on this step
}
},
}A throwing callback never cancels anything. Any hook that throws is logged
and ignored, and the transition continues. This is deliberate and differs from
when, where a throw skips the step: skipping a step is always safe, while a
guard that threw on every call would trap the user with no way forward or back.
No step callback can brick a tour.
Where the exceptions are:
- Hidden steps (
kind: 'hidden') runonEnterandonShowonly. They never mount, soonBeforeShow,onBeforeHideandonHideare ignored on them. - A tour restored from a saved session runs
onEnterandonShow, but neveronBeforeShow— a veto during a cold restore would strand the user mid-tour. onHidealso fires when the tour ends on a step, throughstop(),skip(),complete()orreset(). The step did leave the screen.next()on the last step completes the tour, so it firesonHideand no incoming hooks — there is no step to show.
Waiting for a late target. Set waitForTarget: true on a step whose target
is rendered after a fetch or a lazy mount, and the step waits for it to appear
before committing. It applies on the step's own route; a step with a different
route always waits for its target after the router hop regardless. The flag
needs a target to observe and is ignored without one. waitTimeout
(default 3000) bounds both waits; on timeout onStepError fires with
TourRouteError({ code: 'TARGET_NOT_FOUND' }) and the tour stops.
{
id: 'chart',
target: '#revenue-chart',
content: 'Your revenue at a glance',
waitForTarget: true,
waitTimeout: 5000,
}Every hook receives the same TourCallbackContext the tour-level callbacks
get, with currentStep and currentStepIndex pointing at the step the hook
belongs to.
Router Types
interface RouterAdapter {
getCurrentRoute(): string;
navigate(route: string): void;
matchRoute(pattern: string, mode?: 'exact' | 'startsWith' | 'contains'): boolean;
onRouteChange(callback: (route: string) => void): () => void;
}
interface MultiPagePersistenceConfig {
enabled?: boolean;
storage?: 'localStorage' | 'sessionStorage' | 'memory' | Storage;
key?: string;
syncTabs?: boolean;
expiryMs?: number;
}Step Visibility & Targets
// Discriminated union for filtered step lists. See useStep / useTour.
type VisibleTourStep = TourStep & { __visible: true };
type HiddenTourStep = TourStep & { __visible: false };
function isVisibleStep(step: TourStep): step is VisibleTourStep;
// Target resolution types
type TourTargetRef = React.RefObject<HTMLElement | null>;
type TourTargetGetter = () => HTMLElement | null;
// Step media — image / video / lottie attached to a step
interface TourStepMedia {
type: 'image' | 'video' | 'lottie';
src: string;
alt?: string;
poster?: string;
}Target Waiting
interface WaitForStepTargetOptions {
timeoutMs?: number; // default: 5000
pollIntervalMs?: number; // default: 50
signal?: AbortSignal;
}
// Resolves when a step's target appears in the DOM, rejects on timeout/abort.
function waitForStepTarget(
step: TourStep,
options?: WaitForStepTargetOptions
): Promise<HTMLElement>;i18n Types
interface LocaleContextValue {
locale: string;
messages: Messages;
t?: TranslateFn;
direction?: 'ltr' | 'rtl';
}
interface LocaleProviderProps extends Partial<LocaleContextValue> {
children: React.ReactNode;
}
type TranslateFn = (key: string, vars?: Record<string, unknown>) => string;
// Type guard for `LocalizedText`'s key-shape branch
function isI18nKey(value: unknown): value is { key: string };See LocaleProvider, useLocale, and the i18n guide.
Segmentation & Audience Types
// Segments registered on <SegmentationProvider>
type SegmentDefinition = AudienceCondition[]; // AND-joined
interface StaticSegment {
type: 'static';
userIds: ReadonlyArray<string>;
}
type SegmentSource = SegmentDefinition | StaticSegment;
interface SegmentationContextValue {
segments: Record<string, SegmentSource>;
userContext?: Record<string, unknown>;
currentUserId?: string;
}
interface SegmentationProviderProps extends SegmentationContextValue {
children: React.ReactNode;
}
// Audience prop accepted on tours, hints, announcements
type AudienceProp = AudienceCondition[] | { segment: string };
// Type guard for the segment-shape branch
function isSegmentAudience(audience: AudienceProp): audience is { segment: string };
// Headless audience evaluator (re-exports in @tour-kit/react too)
function evaluateAudience(
audience: AudienceProp | undefined,
segments: Record<string, boolean>,
userContext: Record<string, unknown> | undefined,
source?: string
): boolean;
// Returns a human-readable explanation of why an audience did/didn't match
function explainAudience(
audience: AudienceProp,
segments: Record<string, boolean>,
userContext?: Record<string, unknown>
): string;See Segmentation guide and useSegmentationContext.
Diagnostic Types
// All built-in gates evaluated by the can-show pipeline, in declared order
const BUILTIN_GATE_ORDER: readonly GateName[];
// Stable identifiers used by diagnostics and analytics events
type GateName =
| 'audience' | 'segment' | 'frequency' | 'schedule'
| 'persistence' | 'capacity' | 'custom';
type GateCode = 'pass' | 'fail' | 'skipped' | 'error';
// Context passed to diagnostic listeners (eventbus, devtools)
interface DiagnosticContext {
source: 'tour' | 'hint' | 'announcement' | 'checklist';
id: string;
gate: GateName;
code: GateCode;
reason?: string;
}See Diagnostic guide.
Flow Session
interface UseFlowSessionConfig {
flowId: string;
storage?: 'localStorage' | 'sessionStorage' | 'memory';
ttlMs?: number;
}
interface FlowSessionConfig extends UseFlowSessionConfig {
initialState?: Record<string, unknown>;
}
interface UseFlowSessionReturn<TState = Record<string, unknown>> {
state: TState;
setState: (next: Partial<TState>) => void;
clear: () => void;
isRestored: boolean;
}Broadcast & Frequency
interface UseBroadcastReturn {
broadcast: (event: string, payload?: unknown) => void;
subscribe: (event: string, handler: (payload: unknown) => void) => () => void;
}
interface FrequencyState {
lastShownAt?: number;
shownCount: number;
dismissedAt?: number;
completedAt?: number;
}Default Configurations
import {
defaultKeyboardConfig,
defaultSpotlightConfig,
defaultPersistenceConfig,
defaultA11yConfig,
defaultScrollConfig,
} from '@tour-kit/core';For detailed documentation on each export, see the individual pages in the @tour-kit/core section.
See also
@tour-kit/corepackage overview — prose-style docs and recipes for each export above.- API reference index — browse other package references (
@tour-kit/react,@tour-kit/hints, …). - Quick start — use these primitives in a working app.
- TypeScript guide — set up strict types for the symbols documented here.
Ship onboarding, not config.
npm i @tour-kit/core is free while you build. Every package works unlicensed in development, a one-time licence from $9.99 removes the production watermark when you ship.
Free in development, no signup, no credit card. Pay once, only when you ship.