useOnboardingBuilder
The useOnboardingBuilder composable provides a fluent, type-safe builder API for constructing multi-phase onboarding tours on top of driver.js. It lets you declare named phases with onEnter/onExit lifecycle hooks and attach steps to each phase; the builder transparently wires phase transitions into driver.js navigation so that moving between steps across phases runs the correct hooks.
The resulting builder is consumed by the Onboarding component via its builder prop.
Features
- Phased Tours: Split a tour into named phases, each with its own
onEnter/onExitasync hooks. - Fluent API: Chain
addPhases→switchPhase→addStepsto describe the whole tour declaratively. - Type-Safe Phase Names: Phase names are inferred from a generic you pass to
addPhases, soswitchPhaseonly accepts valid names. - Lazy Titles & Descriptions: Step
popover.titleandpopover.descriptionaccept either a string or a function (tOrFunc<string>), evaluated at build time. - Automatic Hook Wiring: When the user navigates across a phase boundary — including the final step of the tour — the previous phase's
onExitand the next phase'sonEnterare invoked automatically. Any navigation hooks you provide on steps or in the driver config are merged with the builder's handlers, never overwritten. - driver.js Integration: Produces a configured
Driverinstance viabuildDriver, including i18n labels and Lucide footer-button icons.
Return Shape
useOnboardingBuilder(config?) returns:
| Method | Description |
|---|---|
addPhases | Registers the available phases and returns an OnboardingStepBuilder<Phases>. |
The returned builder (OnboardingStepBuilder<Phases>) exposes:
| Member | Type | Description |
|---|---|---|
addSteps | (steps: OnboardingStep[]) => OnboardingStepBuilder<Phases> | Appends steps to the currently active phase. Throws if the array is empty. |
switchPhase | (phase: Phases) => OnboardingStepBuilder<Phases> | Switches the active phase. Throws if the phase is unknown or already active. |
currentPhase | OnboardingPhase<Phases> | undefined | The phase that is currently active (read-only). |
buildDriver | (config?: Config) => Driver | Builds a driver.js Driver instance from the accumulated steps and merges any extra config. |
Types
OnboardingPhase<Phases>
type OnboardingPhase<Phases> = {
name: Phases;
onEnter?: () => Promise<void>;
onExit?: () => Promise<void>;
};OnboardingStep
A step is a driver.js DriveStep with the popover type extended so that title and description accept either a string or a function (tOrFunc<string>). All other DriveStep and Popover properties (including element, onNextClick, onPrevClick, etc.) are preserved:
type StepPopoverOverride = Omit<Popover, 'title' | 'description'> & {
title?: tOrFunc<string>;
description?: tOrFunc<string>;
};
type OnboardingStep = Omit<DriveStep, 'popover'> & {
popover?: StepPopoverOverride;
};
type tOrFunc<T> = T | (() => T);Usage
Basic Phased Tour
<script lang="ts" setup>
import Onboarding from '@dcc-bs/common-ui.bs.js/components/Onboarding.vue';
const onboarding = ref<InstanceType<typeof Onboarding>>();
const builder = useOnboardingBuilder()
.addPhases<'Phase1' | 'Phase2'>([
{
name: 'Phase1',
onEnter: async () => {
console.log('enter phase 1');
},
onExit: async () => {
console.log('exit phase 1');
},
},
{
name: 'Phase2',
onEnter: async () => {
console.log('enter phase 2');
},
onExit: async () => {
console.log('exit phase 2');
},
},
])
.switchPhase('Phase1')
.addSteps([
{
popover: {
title: 'Step 1',
description: 'This is step 1',
},
},
{
popover: {
title: 'Step 2',
description: 'This is step 2',
},
},
])
.switchPhase('Phase2')
.addSteps([
{
popover: {
title: 'Step 3',
description: 'This is step 3',
},
},
]);
onMounted(() => {
onboarding.value?.start();
});
</script>
<template>
<Onboarding ref="onboarding" :builder="builder" />
</template>Lazy Title and Description
Step text can be supplied as a function so it is evaluated when the driver is built (for example, to read fresh reactive state):
const builder = useOnboardingBuilder()
.addPhases<'intro'>([{ name: 'intro' }])
.switchPhase('intro')
.addSteps([
{
popover: {
title: () => `Welcome, ${userName.value}`,
description: () => t('tour.intro.description'),
},
},
]);Passing Additional driver.js Config
useOnboardingBuilder accepts an optional driver.js Config that is merged into every driver it builds. Use it for global hooks or overrides:
const builder = useOnboardingBuilder({
allowClose: false,
onDeselected: () => {
console.log('user clicked away');
},
}).addPhases<'tour'>([{ name: 'tour' }])
.switchPhase('tour')
.addSteps([/* ... */]);TIP
Hooks you provide at the driver-config level (such as onDeselected, onHighlightStarted) are merged with the builder's own hooks via extendDriverHook, so they run before the builder's phase-transition logic. Similarly, step-level onNextClick/onPrevClick hooks are preserved and executed before the builder's navigation handlers.
How Phase Transitions Work
The builder is a small state machine (Initial → PhaseSwitched → StepsAdded). The state determines how hooks are attached when you call addSteps and switchPhase:
- Initial
switchPhasebefore any steps — the phase'sonEnteris hooked into driver.js'onHighlightStartedcallback and runs only on the very first step of the tour. switchPhaseafter steps exist — theonNextClickhandler of the last step in the outgoing phase is wired to:await currentPhase.onExit?.()await newPhase.onEnter?.()driver.moveNext()
addStepsimmediately after aswitchPhase— theonPrevClickhandler of the first new step is wired to navigate back into the previous phase:await currentPhase.onExit?.()await oldPhase.onEnter?.()driver.movePrevious()
- Last step of the tour — the
onNextClickhandler of the very last step is wired to invoke the current phase'sonExitbefore advancing, ensuring the final phase's teardown always runs.
TIP
The builder merges user-provided hooks with its own navigation handlers via extendDriverHook, so any onNextClick/onPrevClick/onHighlightStarted hooks you define on a step or in the driver config are preserved and run before the builder's phase-transition logic. You can safely combine custom step-level hooks with the fluent builder API.
Errors
The builder throws synchronously when used incorrectly:
| Condition | Message |
|---|---|
addSteps called with an empty array | steps cannot be empty |
switchPhase called with an unknown name | Phase "<name>" not found |
switchPhase called with the already-active phase | Phase "<name>" is already the current phase |
Consuming the Builder
The builder is designed to be handed to the Onboarding component, which calls buildDriver, persists completion in a cookie, and manages the driver lifecycle:
<template>
<Onboarding ref="onboarding" :builder="builder" />
</template>For the full component behavior (cookie persistence, auto-start after the disclaimer modal, exposed start/destroy methods), see the Onboarding component page.
i18n
The driver instance built by this composable is preconfigured with translation keys under common-ui.tour.*. See the Internationalization section for the available keys (skip, next, prev, finish, progress).
