Tour

A guided walk through an interface: a mask with a hole in it over one element at a time, and a card explaining what it is.

Usage

When to reach for Tour, what each of its axes is for, and the decisions it leaves to you.

When to use

A tour is a guided walk through a screen: a dimmed mask with a hole cut in it over one element at a time, and a card beside the hole saying what that element is. It is for the first visit to a screen that is already finished, not for a screen that needs explaining every time.
Reach for it when several controls only make sense together, when a redesign moved something readers already knew how to find, or when a feature ships into a surface people have been using for months.
  • One control needs explaining? That is a Tooltip on the control, or help text beside it. It is there every time rather than once, and it is there at the moment the reader is actually looking at the control.
  • The screen is empty on first use? An empty state with one sentence and one button beats a tour, because it sits in the space the reader is already reading.
  • The thing is complicated? Documentation can be searched, linked and re-read. A tour is gone the moment it is dismissed, and nothing brings it back unless you build that.
  • The interface explains itself? Then nothing at all. A tour over a clear screen is a modal the reader did not ask for, and it teaches them to dismiss the next one unread.
Keep it short either way. Four or five steps is a tour. Twelve is a manual with a mask over it.

Steps, and pointing at a ref

steps is an array of plain objects, one per stop, carrying a title, a description and a target. Everything else on a step is an override of the Tour-level prop with the same name.
target is a function returning an element. It is called when the step becomes current rather than when the Tour mounts, so a step can point at something that had not rendered yet. Hold the element in a useRef and return ref.current.
  • A step with no target, or one whose function returns null, is centered on the screen with no cut-out. That is the right shape for a welcome step and for a closing one.
  • A target that is off screen is scrolled into view first. One that is already visible never moves under the reader.
  • scrollIntoViewOptions defaults to { block: "center", inline: "center" } and can be set on the Tour or on one step.
  • A target that never mounts degrades to the centered card rather than throwing, which is quiet in the wrong way. Build the steps from the same condition that renders the elements: a step about a control only admins see is a step only admins should get.
  • The hole is re-measured on resize and on scroll, and on nothing else. A target that moves for another reason (a panel opening, an image finishing) leaves the cut-out behind until one of those two fires.

The mask and the cut-out

mask defaults to true. It is one full-screen SVG with the target punched out of it, so the highlighted element can still be clicked while its own step is showing.
  • The hole is the target's bounding box plus gap.offset (6px by default) on each side, with gap.radius (2 by default) on its corners. The target's own border-radius is ignored, so a heavily rounded control spotlights as a nearly square rectangle until you set gap.radius. It rounds the spotlight, not the card: round or square the card with classNames.section.
  • mask={false} drops the overlay entirely and leaves the positioned card on its own. Pair it with type="primary". With nothing dimmed behind it, the card has to stand out on its own.
  • mask={{ color }} retints rather than removes, at the Tour level or per step, and a single step can turn its own mask off while the tour around it stays masked.
  • Clicking the dimmed area does nothing. The mask carries no click handler, so there is no click-outside-to-dismiss here.
  • disabledInteraction makes the whole overlay swallow pointer events, cut-out included. Reach for it when using the highlighted control mid-step would break the tour underneath.
  • The page's scroll is locked for as long as the tour is open, so the reader cannot browse away from the step they are on.

Placement

placement is where the card sits relative to the hole. It defaults to bottom when the step has a target and to center when it does not, and a step's own value beats the Tour's.
  • The thirteen values are the popup set: top, bottom, left and right, each with its two aligned variants (topLeft, rightBottom), plus center.
  • autoAdjustOverflow is on, so a card that will not fit flips to the opposite side by itself. Check the tour once at a narrow width.
  • Put the card where it does not cover the thing the reader looks at next. A card below a toolbar hides the content that toolbar acts on, which is usually the point of the step.
  • Keep one placement across a run of steps where the targets allow it. A card that jumps corners on every step costs a re-read each time.

Type: default and primary

type takes default and primary. primary inverts the whole card onto colorPrimary: light solid ink, a white Next button with primary-colored text, and the arrow tinted to match.
  • Pick one per tour and keep it. type set on one step in the middle reads as a bug rather than as emphasis.
  • primary is the right default for an unmasked tour, where the card has no dimmed backdrop separating it from the page.
  • A pale colorPrimary fails twice under primary: the light ink on the card and the primary ink on the white Next button are two different pairs, and both have to be checked.

The buttons, and their wording

The footer draws Previous from the second step on, and Next until the last one, where Next becomes Finish. All three words come from the locale, so a localized app gets them translated without the steps knowing anything about it.
  • nextButtonProps.children and prevButtonProps.children rewrite one step's labels. The ending is where this earns its keep: “Finish” is accurate, “Got it” is friendlier, and “Create your first project” says what happens next.
  • Their onClick runs in addition to the navigation: the step advances, then yours runs. Use it for tracking, not to take the button over.
  • Say what the button does, in the reader's words. A Next that promises to do something will be believed, and the only thing it does is show the next card.
  • actionsRender(originNode, { current, total }) is the way to add a Skip beside the defaults. It hands you the default buttons, so you add to them rather than rebuild the pair.
  • indicatorsRender(current, total) replaces the row of dots. Past about four steps the dots stop being countable, and “3 of 7” is the better answer.

Showing it once, and remembering

open plus onClose make it controlled, and it should be. A <Tour> rendered with neither open nor defaultOpen treats itself as open and starts the moment it mounts.
  • Record the dismissal, not the completion. A reader who closes on step two has decided; showing the tour again on their next visit is a worse mistake than never having shown it.
  • Persist the flag against the reader rather than against the browser. A per-user field survives a new laptop and a second device. localStorage does not.
  • onClose fires with the step index the reader was on, from the close button and from Escape alike. That index is the useful piece of telemetry: it says where the tour lost them.
  • onFinish is not an alternative to onClose. Finishing calls the close path first and then onFinish, so a handler that counts dismissals will count completions too unless you separate them.
  • Reset current when you reopen. If you control current, set it back to 0 on open, or the second run starts on the step the reader left.
  • Leave a way back in. A “Show me around” button in the page header costs one line and means dismissing the tour is not permanent.