Skip to main content
Tour
@coinbase/cds-web@9.28.0
Creates guided tours of a user interface.
import { Tour, TourStep } from '@coinbase/cds-web/tour'
Peer dependencies
  • react-dom: ^18.0.0 || ~19.1.2
Related components

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​

Loading...

You can use TypeScript string literal types to ensure type safety for your step IDs.

type StepId = 'intro' | 'feature-highlight' | 'call-to-action';

function TypeSafeTourExample() {
const [activeTourStep, setActiveTourStep] = useState<TourStepValue<StepId> | null>(null);

const tourSteps: TourStepValue<StepId>[] = [
{ id: 'intro', Component: IntroStep },
{ id: 'feature-highlight', Component: FeatureStep },
{ id: 'call-to-action', Component: CTAStep },
];

return (
<Tour activeTourStep={activeTourStep} onChange={setActiveTourStep} steps={tourSteps}>
<TourStep id="intro">
<IntroContent />
</TourStep>
<TourStep id="feature-highlight">
<FeatureContent />
</TourStep>
{/* TypeScript error if id doesn't match StepId type */}
<TourStep id="call-to-action">
<CTAContent />
</TourStep>
</Tour>
);
}

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.

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 (
<Tour
activeTourStep={activeTourStep}
onChange={setActiveTourStep}
steps={tourSteps}
// Global scroll options
scrollOptions={{
behavior: 'smooth',
marginX: 100,
marginY: 100,
}}
>
...
</Tour>
);
}

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.

Loading...

Mask​

Customize the mask (cutout) around the highlighted element with the tourMaskPadding and tourMaskBorderRadius props.

<Tour
activeTourStep={activeTourStep}
onChange={setActiveTourStep}
steps={tourSteps}
// Padding around the highlighted element (default: 8)
tourMaskPadding={16}
// Border radius of the cutout (default: 8)
tourMaskBorderRadius={12}
>
...
</Tour>

You can provide a completely custom mask component using the TourMaskComponent prop.

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 (
<svg
style={{
position: 'fixed',
top: 0,
left: 0,
width: '100vw',
height: '100vh',
pointerEvents: 'none',
}}
>
<defs>
<mask id="tour-mask">
<rect fill="white" height="100%" width="100%" />
<rect
fill="black"
height={activeTourStepTargetRect.height + padding * 2}
rx={borderRadius}
ry={borderRadius}
width={activeTourStepTargetRect.width + padding * 2}
x={activeTourStepTargetRect.x - padding}
y={activeTourStepTargetRect.y - padding}
/>
</mask>
</defs>
<rect fill="rgba(0,0,0,0.5)" height="100%" mask="url(#tour-mask)" width="100%" />
</svg>
);
};

return (
<Tour
activeTourStep={activeTourStep}
onChange={setActiveTourStep}
steps={tourSteps}
TourMaskComponent={CustomMask}
>
...
</Tour>
);
}

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.

function PositioningExample() {
const [activeTourStep, setActiveTourStep] = useState(null);

const tourSteps = [
{ id: 'step1', Component: StepOne },
{ id: 'step2', Component: StepTwo },
];

return (
<Tour
activeTourStep={activeTourStep}
onChange={setActiveTourStep}
steps={tourSteps}
// Distance between coachmark and target (default: 24)
tourStepOffset={32}
// Padding when shifting to stay in viewport (default: { padding: 32 })
tourStepShift={{ padding: 16 }}
// Auto-placement options from @floating-ui
tourStepAutoPlacement={{ allowedPlacements: ['top', 'bottom'] }}
>
...
</Tour>
);
}

Arrow​

You can customize the arrow that points to the target element by providing a custom TourStepArrowComponent.

function CustomArrowExample() {
const [activeTourStep, setActiveTourStep] = useState(null);

// Custom arrow component - MUST forward ref
const CustomArrow = memo(
forwardRef((props, ref) => {
return <DefaultTourStepArrow {...props} ref={ref} style={{ color: 'var(--color-blue60)' }} />;
}),
);

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 (
<Tour
activeTourStep={activeTourStep}
onChange={setActiveTourStep}
steps={tourSteps}
TourStepArrowComponent={CustomArrow}
>
...
</Tour>
);
}

Portal​

By default, the Tour uses React portals to render outside the DOM hierarchy. You can disable this behavior if needed.

<Tour
...
disablePortal
>
...
</Tour>

Accessibility​

Always provide accessibility labels for close buttons using the closeButtonAccessibilityLabel prop and ensure coachmarks are navigable.

function AccessibleTourExample() {
const AccessibleStep = () => {
const { goNextTourStep, stopTour } = useTourContext();

return (
<Coachmark
action={
<Button
aria-label="Advances to the next step in the tour"
compact
onClick={goNextTourStep}
>
Next
</Button>
}
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.

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,
},
];
}

Is this page useful?

Coinbase Design is an open-source, adaptable system of guidelines, components, and tools that aid the best practices of user interface design for crypto products.