Skip to content

Tour

The Tour component guides users through a sequence of steps, highlighting a target element with a spotlight and showing a popover next to it. Steps are declared with Tour Item components. It is controlled through v-model:active and v-model:step. While active, the rest of the page is dimmed and not interactive and Escape skips the tour.

TIP

Each step's target is a CSS selector or a function returning an element. Make sure the target exists in the DOM when its step becomes active.

Props

mask-padding?: number
The padding in pixels around the highlighted element.
Default: 8

root?: string | HTMLElement | (() => HTMLElement | null)
Container the step targets are resolved within. Useful when multiple tours share selectors on one page. Defaults to the document.

Emits

finish: []
Triggered when the last step is completed.

skip: []
Triggered when the tour is skipped or dismissed with Escape.

next: [number]
Triggered when advancing to the next step, with the new step index.

prev: [number]
Triggered when going back a step, with the new step index.

Slots

default
The steps of the tour, provided as Tour Item components.

Tour Item

Each step of the tour is a FluxTourItem. It targets an element through the target prop, optionally shows a title, and renders its default slot as the step content.

PropTypeDescription
targetstring | (() => HTMLElement | null)A CSS selector or getter for the element this step highlights.
titlestringThe optional title shown at the top of the step.
positionFluxTourPositionThe optional position of the popover relative to the target. Defaults to bottom.

The default slot holds the step content.

Examples

Basic

A three-step product tour.

<template>
    <div
        ref="root"
        style="display: flex; flex-flow: column; gap: 18px; align-items: flex-start">
        <div style="display: flex; gap: 9px">
            <span id="tour-create">
                <FluxPrimaryButton label="Create"/>
            </span>

            <span id="tour-search">
                <FluxSecondaryButton label="Search"/>
            </span>

            <span id="tour-settings">
                <FluxSecondaryButton label="Settings"/>
            </span>
        </div>

        <FluxSecondaryButton
            label="Start tour"
            @click="start"/>

        <FluxTour
            v-model:active="active"
            v-model:step="step"
            :root="root">
            <FluxTourItem
                target="#tour-create"
                title="Create">
                Start by creating a new item here.
            </FluxTourItem>

            <FluxTourItem
                target="#tour-search"
                title="Search">
                Quickly find anything in your workspace.
            </FluxTourItem>

            <FluxTourItem
                target="#tour-settings"
                title="Settings">
                Tweak your preferences anytime.
            </FluxTourItem>
        </FluxTour>
    </div>
</template>

<script
    setup
    lang="ts">
    import { FluxPrimaryButton, FluxSecondaryButton, FluxTour, FluxTourItem } from '@flux-ui/components';
    import { ref } from 'vue';

    const root = ref<HTMLElement>();

    const active = ref(false);
    const step = ref(0);

    function start(): void {
        step.value = 0;
        active.value = true;
    }
</script>

Positions

Control where each popover appears with the position prop.

<template>
    <div style="display: flex; flex-flow: column; gap: 18px; align-items: flex-start">
        <div style="display: flex; gap: 9px; flex-wrap: wrap">
            <span id="pos-bottom">
                <FluxSecondaryButton label="Bottom"/>
            </span>

            <span id="pos-top">
                <FluxSecondaryButton label="Top"/>
            </span>

            <span id="pos-right">
                <FluxSecondaryButton label="Right"/>
            </span>

            <span id="pos-left">
                <FluxSecondaryButton label="Left"/>
            </span>
        </div>

        <FluxSecondaryButton
            label="Start tour"
            @click="start"/>

        <FluxTour
            v-model:active="active"
            v-model:step="step">
            <FluxTourItem
                target="#pos-bottom"
                title="Bottom"
                position="bottom">
                The popover appears below the target.
            </FluxTourItem>

            <FluxTourItem
                target="#pos-top"
                title="Top"
                position="top">
                The popover appears above the target.
            </FluxTourItem>

            <FluxTourItem
                target="#pos-right"
                title="Right"
                position="right">
                The popover appears to the right of the target.
            </FluxTourItem>

            <FluxTourItem
                target="#pos-left"
                title="Left"
                position="left">
                The popover appears to the left of the target.
            </FluxTourItem>
        </FluxTour>
    </div>
</template>

<script
    setup
    lang="ts">
    import { FluxSecondaryButton, FluxTour, FluxTourItem } from '@flux-ui/components';
    import { ref } from 'vue';

    const active = ref(false);
    const step = ref(0);

    function start(): void {
        step.value = 0;
        active.value = true;
    }
</script>

Custom content

Render any component inside a step through its default slot.

<template>
    <div style="display: flex; flex-flow: column; gap: 18px; align-items: flex-start">
        <div style="display: flex; gap: 9px">
            <span id="custom-create">
                <FluxPrimaryButton
                    icon-leading="plus"
                    label="New project"/>
            </span>

            <span id="custom-search">
                <FluxSecondaryButton
                    icon-leading="magnifying-glass"
                    label="Search"/>
            </span>

            <span id="custom-invite">
                <FluxSecondaryButton
                    icon-leading="user-plus"
                    label="Invite"/>
            </span>
        </div>

        <FluxSecondaryButton
            label="Start tour"
            @click="start"/>

        <FluxTour
            v-model:active="active"
            v-model:step="step">
            <FluxTourItem
                target="#custom-create"
                title="Create a project">
                <div style="display: flex; gap: 12px; align-items: flex-start">
                    <FluxBoxedIcon
                        color="primary"
                        name="rocket"/>

                    <div style="display: flex; flex-flow: column; gap: 9px; align-items: flex-start">
                        <span>Spin up a new project and start collaborating right away.</span>

                        <FluxChip
                            icon-leading="bolt"
                            label="Shortcut: C"/>
                    </div>
                </div>
            </FluxTourItem>

            <FluxTourItem
                target="#custom-search"
                title="Find anything">
                <div style="display: flex; flex-flow: column; gap: 9px; align-items: flex-start">
                    <span>Search across projects, files and people from a single place.</span>

                    <div style="display: flex; gap: 6px">
                        <FluxBadge
                            color="info"
                            icon="file"
                            label="Files"/>

                        <FluxBadge
                            color="success"
                            icon="users"
                            label="People"/>
                    </div>
                </div>
            </FluxTourItem>

            <FluxTourItem
                target="#custom-invite"
                title="Invite your team">
                <div style="display: flex; flex-flow: column; gap: 12px; align-items: flex-start">
                    <span>Bring your teammates on board to work together in real time.</span>

                    <FluxLink
                        href="#"
                        icon="circle-info"
                        label="Learn about roles"/>
                </div>
            </FluxTourItem>
        </FluxTour>
    </div>
</template>

<script
    setup
    lang="ts">
    import { FluxBadge, FluxBoxedIcon, FluxChip, FluxLink, FluxPrimaryButton, FluxSecondaryButton, FluxTour, FluxTourItem } from '@flux-ui/components';
    import { ref } from 'vue';

    const active = ref(false);
    const step = ref(0);

    function start(): void {
        step.value = 0;
        active.value = true;
    }
</script>

Rich onboarding

Combine progress, avatars and actions for a richer tour.

<template>
    <div style="display: flex; flex-flow: column; gap: 18px; align-items: flex-start">
        <div style="display: flex; gap: 9px">
            <span id="rich-dashboard">
                <FluxSecondaryButton
                    icon-leading="gauge-high"
                    label="Dashboard"/>
            </span>

            <span id="rich-team">
                <FluxSecondaryButton
                    icon-leading="users"
                    label="Team"/>
            </span>

            <span id="rich-upgrade">
                <FluxPrimaryButton
                    icon-leading="sparkles"
                    label="Upgrade"/>
            </span>
        </div>

        <FluxSecondaryButton
            label="Start tour"
            @click="start"/>

        <FluxTour
            v-model:active="active"
            v-model:step="step">
            <FluxTourItem
                target="#rich-dashboard"
                title="Welcome aboard">
                <div style="display: flex; flex-flow: column; gap: 12px">
                    <span>Let's finish setting up your workspace. Here's how far you've come.</span>

                    <FluxProgressBar
                        :value="40"
                        status="2 of 5 steps complete"/>
                </div>
            </FluxTourItem>

            <FluxTourItem
                target="#rich-team"
                title="Your team">
                <div style="display: flex; flex-flow: column; gap: 12px; align-items: flex-start">
                    <span>You're already collaborating with a few people.</span>

                    <FluxAvatarGroup
                        :max="4"
                        :size="32">
                        <FluxAvatar
                            fallback="colorized"
                            fallback-initials="AB"/>

                        <FluxAvatar
                            fallback="colorized"
                            fallback-initials="CD"/>

                        <FluxAvatar
                            fallback="colorized"
                            fallback-initials="EF"/>
                    </FluxAvatarGroup>
                </div>
            </FluxTourItem>

            <FluxTourItem
                target="#rich-upgrade"
                title="Unlock more">
                <div style="display: flex; flex-flow: column; gap: 12px; align-items: flex-start">
                    <div style="display: flex; gap: 12px; align-items: flex-start">
                        <FluxBoxedIcon
                            color="warning"
                            name="star"/>

                        <span>Upgrade to Pro for unlimited projects and priority support.</span>
                    </div>

                    <FluxPrimaryButton
                        icon-leading="sparkles"
                        label="See plans"
                        @click="active = false"/>
                </div>
            </FluxTourItem>
        </FluxTour>
    </div>
</template>

<script
    setup
    lang="ts">
    import { FluxAvatar, FluxAvatarGroup, FluxBoxedIcon, FluxPrimaryButton, FluxProgressBar, FluxSecondaryButton, FluxTour, FluxTourItem } from '@flux-ui/components';
    import { ref } from 'vue';

    const active = ref(false);
    const step = ref(0);

    function start(): void {
        step.value = 0;
        active.value = true;
    }
</script>

Used components