Tour
Walk people through a screen one element at a time, with a spotlight and a card beside it.
import { Tour, TourBeacon, TourStep } from '@youj/react'
Installation & usage
npm i @youj/reactFor an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.
Use a Tour to show someone around a screen they haven't used before, or a few new things on one they have. Keep it short: three to five steps, each about one thing they can see. For one new control a Popover or a TourBeacon with a one-step tour is enough.
import { useState } from 'react'
import { Button, Tour, type TourStep } from '@youj/react'
const steps: TourStep[] = [
{ target: '#search', title: 'Find any order', body: 'Search by number, customer or amount.' },
{ target: '#filters', title: 'Narrow the list', body: 'Filter by status, channel or date.' },
{ target: '#export', title: 'Take it with you', body: 'Export what you see as CSV.', placement: 'top' },
]
export function OrdersTour() {
const [open, setOpen] = useState(false)
return (
<>
<Button onClick={() => setOpen(true)}>Take the tour</Button>
<Tour steps={steps} open={open} onOpenChange={setOpen} onComplete={markSeen} onSkip={markSeen} />
</>
)
}A step's target is a CSS selector (the first match on the page) or a ref. A step whose target isn't on the page, or is hidden, is skipped in the direction you were going. placement puts the card on a side of the target (bottom by default; top, inline-start or inline-end), and it flips when there's no room. action adds your own content under the text, such as a link or a button that does the thing.
open and step can each be controlled (onOpenChange, onStepChange), or left to the tour with defaultOpen and defaultStep; an uncontrolled tour starts again from defaultStep each time it opens. onComplete runs when Done is pressed, onSkip with the step index when Skip or Escape ends it early.
To keep the tour inside part of the page, as these previews do, pass that element as container and give it contain: paint (or a transform), so the scrim covers it rather than the window.
Description
A first-run tour of a small dashboard: the sidebar, search, the figures and Export, one at a time.
Each step dims the page and cuts a rounded hole over its target, with a card beside it: the step's title and text, the count ("2 of 4") and dots, Back, Next (Done on the last step) and Skip. Between steps the hole glides to the next target. The arrow keys go Back and Next, and Escape skips.
A pulsing dot beside a new control starts a one-step tour about it, and goes away once it has been seen.
TourBeacon is a small button: a dot with a ring that swells out of it and fades. Put it where you want attention and open a tour from its onClick. Here the tour sets allowInteraction, so the bell under the spotlight can be clicked while the card is open.
Code
tour-demo.tsx, the Demo story. The component itself comes from the package.
Reference
Propssteps, open, step, onComplete, onSkip, allowInteraction, dismissible, container…
Props
Tour
| Prop | Type | |
|---|---|---|
stepsRequired | TourStep[] | – |
open | boolean | – |
defaultOpen | booleandefault false | false |
onOpenChange | (open: boolean) => void | – |
step | numberControlled step index. | – |
defaultStep | numberdefault 0 | 0 |
onStepChange | (step: number) => void | – |
onComplete | () => voidRuns when Done is pressed on the last step (or Next runs past the last step that can be shown). | – |
onSkip | (step: number) => voidRuns when the tour is left early, by Skip or Escape, with the step it was on. | – |
allowInteraction | booleanClicks reach the spotlighted element, and focus is not trapped in the card, so the target can be used. default false | false |
dismissible | booleanSkip and Escape end the tour. With false the only ways out are Done and your own controls. default true | true |
spotlightPadding | numberRoom around the target inside the cut-out, in px. default 6 | 6 |
container | HTMLElement | nullWhere the scrim and card render. Default: the provider's portal container,
else | – |
className | stringThe card's classes. | – |
TourBeacon
| Prop | Type | |
|---|---|---|
label | stringThe button's accessible name. Default: the | – |
Other props pass through: extends ComponentProps<'button'>. | ||
TourBeacon also takes a button's props (onClick, className…).
Data attributes
The card is data-slot="tour", the scrim data-slot="tour-scrim" and the outline around the hole data-slot="tour-spotlight". In the card, tour-count, tour-title, tour-body and tour-dots mark the parts, and the current dot carries data-state="active". The beacon is data-slot="tour-beacon".
MotionGlide · card · open, close · beacon · reduced motion
Motion
| Moment | Tokens | ||
|---|---|---|---|
| Glidethe hole moves and resizes from the last target to the next, following it live while the page scrolls it into view | spring.smooth0.36s, bounce 0 | 0.36s, bounce 0 | |
| Cardthe next step’s text fades in, rising 4px, where the card lands | duration.fastease.enter140ms · cubic-bezier(0.16, 1, 0.3, 1) | 140mscubic-bezier(0.16, 1, 0.3, 1) | |
Open, closethe scrim fades (yj-backdrop) and the card opens with yj-pop | duration.fastease.enter140ms · cubic-bezier(0.16, 1, 0.3, 1) | 140mscubic-bezier(0.16, 1, 0.3, 1) | |
| Scrolla target off screen scrolls to the middle smoothly | no token | ||
| Beacona ring swells out of the dot and fades, repeating while it is on screen | duration.deliberateease.exit560ms · cubic-bezier(0.4, 0, 1, 1) | 560mscubic-bezier(0.4, 0, 1, 1) |
Reduced motion
The hole moves to each target at once and scrolling is instant; only the card's text fades. The beacon's ring rests still around the dot.
Reduced motion
Every step lands at once, so nothing glides or slides; what changes is still shown, and announced.
AccessibilityFocus, keyboard, ARIA, live region
Accessibility
- The card is a dialog named by the step's title and described by its text, followed by a hint on the keys. Focus moves into it (to Next) when the tour opens and stays in it while the tour runs.
- ← and → go Back and Next (swapped in right-to-left), Tab moves between the buttons, and Escape skips the tour when it's
dismissible. - Each new step is announced politely: "Step 2 of 4: Find any order".
- The spotlighted element is described by the step's text (
aria-describedby) while it's shown, and its own description is put back afterwards. - With
allowInteraction, clicks reach the spotlighted element and focus isn't held in the card, so the element can be used from the keyboard too. - The count and the dots say the same thing; the dots are hidden from assistive technology.
TourBeaconis a button named "Start the tour" by default; passlabelto say what it's about.