Skip to content

Onboarding ​

The Onboarding component provides an interactive guided tour for new users, powered by driver.js. It is a renderless component — it renders nothing in the DOM but drives an overlay that highlights elements and walks users through your application's features.

Tours are configured declaratively with the useOnboardingBuilder composable, which supports phased step groups with onEnter/onExit lifecycle hooks. The component auto-starts on mount and emits a finished event when the user closes or completes the tour. In typical usage, the component is mounted by the FirstRunOrchestrator, which sequences it after the Disclaimer and Changelogs flows and persists completion via the tour-completed cookie.

Features ​

  • Renderless: Owns no DOM beyond a hidden sentinel; all UI is driven by driver.js
  • Phased Tours: Group steps into named phases with enter/exit callbacks
  • Event-Based Completion: Emits a finished event when the user closes or completes the tour, allowing the parent (e.g., FirstRunOrchestrator) to record completion
  • Locale-Aware: Recreates the driver when the active locale changes so labels stay translated
  • Auto-Start: Starts the tour automatically on mount
  • Programmatic Control: Exposes start() and destroy() methods via template ref
  • Localized Labels: Buttons and progress text are translated via i18n
  • Custom Icons: Lucide icons are injected into the navigation buttons
  • Test-Friendly: The skip button is stamped with data-testid="tour-skip" for E2E selectors

Props ​

PropTypeRequiredDescription
builderOnboardingStepBuilder<Phases>YesThe step builder created via useOnboardingBuilder().addPhases(...)

Events ​

EventPayloadDescription
finished{ completed: boolean }Emitted when the user closes or completes the tour (fires from the driver.js onDestroyStarted hook). The parent component should use this to record completion (e.g., set the tour-completed cookie).

Exposed Methods ​

The component exposes the following methods via a template ref:

MethodSignatureDescription
start() => voidDestroys any active driver, rebuilds it from the builder, and starts the tour.
destroy() => voidTears down the active driver. Does not emit finished — useful for re-running the tour during development.

Persistence ​

Persistence is managed by the parent component — typically the FirstRunOrchestrator — which listens for the finished event and writes the tour-completed cookie. This means calling start() in your own code (e.g., a "Replay tour" button) will not permanently mark the tour as completed; only the orchestrator does that.

Because the cookie is SSR-readable, first-time visitors skip the tour on the server render and avoid a hydration flash.

Usage ​

The recommended way to use the Onboarding component is through the FirstRunOrchestrator, which is placed once in app.vue or your default layout. The builder is app-owned (each app defines its own tour); the orchestrator owns when to mount it.

vue
<script lang="ts" setup>
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",
            },
        },
    ]);
</script>

<template>
    <UApp>
        <FirstRunOrchestrator
            :onboarding-builder="builder"
            :disclaimer="{
                appName: 'Test App',
                confirmationText:
                    'I have read and understood the instructions and confirm that I will use Test App exclusively in compliance with the stated guidelines.',
            }"
        />
        <NuxtPage />
    </UApp>
</template>

TIP

The FirstRunOrchestrator receives the builder via its onboarding-builder prop and manages the tour-completed cookie. If the builder is omitted, the onboarding flow is skipped entirely.

Replay Tour on Demand ​

The OnboardingRestartButton component provides a pre-built button that resets the tour-completed cookie and re-triggers the tour via the orchestrator. It is included by default in the NavigationBar.

vue
<template>
    <OnboardingRestartButton />
</template>

Alternatively, if you are mounting the Onboarding component directly, you can wire a custom button to the start() method via a template ref:

vue
<template>
    <Onboarding ref="onboarding" :builder="builder" />
    <UButton @click="onboarding?.start()">Replay Tour</UButton>
</template>

Targeting Elements ​

Each step accepts an element selector (any value driver.js understands — a CSS selector string, an HTMLElement, or a function returning one). When omitted, the step renders as a centered popover without highlighting an element, which is useful for introductory or summary screens.

ts
.addSteps([
    {
        popover: {
            title: "Welcome",
            description: "Let's take a quick tour of the app.",
        },
    },
    {
        element: "#sidebar",
        popover: { title: "Sidebar", description: "Navigate between sections here." },
    },
])

Lazy Titles and Descriptions ​

Step title and description may be provided either as a string or as a function that returns a string (tOrFunc<string>). Functions are evaluated when the driver is built, which lets you resolve translations or computed values lazily.

In addition to lazy title and description, the step popover accepts all other driver.js Popover properties (such as onNextClick, onPrevClick, positioning options, etc.). Any navigation hooks you provide are merged with the builder's phase-transition hooks rather than overridden — see Optional Driver Config below.

ts
.addSteps([
    {
        element: "#dashboard",
        popover: {
            title: () => t("tour.dashboard.title"),
            description: () => t("tour.dashboard.description"),
        },
    },
])

The useOnboardingBuilder Composable ​

The composable is the only supported way to construct a builder for the Onboarding component. It produces a fluent, type-safe builder with the following API:

ts
const builder = useOnboardingBuilder(config?)
    .addPhases<Phases>(phases)   // returns OnboardingStepBuilder<Phases>
    .switchPhase(phase)          // returns OnboardingStepBuilder<Phases>
    .addSteps(steps)             // returns OnboardingStepBuilder<Phases>
    .buildDriver(config?);       // returns driver.js Driver
MemberSignatureDescription
addPhases<Phases>(phases: OnboardingPhase<Phases>[]) => BuilderRegisters all phases and returns a fresh builder typed by the phase union.
switchPhase(phase: Phases) => BuilderSelects the active phase. Throws if the phase is unknown or already active.
addSteps(steps: OnboardingStep[]) => BuilderAppends steps to the active phase. Throws if the array is empty.
currentPhaseOnboardingPhase<Phases> | undefinedThe phase that subsequent addSteps calls belong to.
buildDriver(config?: Config) => DriverBuilds a driver.js instance from the accumulated steps and config.

Phase Lifecycle Hooks ​

Each phase accepts optional onEnter and onExit async callbacks. The builder wires them transparently into the driver.js popover navigation buttons so that:

  • onExit of the current phase + onEnter of the next phase fire when the user advances from the last step of one phase into the next.
  • onExit of the current phase + onEnter of the previous phase fire when the user navigates backward from the first step of a phase into the previous one.
  • onEnter of the initial phase fires once, when the first step is highlighted.
  • onExit of the final phase fires when the user completes the tour from the last step.
ts
type OnboardingPhase<Phases> = {
    name: Phases;
    onEnter?: () => Promise<void>;
    onExit?: () => Promise<void>;
};

TIP

Hooks are awaited before the driver advances, so you can perform async setup — for example, navigating to a route, fetching data, or waiting for an element to mount — before the next popover appears.

Optional Driver Config ​

You can pass a driver.js Config object either to useOnboardingBuilder(config) (applied to the underlying driver) or to buildDriver(config). Per-call config is merged with and takes precedence over the composable-level config.

ts
const builder = useOnboardingBuilder({
    allowClose: false,
    smoothScroll: true,
});

TIP

The builder reserves several configuration slots for itself: button text, progress text, the onPopoverRender hook (used to inject Lucide icons), and certain onNextClick/onPrevClick/onHighlightStarted hooks (used to drive phase transitions). Custom hooks you provide via the config or on individual step popovers are merged, not overridden — your hook runs first, then the builder's internal phase-transition hook executes.

Auto-Start Behavior ​

On mount, the component immediately builds a driver.js instance from the builder and calls drive(). When managed by the FirstRunOrchestrator, the component is only mounted after the Disclaimer and Changelogs flows are complete, so there is no need for the component to observe the DOM for blocking modals.

TIP

If you mount the Onboarding component directly (without the orchestrator), ensure no blocking modals are open at mount time — driver.js sets pointer-events: none on all descendants except the highlighted element.

Locale Awareness ​

The component watches the active i18n locale. When the locale changes, the active driver is destroyed and rebuilt with translated button labels, progress text, and step content. If the tour was in progress, you may want to call start() again after a locale switch to re-display the popover:

ts
const { locale } = useI18n();
watch(locale, () => {
    nextTick(() => onboarding.value?.start());
});

Runtime Configuration ​

The onboarding flow can be disabled globally via the disableOnboarding runtime config option:

typescript
export default defineNuxtConfig({
  runtimeConfig: {
    public: {
      commonUi: {
        disableOnboarding: true,
      },
    },
  },
});

When set to true, the FirstRunOrchestrator will never mount the Onboarding component.

TIP

You can also set this via the environment variable NUXT_PUBLIC_COMMON_UI_DISABLE_ONBOARDING=true.

i18n Configuration ​

Button labels and progress text are read from the common-ui.tour namespace. Defaults are provided for en and de; override them in your application's i18n files to add languages or change wording.

json
{
    "common-ui": {
        "tour": {
            "skip": "Skip",
            "next": "Next",
            "prev": "Back",
            "finish": "Finish",
            "restart": "Restart tour",
            "progress": "Step \\{\\{current\\}\\} of \\{\\{total\\}\\}"
        }
    }
}
KeyPurpose
common-ui.tour.skipTooltip/aria label for the close button
common-ui.tour.nextLabel for the "Next" navigation button
common-ui.tour.prevLabel for the "Back" navigation button
common-ui.tour.finishLabel for the "Next" button on the final step
common-ui.tour.restartLabel for the OnboardingRestartButton component
common-ui.tour.progressProgress text template {{current}} and {{total}} are interpolated by driver.js

Styling ​

The driver.js popover is themed via the tm-tour-popover class:

  • Max width capped at 450px
  • Navigation buttons use --ui-primary background and white text
  • The finish (done) button uses --ui-success

Because driver.js renders the popover outside this component's DOM, these styles are applied globally. To customize the look further, target the .driver-popover.tm-tour-popover selector in your application's CSS.

How It Works ​

  1. Build: You construct a builder with useOnboardingBuilder() and pass it to the FirstRunOrchestrator via the onboarding-builder prop.
  2. Sequencing: The orchestrator checks the tour-completed cookie and the pending state of higher-priority flows (Disclaimer, Changelogs). If the cookie is true or a higher-priority flow is pending/loading, the Onboarding component is not mounted.
  3. Start: When onboarding becomes the active flow, the orchestrator mounts the Onboarding component, which immediately builds a driver.js instance and calls drive().
  4. Navigation: As the user moves between steps, the builder-injected onNextClick/onPrevClick hooks fire the appropriate phase onEnter/onExit callbacks before advancing. Any custom hooks you supplied on individual steps or via the driver config are merged and run alongside the builder's hooks. When the user completes the tour from the final step, the last active phase's onExit callback is also invoked.
  5. Completion: When the user closes or finishes the tour, onDestroyStarted emits the finished event. The orchestrator receives it and sets tour-completed to true.
  6. Cleanup: On unmount, any active driver is destroyed.
NameTypeDefaultPurpose
tour-completedbooleanfalseSet to true when the user completes or skips the tour. Written by the FirstRunOrchestrator. SSR-readable.

Developed with ❤️ by the DCC. Documentation released under the MIT License.