Skip to main content

Public API

Specify the small consumer-facing contract for tours and targets.

Public API

This page defines the implemented version 1 contract.

Tour definition

ts
export default defineTour({
  id: 'getting-started',
  steps: [
    {
      id: 'welcome',
      ariaLabel: 'Welcome',
      content: 'Welcome to the application.',
    },
    {
      id: 'projects',
      route: { name: 'projects' },
      target: 'new-project',
      title: 'Create a project',
      content: 'Start your first project here.',
      placement: 'bottom-start',
      prepare: async ({ signal }) => {
        const panel = useProjectPanel()
        panel.open()
        await nextTick()

        if (signal.aborted) return
        return () => panel.close()
      },
    },
  ],
})

defineTour() preserves literal tour and step IDs. Every definition has at least one step. Tour IDs and step IDs are unique.

Step contract

ts
interface TourStep {
  readonly id: string
  readonly target?: TourTarget
  readonly scrollTarget?: TourTarget
  readonly route?: TourRoute
  readonly title?: string
  readonly ariaLabel?: string
  readonly content: string | Component
  readonly placement?: TourPlacement
  readonly offset?: number
  readonly scroll?: false | Readonly<ScrollIntoViewOptions>
  readonly interaction?: 'modal' | 'target' | 'page'
  readonly when?: (context: TourStepContext) => boolean | Promise<boolean>
  readonly prepare?: (
    context: TourStepContext,
  ) => void | (() => void) | Promise<void | (() => void)>
}

An omitted target means an intentionally centered step. A requested target that does not exist never falls back to the center.

Targeted steps scroll the target to the vertical center by default and preserve its nearest horizontal position. Use scroll: false to keep the current viewport, or pass ScrollIntoViewOptions when a product needs different behavior. Use scrollTarget to center a stable outer section while spotlighting a more precise target inside it. This keeps nearby steps steady and still restores that section when a user moves back from a distant step.

interaction: 'target' requires a target. The other modes also work for centered steps.

Named-route params and every route's query fields accept serializable strings, finite numbers, null, and arrays of those values. Runtime objects and functions do not belong in definitions; perform that work in prepare() instead.

A visible title names the dialog. A step without a title requires an ariaLabel. Runtime validation rejects empty names.

Target contract

ts
type TourTarget =
  | string
  | {
      id: string
      timeout?: number
      missing?: 'skip' | 'error'
    }
  | {
      selector: string
      timeout?: number
      missing?: 'skip' | 'error'
    }

A string is a semantic target ID. The engine resolves it from the current Vue application's target registry.

vue
<button v-tour-target="'new-project'">New project</button>

Dynamic Vue elements register the same semantic ID:

ts
const trigger = useTemplateRef<HTMLElement>('trigger')
useTourTarget('new-project', trigger)

Direct refs do not belong in TourStep.target. The registry connects local Vue state to stable tour definitions. Selector objects are an explicit escape hatch for third-party or legacy markup.

Controller

ts
const tour = useNuxtTour('getting-started')

tour.isActive
tour.currentStep
tour.currentStepId
tour.index
tour.total
tour.pending

await tour.start()
await tour.start({ at: 'projects', replace: true })
await tour.next()
await tour.previous()
await tour.goTo('projects')
await tour.skip()
await tour.cancel('application-state-changed')

const stop = tour.on('step:show', event => {
  console.log(event.stepId)
})

useNuxtTour() is the Nuxt control plane. Plain Vue uses useTour(definition). Components do not expose a second controller through template refs. Navigation uses stable step IDs, not numeric indexes.

index is zero-based and refers to the definition order. total includes conditional steps, including steps skipped at runtime. This avoids evaluating all conditions early only to calculate progress.

next() and previous() skip unavailable conditional steps. goTo(id) and start({ at: id }) are exact and reject with STEP_UNAVAILABLE when their named destination is not currently eligible.

Controller state fields are readonly Vue refs. Templates unwrap them; scripts read .value.

Custom card

TourHost always owns the dialog wrapper and accessibility lifecycle. The #card slot receives step, controller, index, total, titleId, descriptionId, and pending. Put the visible title and description on those IDs when you render custom card content.

Exports

The package root exports the Nuxt module, ModuleOptions, TourError, and its error types. The /vue entry exports:

defineTour
useTour
useTourTarget
TourHost
TourContent
createTourPlugin
TourError
TourErrorCode
public TypeScript types

The CSS entries are @lupinum/nuxt-tour/style.css and @lupinum/nuxt-tour/structure.css.

The default theme follows prefers-color-scheme. Class-based applications can set .light or .dark; attribute-based applications can set data-theme="light" or data-theme="dark". Application CSS can override the low-specificity --tour-* custom properties without !important.

The stable theme variables are --tour-card-background, --tour-card-color, --tour-card-radius, --tour-card-shadow, --tour-overlay-color, --tour-spotlight-padding, --tour-spotlight-radius, --tour-accent, --tour-accent-hover, --tour-accent-contrast, --tour-muted, --tour-border, --tour-hover, and --tour-focus.

In a plain Vue application, pass a definition to useTour() for complete tour and step-ID inference. Nuxt generates application-specific declarations for the strict useNuxtTour() composable. An unknown Nuxt tour or step ID fails during type checking. Semantic IDs used by useTourTarget() are inferred from those same definitions.