SvTour

A guided product tour: it spotlights each target element in turn, anchors a popover with the step's copy, and walks the user through Back / Next / Done.

SvTour is the onboarding overlay. Each step names a target - a CSS selector or an element - which it dims around with a cut-out spotlight, scrolls into view, and labels with a popover placed above or below depending on room. It steps forward and back, tracks progress ("2 / 4"), and can be skipped. Arrow keys navigate, Escape skips, and everything is portalled to <body>. Steps with no target render centered, handy for an intro or outro.

Related: SvCommand · SvStepper · Navigation & rich overview

Installation

Add it with the CLI - this drops a ready-to-edit SvTour starter into your app:

Prefer to see it first? npx @svgrid/ui try tour opens it in a throwaway sandbox - no project needed.

Or install the package and import it directly. SvTour ships free in @svgrid/grid (dependency-free):

The examples on this page import from @svgrid/grid:

<script lang="ts">
  import { SvButton, SvTour } from '@svgrid/grid'
</script>
import { SvTour } from '@svgrid/grid'

Example

Open the live example: Carousel & guided tour (Layout)

<script lang="ts">
  import { SvTour } from '@svgrid/grid'
  let open = $state(false)
</script>

<SvButton onclick={() => (open = true)}>Take the tour</SvButton>

<SvTour bind:open steps={[
  { target: '#new-btn', title: 'Create', content: 'Start a record here.' },
  { target: '.grid', title: 'Your data', content: 'It shows up in the grid.' },
]} />

Props

Prop Type Default Description
steps ReadonlyArray<TourStep> - The ordered tour steps.
open boolean false Whether the tour is running (bindable).
onFinish () => void - Fires when the user reaches Done.
onSkip () => void - Fires when the user skips or presses Escape.
onStep (index: number) => void - Fires whenever the active step changes.
dir EditorDir (ltr | rtl | auto) - Text direction; under rtl the Left / Right navigation keys swap.

TourStep (exported): { target?: string | HTMLElement; title?: string; content: string }. target is a CSS selector or the element itself; omit it for a centered, target-less step (an intro or outro). content is required.

Examples

Show the tour once

Gate open on a stored flag so returning users are not re-onboarded:

<script lang="ts">
  let open = $state(!localStorage.getItem('toured'))
</script>

<SvTour bind:open onFinish={() => localStorage.setItem('toured', '1')} {steps} />

An intro step with no target

Leave target off the first step for a welcome card centered on screen:

<SvTour bind:open steps={[
  { title: 'Welcome', content: 'Let us show you around in 30 seconds.' },
  { target: '#sidebar', title: 'Navigate', content: 'Everything lives here.' },
]} />

React to progress

Use onStep to lazy-load or highlight related UI as the tour advances:

<SvTour bind:open {steps} onStep={(i) => analytics.track('tour_step', i)} />

First-visit onboarding with replay

A complete onboarding flow: a centered intro, three anchored steps, a centered outro, auto-start on first visit, and a Help button to replay it later. Start it from onMount so the target elements exist before the first step measures them, and persist the "seen" flag from both onFinish and onSkip:

<script lang="ts">
  import { SvTour, SvButton, type TourStep } from '@svgrid/grid'
  import { onMount } from 'svelte'

  const steps: TourStep[] = [
    { title: 'Welcome to Orders', content: 'A 20-second look at the essentials.' },
    { target: '#new-btn', title: 'Create', content: 'Start a new order from here.' },
    { target: '.sv-grid', title: 'Your data', content: 'Every order lands in this grid.' },
    { target: '#filter', title: 'Filter', content: 'Narrow the list with quick filters.' },
    { title: 'You are set', content: 'Reopen this any time from Help.' },
  ]

  let open = $state(false)
  onMount(() => { if (!localStorage.getItem('toured')) open = true })
  const done = () => localStorage.setItem('toured', '1')
</script>

<SvButton onclick={() => (open = true)}>Replay tour</SvButton>

<SvTour bind:open {steps} onFinish={done} onSkip={done} />

Tip: a step whose target selector matches nothing (not mounted yet, or removed) falls back to a centered, target-less popover rather than erroring - which is also exactly how the intro and outro steps here render, since they carry no target.

Accessibility

See also