Tour
A step-by-step guided tour component that highlights elements with a spotlight overlay and positions a popover with navigation controls.
Installation
npx vayu-ui-cli@latest add tourUsage
Tour Demo
Feature A
Feature B
import { Tour, type TourStep, Typography, Button, Divider } from 'vayu-ui';
const steps: TourStep[] = [
{
target: '#tour-title',
title: 'Welcome!',
content: 'This is the Tour component. It highlights elements on the page and guides the user step-by-step.',
placement: 'bottom',
},
{
target: '#tour-card-1',
title: 'Feature Cards',
content: 'Each card represents a feature. The spotlight draws attention to the relevant area.',
placement: 'right',
},
{
target: '#tour-card-2',
title: 'Second Card',
content: 'Navigate with arrow keys, or use the Previous / Next buttons.',
placement: 'left',
},
{
target: '#tour-cta',
title: 'Call to Action',
content: 'Click Finish to complete the tour. You can also press Escape or click Skip at any time.',
placement: 'top',
},
];
export function Example() {
const [open, setOpen] = useState(false);
return (
<Tour
steps={steps}
isOpen={open}
onClose={() => setOpen(false)}
onComplete={() => setOpen(false)}
onSkip={() => setOpen(false)}
>
<div className="flex flex-col gap-4">
<Typography.H3 id="tour-title">Tour Demo</Typography.H3>
<div className="grid grid-cols-2 gap-3">
<div id="tour-card-1" className="p-4 rounded-surface border border-border bg-surface">
<Typography.P>Feature A</Typography.P>
</div>
<div id="tour-card-2" className="p-4 rounded-surface border border-border bg-surface">
<Typography.P>Feature B</Typography.P>
</div>
</div>
<Divider spacing="sm" decorative />
<Button
id="tour-cta"
onClick={() => setOpen(true)}
variant="primary"
size="medium"
>
<Button.Text>Start Tour</Button.Text>
</Button>
</div>
</Tour>
);
}Anatomy
<Tour
steps={steps}
isOpen={open}
onClose={() => setOpen(false)}
onComplete={() => setOpen(false)}
onSkip={() => setOpen(false)}
showProgress={true}
showStepNumbers={true}
maskClickable={false}
closeOnEscape={true}
closeOnMaskClick={false}
scrollBehavior="smooth"
highlightedAreaClassName="custom-spotlight"
maskClassName="custom-mask"
>
{/* Your application content — target elements identified by CSS selectors */}
</Tour>Accessibility
- Keyboard navigation:
ArrowRightadvances to the next step,ArrowLeftgoes to the previous step,Escapecloses the tour (whencloseOnEscapeis true) - ARIA attributes:
role="dialog"witharia-modal="true"on the popover,aria-labelledbyreferencing the dialog title,role="progressbar"witharia-valuenow,aria-valuemin,aria-valuemax, andaria-labelon the progress bar - Focus management: Body scroll is locked during the tour; the target element is scrolled into view with
block: 'center'andinline: 'center' - Screen reader live region: A visually hidden
divwitharia-live="polite"andaria-atomic="true"announces step changes
Screen reader behavior
When a tour step changes, the live region announces: "Step N of M: [step title]". The popover is identified as a dialog via role="dialog" and aria-modal="true", informing screen readers that content outside the tour is temporarily inert. The progress bar conveys completion percentage through role="progressbar" with current, minimum, and maximum values. All navigation buttons include descriptive aria-label attributes for context.
Component Folder Structure
Tour/
├── Tour.tsx # Root component with TourContext, state management, keyboard handling
├── TourOverlay.tsx # SVG mask overlay with spotlight cutout
├── TourPopover.tsx # Dialog popover with arrow, header, body, progress, footer
├── use-position.ts # Popover position calculation with viewport clamping
├── use-target.ts # Target element finding, MutationObserver, scroll/resize handling
├── hooks.ts # useTour context consumer hook
├── types.ts # TypeScript interfaces (TourStep, TourContextValue, TourProps)
├── index.ts # Public exports
└── README.md # Internal documentationProps
Tour
| Prop | Type | Default | Description |
|---|---|---|---|
steps | TourStep[] | — | Array of tour step definitions |
isOpen | boolean | false | Controlled open state |
onClose | () => void | — | Callback fired when the tour closes |
onComplete | () => void | — | Callback fired when the last step completes |
onSkip | () => void | — | Callback fired when the user skips |
showProgress | boolean | true | Show progress bar |
showStepNumbers | boolean | true | Show step counter badge |
maskClickable | boolean | false | Allow pointer events through overlay |
closeOnEscape | boolean | true | Close on Escape key |
closeOnMaskClick | boolean | false | Close when clicking the overlay |
scrollBehavior | ScrollBehavior | "smooth" | Scroll-into-view behavior |
highlightedAreaClassName | string | — | Class name for the spotlight border |
maskClassName | string | — | Class name for the overlay mask |
TourStep
| Prop | Type | Default | Description |
|---|---|---|---|
target | string | — | CSS selector for the target element |
title | string | — | Step heading |
content | string | ReactNode | — | Step body |
placement | 'top' | 'bottom' | 'left' | 'right' | 'center' | "bottom" | Popover placement |
spotlightPadding | number | 8 | Spotlight cutout padding |
disableInteraction | boolean | false | Block pointer events on target |
showSkip | boolean | true | Show skip button for this step |
nextButtonText | string | "Next" | Override next button label |
prevButtonText | string | "Previous" | Override previous button label |
onNext | () => void | Promise<void> | — | Async hook before advancing |
onPrev | () => void | Promise<void> | — | Async hook before going back |
customButtons | ReactNode | — | Replace the default button row |
useTour Hook
const { isOpen, currentStep, steps, goToStep, nextStep, prevStep, close, skip } = useTour();Access tour state and controls from any component rendered inside <Tour>. Throws an error if used outside the Tour provider.