Tour / Coachmarks
Preview: Default
Guided product tour: a popover anchored to each step's target element, with a highlight ring on the target.
Import
import { Tour } from '@marbleui/ui'
import { useTourRefs } from '@marbleui/ui'Props
<Tour>
| Prop | Type | Default | Description |
|---|---|---|---|
| isOpen *required | boolean | — | |
| onOpenChange *required | (open: boolean) => void | — | |
| steps *required | TourStep[] | — | |
| onFinish | () => void | — |
Hooks
useTourRefs<K extends string>(...keys: K[]): Record<K, RefObject<HTMLElement | null>>Accessibility
Keyboard interaction, focus management and ARIA semantics come from the underlying primitives: React Aria Dialog · React Aria Heading · React Aria Popover. Follow the primitive's docs for the full keyboard map.
Usage notes
Guided product tour: a popover anchored to each step’s target element with a highlight ring. Use for first-run onboarding; for a one-off explanation use Popover or ToggleTip.
const r = useTourRefs('search', 'new')
<Button ref={r.new} className={tourTarget}>New</Button>
<Tour
isOpen={open}
onOpenChange={setOpen}
onFinish={markTourSeen}
steps={[{ target: r.new, title: 'Create', body: 'Start a new issue.' }]}
/>
- Controlled only: you own
isOpenandonOpenChange. The tour restarts at step 1 whenever it opens. - Each step needs a
targetref; addtourTargetto the target’sclassNameto show the ring. Targets must accept a ref, so wrap components that don’t in aspan. placementper step defaults tobottom.onFinishfires only when Done is pressed on the last step, not when closed early.- The popover is non-modal and has a close button, Back and Next.
Example source · show· hide
import { useState } from 'react'
import { Button } from '../button'
import { SearchField } from '../search-field'
import { Tour, tourTarget, useTourRefs } from './tour'
function Demo() {
const [open, setOpen] = useState(false)
const r = useTourRefs('search', 'new', 'help')
return (
<div className="flex flex-col items-center gap-6">
<div className="flex w-[30rem] items-center gap-2 rounded-xl border border-border bg-surface p-2">
<div ref={r.search as React.RefObject<HTMLDivElement>} className={`flex-1 rounded-md ${tourTarget}`}>
<SearchField aria-label="Search" variant="filled" />
</div>
<span ref={r.new} className={`rounded-md ${tourTarget}`}><Button>New</Button></span>
<span ref={r.help} className={`rounded-md ${tourTarget}`}><Button variant="ghost">Help</Button></span>
</div>
<Button variant="soft" onPress={() => setOpen(true)}>Start tour</Button>
<Tour
isOpen={open}
onOpenChange={setOpen}
steps={[
{ target: r.search, title: 'Search everything', body: 'Find issues, docs and people. Press ⌘K from anywhere.' },
{ target: r.new, title: 'Create', body: 'Start a new issue, doc or project.' },
{ target: r.help, title: 'Need a hand?', body: 'Guides, shortcuts and support live here.', placement: 'top' },
]}
/>
</div>
)
}
export const styles = { Default: Demo }Source: packages/ui/src/tour/tour.tsx
Catalog
- Category
- overlays
- Priority
- Core
- Primitive
- composed
- React Aria
- Popover
- Base UI
- Popover