OnboardingTour

<OnboardingTour> displays a sequence of coachmark tooltips that highlight specific DOM elements, guiding first-time users through a feature. Completion state is persisted in ra-core's store (localStorage by default), so the tour only runs once per user.

Installation

pnpm dlx shadcn@latest add @shadmin/onboarding-tour

Usage

import { OnboardingTour } from "@/components/admin";
 
const DashboardTour = () => (
  <OnboardingTour
    id="dashboard-first-run"
    steps={[
      {
        target: '[data-tour="sidebar"]',
        title: "Resources",
        content: "Click any item to navigate.",
      },
      {
        target: '[data-tour="search"]',
        title: "Quick search",
        content: "Press cmd+K to open the palette.",
      },
      {
        target: '[data-tour="theme"]',
        title: "Theme",
        content: "Switch between light and dark.",
      },
    ]}
  />
);

Add data-tour attributes to the elements you want to highlight:

<button data-tour="search">Search</button>

Place <OnboardingTour> anywhere in the tree that has access to the ra-core context (inside <Admin> or wrapped in <CoreAdminContext>).

Props

PropRequiredTypeDefaultDescription
idRequiredstringStore key suffix. Tours with the same id share completion state.
stepsRequiredTourStep[]Ordered list of coachmark steps.
autoStartOptionalbooleantrueStart automatically on mount if not yet completed.
onCompleteOptional() => voidCalled when the user finishes or skips the tour.
placementOptional"auto" | "top" | "bottom" | "left" | "right""auto"Default tooltip placement for all steps. Step-level placement overrides this.

TourStep

interface TourStep {
  target: string; // CSS selector for the element to highlight
  title?: ReactNode; // Optional heading shown above the content
  content: ReactNode; // Main body text or JSX
  placement?: "top" | "bottom" | "left" | "right"; // Overrides component-level placement
}

id

The id prop is used to construct the store key tour.<id>.completed. Completing the tour (via Finish or Skip) stores true at that key, preventing the tour from re-appearing on future page loads.

To reset the tour for testing, clear the store entry:

import { useStore } from "ra-core";
 
const ResetButton = () => {
  const [, setCompleted] = useStore(
    "tour.dashboard-first-run.completed",
    false,
  );
  return <button onClick={() => setCompleted(false)}>Reset tour</button>;
};

placement

When placement is "auto" (the default), individual steps without a placement property default to "bottom". Set a component-level placement to change the default for all steps, then override per-step as needed:

<OnboardingTour
  id="my-tour"
  placement="top"
  steps={[
    { target: ".foo", content: "Appears above .foo" },
    {
      target: ".bar",
      content: "Appears to the right of .bar",
      placement: "right",
    },
  ]}
/>

Behaviour

  • Spotlight: A semi-transparent overlay covers the page with a cutout around the target element. Clicking the overlay dismisses the tour (same as Skip).
  • Highlight ring: A primary-color ring is rendered around the target's bounding box.
  • Navigation: Back / Next buttons move between steps. The last step shows Finish instead of Next.
  • Missing target: If the CSS selector matches no element, the tooltip is hidden but the overlay stays visible. The tour can still be skipped.
  • Resize/scroll: Target position updates every 250 ms and on resize/scroll events.