# Tour Creates guided tours of a user interface. ## Import ```tsx import { Tour, TourStep } from '@coinbase/cds-web/tour' ``` ## Examples The Tour component guides users through your app with step-by-step coachmarks. You define tour steps with unique IDs and wrap target elements with `TourStep` components. ### Basic usage ```jsx live function Example() { const [activeTourStep, setActiveTourStep] = useState(null); const StepOne = () => { const [checked, setChecked] = useState(false); const { goNextTourStep, stopTour } = useTourContext(); return ( Next } checkbox={ Don't show again } content="Add up to 3 lines of body copy. Deliver your message with clarity and impact" onClose={stopTour} title="My first step" /> ); }; const StepTwo = () => { const { goNextTourStep, goPreviousTourStep, stopTour } = useTourContext(); return ( } content={ 50% Add up to 3 lines of body copy. Deliver your message with clarity and impact } media={} onClose={stopTour} title="My second step" /> ); }; const StepThree = () => { const { stopTour, goPreviousTourStep } = useTourContext(); return ( } content="Add up to 3 lines of body copy. Deliver your message with clarity and impact" title="My third step" width={350} /> ); }; const tourSteps = [ { id: 'step1', onBeforeActive: () => console.log('step1 before'), Component: StepOne, }, { id: 'step2', onBeforeActive: () => console.log('step2 before'), Component: StepTwo, }, { id: 'step3', onBeforeActive: () => console.log('step3 before'), Component: StepThree, }, ]; const TourExample = ({ spacerWidthIncrement = 0, spacerHeightIncrement = 500 }) => { const { startTour } = useTourContext(); const handleClick = useCallback(() => startTour(), [startTour]); return ( This is step 1 This is step 2 This is step 3 ); }; return ( ); } ``` You can use TypeScript string literal types to ensure type safety for your step IDs. ```tsx type StepId = 'intro' | 'feature-highlight' | 'call-to-action'; function TypeSafeTourExample() { const [activeTourStep, setActiveTourStep] = useState | null>(null); const tourSteps: TourStepValue[] = [ { id: 'intro', Component: IntroStep }, { id: 'feature-highlight', Component: FeatureStep }, { id: 'call-to-action', Component: CTAStep }, ]; return ( {/* TypeScript error if id doesn't match StepId type */} ); } ``` ### Scrolling The Tour component automatically scrolls to bring off-screen targets into view. You can customize this behavior with the `scrollOptions` prop or disable it entirely with `disableAutoScroll`. ```jsx function ScrollingExample() { const [activeTourStep, setActiveTourStep] = useState(null); const tourSteps = [ { id: 'step1', Component: StepOne }, { id: 'step2', // Disable auto-scroll for just this step disableAutoScroll: true, Component: StepTwo, }, { id: 'step3', // Custom scroll options for this step scrollOptions: { behavior: 'smooth', marginX: 50, marginY: 150, }, Component: StepThree, }, ]; return ( ... ); } ``` ### Customization #### Overlay You can hide the dimmed overlay behind the coachmark using the `hideOverlay` prop. This can be set globally on the `Tour` component or per-step. ```jsx live function HideOverlayExample() { const [activeTourStep, setActiveTourStep] = useState(null); const [hideOverlay, setHideOverlay] = useState(false); const StepOne = () => { const { goNextTourStep, stopTour } = useTourContext(); return ( Next } content="This step respects the global hideOverlay setting." onClose={stopTour} title="Step One" /> ); }; const StepTwo = () => { const { stopTour, goPreviousTourStep } = useTourContext(); return ( } content="This step also respects the global hideOverlay setting." title="Step Two" /> ); }; const tourSteps = [ { id: 'step1', Component: StepOne }, { id: 'step2', Component: StepTwo }, ]; const TourContent = () => { const { startTour } = useTourContext(); return ( setHideOverlay((prev) => !prev)}> Hide overlay Step 1 Step 2 ); }; return ( ); } ``` #### Mask Customize the mask (cutout) around the highlighted element with the `tourMaskPadding` and `tourMaskBorderRadius` props. ```jsx ... ``` You can provide a completely custom mask component using the `TourMaskComponent` prop. ```jsx function CustomMaskExample() { const [activeTourStep, setActiveTourStep] = useState(null); const CustomMask = ({ activeTourStepTargetRect, padding = 8, borderRadius = 8 }) => { // Custom mask implementation // activeTourStepTargetRect contains { x, y, width, height } of the target element return ( ); }; return ( ... ); } ``` #### Positioning The Tour component uses `@floating-ui` to position coachmarks relative to their target elements. You can customize positioning with the `tourStepOffset`, `tourStepShift`, and `tourStepAutoPlacement` props. ```jsx function PositioningExample() { const [activeTourStep, setActiveTourStep] = useState(null); const tourSteps = [ { id: 'step1', Component: StepOne }, { id: 'step2', Component: StepTwo }, ]; return ( ... ); } ``` #### Arrow You can customize the arrow that points to the target element by providing a custom `TourStepArrowComponent`. ```jsx function CustomArrowExample() { const [activeTourStep, setActiveTourStep] = useState(null); // Custom arrow component - MUST forward ref const CustomArrow = memo( forwardRef((props, ref) => { return ; }), ); const tourSteps = [ { id: 'step1', Component: StepOne, // Per-step custom arrow ArrowComponent: CustomArrow, // Or just customize the style arrowStyle: { color: 'var(--color-purple60)' }, }, { id: 'step2', Component: StepTwo }, ]; return ( ... ); } ``` #### Portal By default, the Tour uses React portals to render outside the DOM hierarchy. You can disable this behavior if needed. ```jsx ... ``` ### Accessibility Always provide accessibility labels for close buttons using the `closeButtonAccessibilityLabel` prop and ensure coachmarks are navigable. ```jsx function AccessibleTourExample() { const AccessibleStep = () => { const { goNextTourStep, stopTour } = useTourContext(); return ( Next } closeButtonAccessibilityLabel="Close tour and return to main content" content="This coachmark has proper accessibility labels for screen readers." onClose={stopTour} title="Accessible Step" /> ); }; } ``` ### Callbacks Use the `onBeforeActive` callback to perform actions before a step becomes active, such as fetching data, preparing the UI, or custom scrolling logic. ```jsx function CallbacksExample() { const tourSteps = [ { id: 'step1', onBeforeActive: () => { console.log('Step 1 is about to become active'); // Perform any setup needed }, Component: StepOne, }, { id: 'step2', onBeforeActive: async () => { // Async operations are supported await fetchStepData(); console.log('Step 2 data loaded'); }, Component: StepTwo, }, ]; } ``` ## Props | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `activeTourStep` | `TourStepValue \| null` | Yes | `-` | - | | `onChange` | `(tourStep: TourStepValue \| null) => void` | Yes | `-` | - | | `steps` | `TourStepValue[]` | Yes | `-` | - | | `TourMaskComponent` | `TourMaskComponent` | No | `DefaultTourMask` | The Component to render as a tour overlay and mask. | | `TourStepArrowComponent` | `TourStepArrowComponent` | No | `DefaultTourStepArrow` | The default Component to render for each TourStep arrow element. | | `disableAutoScroll` | `boolean` | No | `-` | Disable automatically scrolling to active elements. | | `disablePortal` | `boolean` | No | `-` | - | | `hideOverlay` | `boolean` | No | `false` | Hide overlay when tour is active | | `scrollOptions` | `TourScrollOptions` | No | `{ behavior: 'smooth', marginX: 100, marginY: 100, }` | Controls the scrolling behavior and margins when calling element.scrollTo() to scroll to an active TourStep target. | | `testID` | `string` | No | `-` | Used to locate this element in unit and end-to-end tests. Under the hood, testID translates to data-testid on Web. On Mobile, testID stays the same - testID | | `tourMaskBorderRadius` | `string \| number` | No | `-` | Corner radius for the TourMasks content mask. Uses SVG rect elements rx and ry attributes https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/rx. | | `tourMaskPadding` | `string \| number` | No | `-` | Padding to add around the edges of the TourMasks content mask. | | `tourStepAutoPlacement` | `{ padding?: Padding; rootBoundary?: RootBoundary \| undefined; elementContext?: ElementContext \| undefined; altBoundary?: boolean \| undefined; crossAxis?: boolean \| undefined; alignment?: Alignment \| null \| undefined; autoAlignment?: boolean \| undefined; allowedPlacements?: Placement[] \| undefined; boundary?: Boundary \| undefined; } \| undefined` | No | `24` | Configures @floating-ui autoPlacement options for Tour Step component. See https://floating-ui.com/docs/autoplacement. | | `tourStepOffset` | `number \| Partial<{ mainAxis: number; crossAxis: number; alignmentAxis: number \| null; }> \| Derivable` | No | `24` | Configures @floating-ui offset options for Tour Step component. See https://floating-ui.com/docs/offset. | | `tourStepShift` | `{ padding?: Padding; rootBoundary?: RootBoundary \| undefined; elementContext?: ElementContext \| undefined; altBoundary?: boolean \| undefined; crossAxis?: boolean \| undefined; mainAxis?: boolean \| undefined; limiter?: { fn: (state: MiddlewareState) => Coords; options?: any; } \| undefined; boundary?: Boundary \| undefined; } \| undefined` | No | `-` | Configures @floating-ui shift options for Tour Step component. See https://floating-ui.com/docs/shift. |