useWalkthrough is the state machine behind NbWalkthrough, with no rendering attached. It owns the step index, the version-gated auto-start rule, and persistence.
Reach for it directly when the tour has to be started from somewhere that has no template of its own (a help menu handler, a router guard, an onboarding checklist), or when you want to unit-test the flow without mounting an overlay.
Basic usage
<template>
<NbWalkthrough :controller="tour" />
</template>
<script setup lang="ts">
import { onMounted } from 'vue'
import { useWalkthrough } from '@nubisco/ui'
import { introTour } from '@/onboarding/intro.tour'
const tour = useWalkthrough(introTour)
// Runs only if nothing is stored for the current version.
onMounted(() => tour.maybeAutoStart())
</script>Version-gated auto-start
maybeAutoStart() is the whole first-run policy in one call. It returns true when it started the tour.
const tour = useWalkthrough(introTour) // { id: 'app-intro', version: 1, … }
await tour.maybeAutoStart() // true — first visit
await tour.finish()
await tour.maybeAutoStart() // false — already completed version 1Bumping version to 2 in the tour definition makes maybeAutoStart() return true again for everyone who only completed version 1. A user who already saw version 3 is never dragged back to version 2: the check is "stored version is at least the current one", not equality.
Reacting to the run
Pass callbacks in the options. Unlike the component's events, these fire however the run ends, including from imperative calls.
const tour = useWalkthrough(introTour, {
onStepChange: (step, index) =>
analytics.track('tour_step', { index, id: step.id }),
onFinish: (walkthrough) =>
analytics.track('tour_finished', { id: walkthrough.id }),
onSkip: (walkthrough, index) =>
analytics.track('tour_skipped', { at: index }),
})A reactive tour definition
The source can be a plain object, a ref, or a getter, so the tour can change with the locale or with a feature flag.
const tour = useWalkthrough(() => ({
id: 'app-intro',
version: 2,
steps: t('onboarding.steps'),
}))Custom persistence
See pluggable persistence on the component page. Any object satisfying IWalkthroughStorage works, and reads may return a promise.
import { createMemoryWalkthroughStorage, useWalkthrough } from '@nubisco/ui'
// In tests: no localStorage, no cross-test bleed.
const tour = useWalkthrough(introTour, {
storage: createMemoryWalkthroughStorage(),
})Testing a tour
Because the composable has no DOM dependencies, the flow is testable on its own.
import { describe, expect, it } from 'vitest'
import { createMemoryWalkthroughStorage, useWalkthrough } from '@nubisco/ui'
import { introTour } from '@/onboarding/intro.tour'
it('remembers a completed run', async () => {
const storage = createMemoryWalkthroughStorage()
const first = useWalkthrough(introTour, { storage })
expect(await first.maybeAutoStart()).toBe(true)
await first.finish()
const second = useWalkthrough(introTour, { storage })
expect(await second.maybeAutoStart()).toBe(false)
})