TypeScript
Configure TypeScript strict mode, use inferred step types, and leverage generic tour configurations in userTourKit
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.
Const-tuple narrowing (recommended)
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 idDynamic 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 appliedIf 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:
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true
}
}