Skip to content
usefy

SpotlightTour

ComponentsProduct tours

React onboarding tours with a tracking spotlight overlay and enterprise a11y

Live demo

Install

$ npm install @usefy/spotlight-tour

Quick start

SpotlightTour.tsx
import { SpotlightTour } from "@usefy/spotlight-tour";

function App() {
  return (
    <>
      <YourApp />
      <SpotlightTour
        defaultOpen
        steps={[
          // No target → a centered welcome modal.
          { title: "Welcome! 👋", content: "Let's take a quick look around." },
          { target: "#search", title: "Search", content: "Find anything from here." },
          { target: "#settings", content: "Tune your preferences.", placement: "left" },
        ]}
        onFinish={() => console.log("toured!")}
        onSkip={(at) => console.log(`dismissed at step ${at}`)}
      />
    </>
  );
}

API reference

<SpotlightTour /> Props

All useSpotlightTour options (steps, open/defaultOpen, step/defaultStep, tourId, keyboard, scrollLock, onFinish, onSkip, …) plus:

PropTypeDefaultDescription
maskColorstring"rgba(0 0 0 / 0.55)"Dim overlay color.
transitionDurationnumber300Spotlight/tooltip transition (ms). 0 disables; forced to 0 under reduced motion.
overlayClick"ignore" | "close" | "next""ignore"What a click on the dimmed area does ("close" fires onSkip). Per-step disableOverlayClose downgrades to "ignore".
zIndexnumber1000z-index of the portal layer.
labelsPartial<TourLabels>EnglishButton/hint label overrides (i18n): back, next, skip, finish, close, gatedHint.
showProgress / showCounter / showSkip / showClosebooleantrueToggle the dots, "2 / 5" counter, Skip button, and close (×).
renderStep(ctx) => ReactNodeReplace the whole tooltip UI; positioning stays automatic. Spread ctx.tooltipProps for the dialog semantics.
theme"light" | "dark" | "system""system"Color theme; "system" follows prefers-color-scheme.
classNamesSpotlightTourClassNamesPer-part class overrides: overlay, spotlight, tooltip, arrow, header, content, footer, dots, counter.
classNamestringExtra class on the portal root.
controllerRefRef<TourController>Imperative access: start(at?), next, prev, goTo, skip, finish.

TourStep

FieldTypeDefaultDescription
targetstring | RefObject | () => Element | nullElement to spotlight. Omit for a centered modal step.
title / contentReactNodeTooltip heading / body (content required).
placement"top" | "bottom" | "left" | "right" | "auto""auto"Preferred tooltip side; "auto" prefers a side that fits, then the roomiest.
spotlightPadding / spotlightRadiusnumber8 / 8Hole padding / corner radius (px).
missingTarget"skip" | "wait" | "center""skip"Policy when the target can't be resolved.
waitTimeoutnumber3000How long "wait" holds before skipping (ms).
advanceOn{ event: "click"; selector?: string }Interaction gate — see the recipe.
spotlightClicksbooleantrueWhether clicks pass through the hole to the target.
scrollIntoViewbooleantrueAuto-scroll an off-screen target into view.
disableOverlayClosebooleanfalseMake overlayClick behave as "ignore" for this step.
onEnter / onLeave() => voidFire when the step settles on screen / is left.

<SpotlightBeacon /> Props

PropTypeDefaultDescription
targetTourTargetElement the pulsing dot pins to (top-right corner, tracks live).
onActivate() => voidCalled on click — typically controller.current?.start().
aria-labelstring"Start tour"Accessible name of the beacon button.
classNamestringExtra class on the beacon button.

Renders nothing while the target is unresolved or invisible; static (no pulse) under reduced motion.


Go deeper

This page is the quick reference. For every example, prop, and edge case, read the full README — or open Storybook to change props live.

More in components