Skip to content

Tour ​

A neo-brutalist step-by-step guided tour component. Uses a canvas cutout mask to highlight target elements with popover panels for step descriptions. Supports four-directional placement, keyboard navigation, and viewport boundary constraints to help users quickly learn interface features.

Demo ​

Preview
导航
操作区
⚙ 设置

Installation ​

pnpm dlx brutx-vue@latest add tour

Usage ​

Basic Usage ​

Define a steps array and control the tour's visibility and current step via v-model:open and v-model:current:

vue
<script setup>
import { ref } from 'vue'
import { Tour } from 'brutx-ui-vue'

const isOpen = ref(true)
const current = ref(0)

const steps = [
    {
        target: '#step-nav',
        title: 'Navigation',
        description: 'This is the main navigation area with links to key sections.',
        placement: 'bottom',
    },
    {
        target: '#step-search',
        title: 'Search',
        description: 'Use the search bar to quickly find what you need.',
        placement: 'right',
    },
    {
        target: '#step-profile',
        title: 'Profile',
        description: 'Click your avatar to manage your profile settings.',
        placement: 'left',
    },
]
</script>

<template>
    <Tour v-model:open="isOpen" v-model:current="current" :steps="steps" />
</template>

Custom Placement and Mask ​

Each step can independently set the popover direction (placement) and whether to show the mask (mask). When not specified on a step, the component-level mask prop is inherited (defaults to true).

vue
<script setup>
import { ref } from 'vue'
import { Tour } from 'brutx-ui-vue'

const isOpen = ref(true)
const current = ref(0)

const steps = [
    {
        target: '#hero-section',
        title: 'Welcome',
        description: 'This is the hero section.',
        placement: 'top',
        mask: true,
    },
    {
        target: '#sidebar',
        title: 'Sidebar',
        description: 'The sidebar has no mask overlay.',
        placement: 'right',
        mask: false,
    },
]
</script>

<template>
    <Tour v-model:open="isOpen" v-model:current="current" :steps="steps" />
</template>

Listening to Events ​

Respond to user actions via the @skip, @finish, and @close events:

vue
<script setup>
import { ref } from 'vue'
import { Tour } from 'brutx-ui-vue'

const isOpen = ref(true)
const current = ref(0)

const steps = [
    { target: '#feature-a', title: 'Feature A', description: 'Learn about Feature A.' },
    { target: '#feature-b', title: 'Feature B', description: 'Learn about Feature B.' },
]

const onSkip = () => {
    console.log('User skipped the tour')
}

const onFinish = () => {
    console.log('User completed all steps')
}

const onClose = () => {
    console.log('Tour closed')
}
</script>

<template>
    <Tour
        v-model:open="isOpen"
        v-model:current="current"
        :steps="steps"
        @skip="onSkip"
        @finish="onFinish"
        @close="onClose"
    />
</template>

Custom Scroll Behavior ​

Customize how the target element scrolls into view via scrollIntoViewOptions:

vue
<script setup>
import { ref } from 'vue'
import { Tour } from 'brutx-ui-vue'

const isOpen = ref(true)
const current = ref(0)

const steps = [
    { target: '#section-1', title: 'Section 1', description: 'Content at the top of the page.' },
    { target: '#section-2', title: 'Section 2', description: 'Requires scrolling down to see.' },
]
</script>

<template>
    <Tour
        v-model:open="isOpen"
        v-model:current="current"
        :steps="steps"
        :scroll-into-view-options="{ behavior: 'smooth', block: 'center' }"
    />
</template>

Data Types ​

TourStep ​

ts
interface TourStep {
    target: string | HTMLElement
    title?: string
    description?: string
    placement?: 'top' | 'bottom' | 'left' | 'right'
    mask?: boolean
}
FieldTypeDefaultDescription
targetstring | HTMLElement—Target element as a CSS selector string or DOM element reference (required)
titlestring—Step title
descriptionstring—Step description text
placement'top' | 'bottom' | 'left' | 'right''bottom'Popover direction relative to the target element
maskboolean—Whether to show the mask for this step; inherits from the component-level mask prop when not specified

Exported Types ​

ts
import type { TourStep, TourProps } from 'brutx-ui-vue'

API Reference ​

Tour — Props
NameTypeDefaultDescription
steps
Required
TourStep[]
—
Provides ordered tour steps. Each step targets an element by CSS selector or HTMLElement and may include a title, description, placement, and mask setting.
Details

Declared type

TourStep[]

Referenced types

TourStep
Type definition — TourStep
export interface TourStep {
    target: string | HTMLElement
    title?: string
    description?: string
    placement?: 'top' | 'bottom' | 'left' | 'right'
    mask?: boolean
}

Default value details

Not declared
—

Source

types.ts:12

packages/ui/src/components/tour/types.ts

mask
boolean | undefined
true
Controls whether the mask is shown outside the current target. It defaults to true and can be overridden by the current TourStep.mask.
Details

Declared type

boolean | undefined

Literal values

  • false
  • true

Default value details

Resolved value
true

Source

types.ts:13

packages/ui/src/components/tour/types.ts

scrollIntoViewOptions
ScrollIntoViewOptions | undefined
undefined
Sets the options passed to scrollIntoView before showing a step. When omitted, block: center and inline: nearest are used.
Details

Declared type

ScrollIntoViewOptions | undefined

Default value details

Declaration
undefined

Source: packages/ui/src/components/tour/Tour.vue

Resolved value
undefined

Source

types.ts:14

packages/ui/src/components/tour/types.ts

current
number | undefined
0
The zero-based current tour step, synchronized through v-model:current and defaulting to the first step.
Details

Declared type

number | undefined

Default value details

Resolved value
0

Source

Tour.vue

packages/ui/src/components/tour/Tour.vue

open
boolean | undefined
true
Controls whether the tour is open, synchronized through v-model:open and defaulting to open.
Details

Declared type

boolean | undefined

Literal values

  • false
  • true

Default value details

Resolved value
true

Source

Tour.vue

packages/ui/src/components/tour/Tour.vue

Tour — Events
NameSignature or typeDescription
@close
[]
Emitted when the user skips or finishes the tour and it closes.
Details

Declared type

[]

Source

Tour.vue

packages/ui/src/components/tour/Tour.vue

@update:open
[value: boolean]
Emits the new boolean open state when visibility changes so v-model:open can be synchronized.
Details

Declared type

[value: boolean]

Literal values

  • false
  • true

Source

Tour.vue

packages/ui/src/components/tour/Tour.vue

@finish
[]
Emitted when the user chooses Finish on the final step.
Details

Declared type

[]

Source

Tour.vue

packages/ui/src/components/tour/Tour.vue

@update:current
[value: number]
Emits the new index when the current step changes so v-model:current can be synchronized.
Details

Declared type

[value: number]

Source

Tour.vue

packages/ui/src/components/tour/Tour.vue

@skip
[]
Emitted when the user chooses to skip the tour.
Details

Declared type

[]

Source

Tour.vue

packages/ui/src/components/tour/Tour.vue

Accessibility ​

  • Keyboard: Press Escape to skip the tour, press Enter to advance to the next step or finish the tour
  • ARIA Attributes: Step title and description in the popover panel are rendered with semantic markup; buttons include accessible text labels
  • Focus Management: Buttons inside the popover panel are focusable while the tour is open; focus is released when the tour closes
  • Viewport Constraints: The popover panel is automatically constrained within the viewport safe area and will not overflow screen edges

FAQ ​

Q: What happens if the target element is outside the visible area?

A: The Tour automatically calls scrollIntoView when switching steps to scroll the target element to the center of the viewport. You can customize the scroll behavior via the scrollIntoViewOptions prop, for example { behavior: 'smooth', block: 'start' } for smooth scrolling to the top.

Q: The mask position is incorrect after the target element resizes.

A: The Tour internally uses ResizeObserver to monitor target element size changes and automatically recalculates the mask cutout area and popover position. Global scroll and resize event listeners are also registered to prevent positioning drift caused by page scrolling or window resizing. All listeners are automatically cleaned up when the component unmounts.

Q: How do I use a DOM element reference instead of a CSS selector?

A: The target prop accepts an HTMLElement instance directly. In Vue, you can use a template ref to obtain the element and pass it to target:

vue
<script setup>
import { ref } from 'vue'
import { Tour } from 'brutx-ui-vue'

const targetEl = ref(null)
const isOpen = ref(true)
const current = ref(0)
const steps = [{ target: targetEl, title: 'Title', description: 'Description' }]
</script>

<template>
    <div ref="targetEl">Target element</div>
    <Tour v-model:open="isOpen" v-model:current="current" :steps="steps" />
</template>

Q: How do I run custom logic after the last step?

A: Listen to the @finish event. It is emitted when the user completes all steps and clicks the "Finish" button. The @close event is also emitted in both skip and finish scenarios, making it suitable for unified cleanup logic after the tour ends.

Brute force builds.