Skip to main content
userTourKit
Getting Started

TypeScript

Configure TypeScript strict mode, use inferred step types, and leverage generic tour configurations in userTourKit

domidex01Published

userTourKit is written in TypeScript and provides full type definitions.

Type Imports

import type {
  TourConfig,
  TourStepConfig,
  TourState,
  Placement,
  SpotlightConfig,
} from '@tour-kit/react';

Typed Tour Configuration

import { createTour, createStep } from '@tour-kit/react';

const tour = createTour({
  id: 'onboarding',
  steps: [
    createStep({
      id: 'welcome',
      target: '#hero',
      title: 'Welcome',
      content: 'Hello!',
      placement: 'bottom',
    }),
  ],
});

Hook Return Types

import { useTour, type UseTourReturn } from '@tour-kit/react';

function MyComponent() {
  const tour: UseTourReturn = useTour('my-tour');
  // tour.start, tour.next, tour.prev, etc.
}

Typed Step IDs

userTourKit ships generic TourStep<TId> and Tour<TStep> so that const-authored tours catch misspelled step ids at compile time. The default is TId extends string = string, so dynamic tours (server-fetched, JIT-built) keep working without any changes.

Author your steps as a const tuple with satisfies and feed them to StepIdOf to derive the literal union:

import { useTour } from '@tour-kit/react';
import type { StepIdOf, TourStep } from '@tour-kit/react';

const steps = [
  { id: 'welcome', target: '#hero', content: 'Welcome' },
  { id: 'pricing', target: '#pricing', content: 'Pricing' },
] as const satisfies ReadonlyArray<TourStep>;

type StepId = StepIdOf<typeof steps>;
// StepId = 'welcome' | 'pricing'

function MyComponent() {
  const tour = useTour<(typeof steps)[number]>();

  // ✅ Compiles
  tour.goToStep('welcome');

  // ❌ Type error: Argument of type '"biling"' is not assignable...
  tour.goToStep('biling');
}

The same narrowing applies to startTour(tourId, stepId?):

tour.startTour('onboarding', 'welcome'); // ✅
tour.startTour('onboarding', 0);         // ✅ numeric index escape hatch
tour.startTour('onboarding', 'biling');  // ❌ unknown step id

Dynamic tours (server-fetched JSON)

When step ids aren't known at compile time, omit the generic — the default TourStep widens id back to string:

import type { Tour, TourStep } from '@tour-kit/react';

// Server returns arbitrary tour JSON
const dynamicSteps: TourStep[] = await fetch('/api/tour').then((r) => r.json());

const dynamic: Tour = { id: 'dynamic', steps: dynamicSteps };
// dynamic.steps[0].id is `string` — no narrowing applied

If you want the widening to be explicit in your type signatures, pass TourStep<string> directly:

const widened: Tour<TourStep<string>> = dynamic;

Narrowed callbacks

Tour.onStepChange receives a step typed as TStep, not the base TourStep. Pass a literal-union step type to narrow step.id inside the callback:

const tour: Tour<TourStep<'a' | 'b'>> = {
  id: 'demo',
  steps: [/* ... */],
  onStepChange: (step) => {
    // step.id is 'a' | 'b'
    if (step.id === 'a') {/* ... */}
  },
};

See issue #34 for the design discussion and the test fixtures under packages/core/src/__tests__/types/ for the canonical patterns.

Generic Step Data

You can type custom data attached to steps:

interface MyStepData {
  videoUrl?: string;
  requiredRole?: 'admin' | 'user';
}

const { currentStep } = useTour<MyStepData>('my-tour');

if (currentStep?.data?.videoUrl) {
  // TypeScript knows videoUrl is a string
}

Strict Mode Compatibility

userTourKit is fully compatible with React Strict Mode and follows all TypeScript strict mode guidelines:

tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true
  }
}