Skip to main content
Tour
@coinbase/cds-web@8.34.1
Creates guided tours of a user interface.
Import
import { Tour, TourStep } from '@coinbase/cds-web/tour'
SourceView source codeStorybookView Storybook
Related components
View as Markdown

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.