Getting started
Five steps to a working tour in a SvelteKit app.
1. Install
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 -->
<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
<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.
<!-- 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.
{#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.