Onboarding Flow
First-run onboarding on the Guide system: a pulsing beacon offers an opt-in spotlight tour, a non-modal help panel links back into the UI, a manual hint flags the next feature, and the tour reports its own funnel through onStep, onComplete and onSkip.
Live preview
WorkspacePage.svelte
onStep / onComplete / onSkip, and finishing reveals the New: API keys hint. The ⓘ opens the help panel.Tour analytics
onStep / onComplete / onSkip fire from the tour itself:
the funnel and drop-off signal onboarding is run for.
<script lang="ts">
import {
Guide,
GuideArticle,
GuideBeacon,
GuideController,
GuideHint,
GuideMarker,
GuideMention,
GuidePanel,
GuideProvider
} from '@urbicon-ui/blocks';
import type { GuideTour } from '@urbicon-ui/blocks';
const guide = new GuideController();
let hintOpen = $state(false);
const onboardingTour: GuideTour = {
id: 'onboarding',
// once: false — here, on the beacon and on the hint — keeps the docs demo
// repeatable. Drop all three in your app: the default (true) shows each
// once per user, remembered through the controller's storage adapter.
once: false,
steps: [
{
target: 'projects',
title: 'Create your first project',
body: 'Everything starts with a project: your space for tasks, files, and docs.'
},
{
target: 'team',
title: 'Invite your team',
body: 'Bring teammates in so they can collaborate from day one.'
},
{
// interactive keeps the spotlit tile clickable during this step
target: 'api',
title: 'Generate an API key',
body: 'Automate anything once you are set up.',
interactive: true
}
],
// Stand-ins for your analytics calls — the demo pipes the same events into
// the log beside the workspace.
onStep: (e) =>
analytics.track('onboard_step', { step: e.index + 1, total: e.total, via: e.via }),
onComplete: () => {
analytics.track('onboard_complete');
hintOpen = true; // reveal the "new feature" hint once onboarding is done
},
onSkip: (e) => analytics.track('onboard_skip', { droppedAt: e.index + 1 })
};
</script>
<GuideProvider controller={guide}>
<!-- Your app shell — the provider wraps it once, near the root. -->
<header>
<span>Acme Workspace</span>
<!-- the ⓘ: opens the help panel at the matching article -->
<GuideMarker for="projects" />
<!-- the opt-in tour entry; hides itself once the tour is seen -->
<GuideBeacon tour={onboardingTour} once={false} />
</header>
<!-- data-guide marks each target once; tour steps, the marker, the mentions
and the hint all resolve to it -->
<button data-guide="projects">New project</button>
<button data-guide="team">Invite team</button>
<button data-guide="api">API keys</button>
<!-- non-modal help: the workspace stays usable behind the open panel -->
<GuidePanel title="Workspace help">
<GuideArticle id="projects" title="Projects & workspace">
<p>
A <GuideMention for="projects">project</GuideMention> groups your tasks, files, and
docs. Start there, then add people from
<GuideMention for="team">team settings</GuideMention>.
</p>
<p>
Prefer automation? Generate an
<GuideMention for="api">API key</GuideMention> and drive everything from the API.
</p>
</GuideArticle>
</GuidePanel>
<!-- waits for onComplete to raise hintOpen -->
<GuideHint
for="api"
trigger="manual"
open={hintOpen}
once={false}
title="New: API keys"
onDismiss={() => (hintOpen = false)}
>
You can now generate scoped API keys for automation.
</GuideHint>
<!-- the tour renderer: mount once; renders nothing until a tour starts -->
<Guide />
</GuideProvider>Opt-in, drop-off, top layer
The tour is offered, not imposed
An auto-starting tour interrupts everyone to help a few. GuideBeacon waits instead: it pulses beside the
header until clicked, and with once (the default)
the controller remembers a finished or skipped tour by its id, so nobody is toured twice. That memory lives in
a storage adapter: localStorage out of the box, swappable through the controller's storage option when seen-state should follow the account
instead of the browser.
Skip is a signal
onSkip reports the step index where the user
dropped off, and it fires however the tour ends early: the Skip button, Escape, or a
foreign modal taking over. Programmatic teardown via stopTour() stays silent on purpose, so a route change
does not count as a lost user.
The overlays out-stack the app
The tour's spotlight and the hint render in the native popover top layer: they clear
whatever stacking contexts your app builds, which is also why they may reach over this
docs page. A foreign modal above them pauses the tour and hides the hint rather than
fighting for z-index.
The tour above is the smallest one. Steps can also gate on the user's real action (advance: 'action') or live on another route (route, with a navigate hook wired to goto); the Guide page documents both, together with the panel's search and article groups.