v2.8.0

useTour New

A composable to build guided tours by re-anchoring a single Popover across steps.

Usage

Use the auto-imported useTour composable to drive a guided tour with a single Popover whose anchor moves between steps. The composable owns the step state and resolves each step's target into a reference you bind to <B24Popover>, while you keep full control over the content and navigation.

Dashboard

Overview of your workspace.

Profile

Manage your account.

Settings

Configure your preferences.

<script setup lang="ts">
import MagicWandIcon from '@bitrix24/b24icons-vue/outline/MagicWandIcon'

const dashboard = useTemplateRef('dashboard')
const profile = useTemplateRef('profile')
const settings = useTemplateRef('settings')

const tour = useTour([
  {
    target: () => dashboard.value,
    title: 'Welcome aboard',
    body: 'The popover re-anchors to each target as you step through the tour.'
  },
  {
    target: () => profile.value,
    title: 'Make it yours',
    body: 'You own the content and the buttons — no extra theme or locale to maintain.',
    side: 'right' as const
  },
  {
    target: () => settings.value,
    title: 'You\'re all set',
    body: 'Press Finish to close the tour.'
  }
])
</script>

<template>
  <div class="w-full space-y-4">
    <div class="flex justify-end">
      <B24Button :icon="MagicWandIcon" @click="tour.start()">
        Start tour
      </B24Button>
    </div>

    <div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
      <div ref="dashboard" class="p-4 rounded-lg border border-default bg-elevated/50">
        <p class="font-medium text-base">
          Dashboard
        </p>
        <p class="text-sm text-muted">
          Overview of your workspace.
        </p>
      </div>
      <div ref="profile" class="p-4 rounded-lg border border-default bg-elevated/50">
        <p class="font-medium text-base">
          Profile
        </p>
        <p class="text-sm text-muted">
          Manage your account.
        </p>
      </div>
      <div ref="settings" class="p-4 rounded-lg border border-default bg-elevated/50">
        <p class="font-medium text-base">
          Settings
        </p>
        <p class="text-sm text-muted">
          Configure your preferences.
        </p>
      </div>
    </div>

    <B24Popover
      :open="tour.open.value"
      :reference="tour.reference.value"
      :content="{ side: tour.current.value?.side, sideOffset: 8 }"
      :dismissible="false"
      arrow
    >
      <template #content>
        <div class="p-4 max-w-xs space-y-2">
          <div class="flex items-center justify-between gap-4">
            <p class="font-semibold text-base">
              {{ tour.current.value?.title }}
            </p>
            <span class="text-xs text-muted tabular-nums">
              {{ tour.index.value + 1 }} / {{ tour.total.value }}
            </span>
          </div>
          <p class="text-sm text-muted">
            {{ tour.current.value?.body }}
          </p>
          <div class="flex items-center justify-between pt-2">
            <B24Button
              color="air-tertiary"
              size="sm"
              :disabled="!tour.hasPrev.value"
              @click="tour.prev()"
            >
              Back
            </B24Button>
            <B24Button size="sm" @click="tour.next()">
              {{ tour.hasNext.value ? 'Next' : 'Finish' }}
            </B24Button>
          </div>
        </div>
      </template>
    </B24Popover>
  </div>
</template>
<script setup lang="ts">
import { useTemplateRef } from 'vue'
import MagicWandIcon from '@bitrix24/b24icons-vue/outline/MagicWandIcon'

const dashboard = useTemplateRef('dashboard')
const profile = useTemplateRef('profile')
const settings = useTemplateRef('settings')

const tour = useTour([
  {
    target: () => dashboard.value,
    title: 'Welcome aboard',
    body: 'The popover re-anchors to each target as you step through the tour.'
  },
  {
    target: () => profile.value,
    title: 'Make it yours',
    body: 'You own the content and the buttons — no extra theme or locale to maintain.',
    side: 'right' as const
  },
  {
    target: () => settings.value,
    title: 'You\'re all set',
    body: 'Press Finish to close the tour.'
  }
])
</script>

<template>
  <div class="w-full space-y-4">
    <div class="flex justify-end">
      <B24Button :icon="MagicWandIcon" @click="tour.start()">
        Start tour
      </B24Button>
    </div>

    <div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
      <div ref="dashboard" class="p-4 rounded-lg border border-default bg-elevated/50">
        <p class="font-medium text-base">
          Dashboard
        </p>
        <p class="text-sm text-muted">
          Overview of your workspace.
        </p>
      </div>
      <div ref="profile" class="p-4 rounded-lg border border-default bg-elevated/50">
        <p class="font-medium text-base">
          Profile
        </p>
        <p class="text-sm text-muted">
          Manage your account.
        </p>
      </div>
      <div ref="settings" class="p-4 rounded-lg border border-default bg-elevated/50">
        <p class="font-medium text-base">
          Settings
        </p>
        <p class="text-sm text-muted">
          Configure your preferences.
        </p>
      </div>
    </div>

    <B24Popover
      :open="tour.open.value"
      :reference="tour.reference.value"
      :content="{ side: tour.current.value?.side, sideOffset: 8 }"
      :dismissible="false"
      arrow
    >
      <template #content>
        <div class="p-4 max-w-xs space-y-2">
          <div class="flex items-center justify-between gap-4">
            <p class="font-semibold text-base">
              {{ tour.current.value?.title }}
            </p>
            <span class="text-xs text-muted tabular-nums">
              {{ tour.index.value + 1 }} / {{ tour.total.value }}
            </span>
          </div>
          <p class="text-sm text-muted">
            {{ tour.current.value?.body }}
          </p>
          <div class="flex items-center justify-between pt-2">
            <B24Button
              color="air-tertiary"
              size="sm"
              :disabled="!tour.hasPrev.value"
              @click="tour.prev()"
            >
              Back
            </B24Button>
            <B24Button size="sm" @click="tour.next()">
              {{ tour.hasNext.value ? 'Next' : 'Finish' }}
            </B24Button>
          </div>
        </div>
      </template>
    </B24Popover>
  </div>
</template>

Each step requires a target that the popover anchors to. It accepts a CSS selector, an element, a virtual element (anything with getBoundingClientRect), or a ref/getter returning one of those. Pass null to anchor the step to the center of the viewport. Any other field on a step (title, body, side, …) is passed through untouched and available via current.

<script setup lang="ts">
const card = useTemplateRef('card')

const tour = useTour([
  { target: '#cta', title: 'Get started' },
  { target: () => card.value, title: 'Profile', side: 'right' },
  { target: null, title: 'All set' }
])
</script>

<template>
  <B24Button @click="tour.start()">Start tour</B24Button>

  <B24Popover :open="tour.open.value" :reference="tour.reference.value" :dismissible="false">
    <template #content>
      <!-- your content + buttons -->
      <B24Button :disabled="!tour.hasPrev.value" @click="tour.prev()">Back</B24Button>
      <B24Button @click="tour.next()">{{ tour.hasNext.value ? 'Next' : 'Finish' }}</B24Button>
    </template>
  </B24Popover>
</template>
  • Built on the Popover's reactive reference prop, so the popover smoothly repositions when the active step changes.
  • The active target is scrolled into view automatically when a step becomes active.
  • Since you render the content yourself, there is no extra theme or locale to maintain.

API

useTour(steps, options?)

Parameters

steps
MaybeRefOrGetter<TourStep[]> required
The list of tour steps. Can be a static array, a ref, or a getter for reactive steps.
options
UseTourOptions
Configuration options for the tour.

Return

open
Ref<boolean>
Whether the tour is currently open.
index
Ref<number>
The current step index, clamped to the steps range.
current
ComputedRef<TourStep | undefined>
The current step object, or undefined when there are no steps.
reference
ComputedRef<ReferenceElement | undefined>
The resolved anchor for the current step, to pass to <B24Popover :reference>.
total
ComputedRef<number>
The total number of steps.
hasNext
ComputedRef<boolean>
Whether a next step exists.
hasPrev
ComputedRef<boolean>
Whether a previous step exists.
start
(index?: number) => void
Open the tour, optionally at a given index.
next
() => void
Go to the next step. Loops or finishes at the end depending on the loop option.
prev
() => void
Go to the previous step.
goTo
(index: number) => void
Jump to a specific step and open the tour.
finish
() => void
Close the tour.