Skip to main content
userTourKit
API Reference

@tour-kit/core API

API reference for @tour-kit/core: useTour, useStep, useFocusTrap, createTour, createStep, and all utility exports

domidex01Published

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>
PropTypeDescription
configTourKitConfigGlobal configuration for all tours
dir'ltr' | 'rtl' | 'auto'Text direction (auto detects from document)
onTourStart(tourId: string) => voidCalled when any tour starts
onTourComplete(tourId: string) => voidCalled when any tour completes
onTourSkip(tourId: string, stepIndex: number) => voidCalled when any tour is skipped
onStepView(tourId: string, stepId: string, stepIndex: number) => voidCalled 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>
PropTypeDescription
toursTour[]Array of tour definitions
routerRouterAdapterRouter adapter for multi-page tours
onStart(tourId: string) => voidCalled when tour starts
onComplete(tourId: string) => voidCalled when tour completes
onSkip(tourId: string, stepIndex: number) => voidCalled when tour is skipped
onStepChange(step: TourStep, index: number) => voidCalled 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?);
ReturnTypeDescription
isActivebooleanWhether a tour is currently active
currentStepTourStep | nullCurrent step data
currentStepIndexnumber0-based index of current step
totalStepsnumberTotal number of steps
isFirstStepbooleanWhether on first step
isLastStepbooleanWhether on last step
progressnumberProgress percentage (0-100)
isLoadingbooleanWhether tour is loading
isTransitioningbooleanWhether transitioning between steps
start(tourIdOrIndex?, stepIndex?) => voidStart a tour
next() => voidGo to next step
prev() => voidGo to previous step
goTo(stepIndex: number) => voidGo to specific step
skip() => voidSkip/exit the tour
complete() => voidMark tour as complete
stop() => voidStop without completion
isStepActive(stepId: string) => booleanCheck if step is active
getStep(stepId: string) => TourStep | undefinedGet 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') run onEnter and onShow only. They never mount, so onBeforeShow, onBeforeHide and onHide are ignored on them.
  • A tour restored from a saved session runs onEnter and onShow, but never onBeforeShow — a veto during a cold restore would strand the user mid-tour.
  • onHide also fires when the tour ends on a step, through stop(), skip(), complete() or reset(). The step did leave the screen.
  • next() on the last step completes the tour, so it fires onHide and 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

Free & open source

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.