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.

Accessibility

What Tour does for you, and what it leaves to the page around it.

What it renders, and what it does not

The step card looks like a modal dialog and is not one. It is a plain <div> inside a portal, with no role="dialog", no aria-modal and no accessible name. Nothing announces that a tour started.
  • The close button is real and is the only ARIA in the component: a <button type="button"> carrying aria-label from the locale (“Close” in en_US), so it translates with the rest of the app.
  • Previous, Next and Finish are real buttons with visible text, also from the locale. They need nothing added.
  • The indicators are empty <span>s. The dots carry no text at all, so “step 3 of 7” exists for sighted readers only. Put the position in the title, or render it as text through indicatorsRender, if it matters.
  • Because the panel is not named, the fastest fix is the title: write step titles that make sense read alone, since that is how they arrive.

Focus is not moved, trapped or restored

Tour never moves focus. It stays wherever it was when the tour opened, does not follow the card between steps, and is not returned anywhere on close.
  • On open, a keyboard reader is still on the button they pressed. The panel is portalled to the end of document.body, so reaching it means tabbing through everything else on the page first.
  • Between steps, the card is re-rendered in place. Focus that was on Next survives because the button is the same element, but nothing announces the new title, so the card changes silently.
  • On close, focus lands nowhere in particular. Move it yourself in onClose, back to the control that started the tour, or to the first element the tour was talking about.
  • If a tour is important enough to dim the page for, give the panel a name and the focus to match: move focus into it on open and mark it up as a dialog through renderPanel, which then renders the whole card. Escape already closes, the one dialog behavior Tour has.

Keyboard

keyboard defaults to true and turns on two things: Escape closes the tour, and the arrow keys walk it.
  • Escape closes through the portal's own stacking logic, so a tour opened over a modal closes the top layer rather than both. It calls the same path as the close button, which is why onClose receives the current index either way.
  • ArrowLeft and ArrowRight move between steps wherever focus is, not only inside the card, so the tour can be walked before anyone reaches the buttons.
  • The arrow handler steps aside for editable targets, so arrowing through text in an input the tour is pointing at does not advance the tour.
  • keyboard={false} removes both at once, Escape included. Turning it off to protect an arrow-key interaction on the page costs the reader the escape hatch as well.

The mask does not make the page inert

The overlay only blocks pointer events. Nothing is set inert, nothing is hidden with aria-hidden, and no focus is trapped.
  • Tab still walks the page behind the mask. A keyboard or screen reader user moves through controls they cannot click, which is a worse state than either fully modal or fully non-modal.
  • The cut-out stays clickable, so the highlighted control can be used mid-step. disabledInteraction makes the whole overlay block clicks, cut-out included.
  • Page scroll is locked while the tour is open, so a reader who cannot see the card also cannot scroll to find out what changed.
  • Treat the mask as decoration for assistive technology, and put everything the step means into the title and description. A highlight nobody can see says nothing.

A tour that cannot be skipped is a trap

closable={false} does more than remove the ✕. It also turns off Escape, so the reader loses the keyboard exit too.
  • What is left is the Next button, step by step, to the end. For a reader using a screen reader, on a card with no name and no focus moved to it, that is a page they cannot use and cannot leave.
  • If the tour must be completed, make skipping it explicit rather than impossible: add a Skip through actionsRender, which hands you the default buttons to add to.
  • Never gate a task on finishing a tour. The tour explains the screen, and the screen is what the reader came for.

Contrast and motion

The card is painted from theme tokens, so contrast is a property of the theme. Two pairs on this component are easy to miss, and both come from type="primary".
  • Primary is two pairs, not one. Light solid ink on colorPrimary for the card, and colorPrimary ink on white for the Next button. A pale seed passes neither, and the ghost Previous button sits on a faint white fill, which is thinner still.
  • The mask darkens the page, not the card. Its default fill is rgba(0,0,0,0.5). A retint through mask={{ color }} is a new background for anything showing through the hole, so check the highlighted control against it, not against the page.
  • The cut-out animates as it moves, sliding across the screen between steps. The animated prop does not turn this off.
  • Turn it off in the theme: motion: false sets the duration tokens to 0s, which stops the slide along with every other transition. Wire it to the reader's own reduced-motion preference rather than shipping it on.