svelte-tourlight GitHub

Getting started

Five steps to a working tour in a SvelteKit app.

1. Install

Terminal
npm i svelte-tourlight
# or
pnpm add svelte-tourlight

Peer dependency: svelte@^5. The only runtime dependency is @floating-ui/dom. The styles load with the provider, so there is nothing else to import.

2. Put the provider in your root layout

Mount <SpotlightProvider> once in src/routes/+layout.svelte. It stays mounted across navigations, which is what lets a tour move between pages.

src/routes/+layout.svelte
<!-- src/routes/+layout.svelte -->
<script lang="ts">
  import { goto } from '$app/navigation';
  import { resolve } from '$app/paths';
  import { SpotlightProvider, SpotlightTour } from 'svelte-tourlight';
  import { onboarding } from '$lib/tours';

  let { children } = $props();
</script>

<SpotlightProvider
  theme="auto"
  persist={sessionStorage}
  basePath={resolve('/').replace(/\/$/, '')}
  navigate={(path) => goto(path)}
>
  <SpotlightTour id="onboarding" steps={onboarding} />
  {@render children()}
</SpotlightProvider>

3. Route-aware steps with navigate

A step with a route lives on that page. If the current page doesn't match, the provider calls navigate(path); pass SvelteKit's goto. If your app has a base path, pass it as basePath and keep routes in your tours base-free. Routes match exactly, with :param segments or a trailing *.

4. Start a tour

Any component
<script lang="ts">
  import { useSpotlight } from 'svelte-tourlight';
  const tour = useSpotlight();
</script>

<button onclick={() => tour.start('onboarding')}>Take the tour</button>

Give targets stable data-tour="…" attributes rather than styling classes. A step whose target never appears (hidden by permissions, say) is skipped and reported as target-missing.

5. Persistence

Progress is saved through a small synchronous storage interface, so a tour survives reloads. Use localStorage, sessionStorage, memory, or seed it from your API.

Persistence options
<!-- localStorage: progress survives closing the tab -->
<SpotlightProvider persist>

<!-- sessionStorage: survives reloads in this tab only -->
<SpotlightProvider persist={sessionStorage}>

<!-- Your API: seed from load data, write back on change -->
<SpotlightProvider
  initialState={data.tourState}
  onStateChange={(id, state) => saveTourState(id, state)}
>

Extra: the footer snippet

footer renders under every step's body, inside the default tooltip. It's the place for a "Don't show tours again" checkbox without replacing the whole tooltip.

footer snippet
{#snippet footer(p)}
  {#if p.isFirst && !p.isHighlight}
    <label>
      <input type="checkbox" onchange={() => { disableTours(); p.close(); }} />
      Don't show tours again
    </label>
  {/if}
{/snippet}

<SpotlightProvider {footer}>

Next: the basic tour example.