# Tour

URL: https://ark-ui.com/docs/components/tour
LLM: https://ark-ui.com/llms.txt/components/tour

A guided tour that helps users understand the interface.

---



## Anatomy



```tsx
const tour = useTour({ steps: [...] })

<Tour.Root tour={tour}>
  <Tour.Backdrop />
  <Tour.Spotlight />
  <Tour.Positioner>
    <Tour.Content>
      <Tour.Arrow>
        <Tour.ArrowTip />
      </Tour.Arrow>
      <Tour.Title />
      <Tour.Description />
      <Tour.ProgressText />
      <Tour.CloseTrigger />
      <Tour.Actions>
        <Tour.ActionTrigger />
      </Tour.Actions>
    </Tour.Content>
  </Tour.Positioner>
</Tour.Root>
```

## Examples

```tsx
import { Portal } from '@ark-ui/react/portal'
import { Tour, useTour } from '@ark-ui/react/tour'
import { MoreHorizontalIcon, SaveIcon, SparklesIcon, UploadIcon, XIcon } from 'lucide-react'
import button from 'styles/button.module.css'
import styles from 'styles/tour.module.css'

const steps: Tour.StepDetails[] = [
  {
    id: 'welcome',
    type: 'dialog',
    title: 'Welcome to the App!',
    description: "Let's take a quick tour to get you started with the main features.",
    actions: [{ label: 'Start Tour', action: 'next' }],
  },
  {
    id: 'upload',
    type: 'tooltip',
    title: 'Upload Files',
    description: 'Click here to upload your files to the cloud.',
    target: () => document.querySelector<HTMLElement>('#btn-upload'),
    actions: [
      { label: 'Back', action: 'prev' },
      { label: 'Next', action: 'next' },
    ],
  },
  {
    id: 'save',
    type: 'tooltip',
    title: 'Save Changes',
    description: 'Save your work to keep your progress.',
    target: () => document.querySelector<HTMLElement>('#btn-save'),
    actions: [
      { label: 'Back', action: 'prev' },
      { label: 'Next', action: 'next' },
    ],
  },
  {
    id: 'more',
    type: 'tooltip',
    title: 'More Options',
    description: 'Access additional settings and actions from this menu.',
    target: () => document.querySelector<HTMLElement>('#btn-more'),
    actions: [
      { label: 'Back', action: 'prev' },
      { label: 'Next', action: 'next' },
    ],
  },
  {
    id: 'complete',
    type: 'dialog',
    title: "You're all set!",
    description: 'You now know the basics. Enjoy using the app!',
    actions: [{ label: 'Finish', action: 'dismiss' }],
  },
]

export const Basic = () => {
  const tour = useTour({ steps })

  return (
    <div className={styles.Root}>
      <button type="button" data-variant="surface" className={button.Root} onClick={() => tour.start()}>
        <SparklesIcon /> Start Tour
      </button>

      <div className={styles.ActionButtons}>
        <button id="btn-upload" type="button" className={button.Root}>
          <UploadIcon /> Upload
        </button>
        <button id="btn-save" type="button" className={button.Root}>
          <SaveIcon /> Save
        </button>
        <button id="btn-more" type="button" className={button.Root}>
          <MoreHorizontalIcon /> More
        </button>
      </div>

      <Tour.Root tour={tour}>
        <Portal>
          <Tour.Backdrop className={styles.Backdrop} />
          <Tour.Spotlight className={styles.Spotlight} />
          <Tour.Positioner className={styles.Positioner}>
            <Tour.Content className={styles.Content}>
              <Tour.Arrow className={styles.Arrow}>
                <Tour.ArrowTip className={styles.ArrowTip} />
              </Tour.Arrow>
              <Tour.CloseTrigger className={styles.CloseTrigger}>
                <XIcon />
              </Tour.CloseTrigger>
              <Tour.ProgressText className={styles.ProgressText} />
              <Tour.Title className={styles.Title} />
              <Tour.Description className={styles.Description} />
              <Tour.Control className={styles.Control}>
                <Tour.Actions>
                  {(actions) =>
                    actions.map((action) => (
                      <Tour.ActionTrigger className={styles.ActionTrigger} key={action.label} action={action} />
                    ))
                  }
                </Tour.Actions>
              </Tour.Control>
            </Tour.Content>
          </Tour.Positioner>
        </Portal>
      </Tour.Root>
    </div>
  )
}
```

### Step Types

Demonstrate all three step types in a single tour: `dialog` for welcome/completion, `tooltip` anchored to elements, and
`floating` for fixed-position content.

```tsx
import { Portal } from '@ark-ui/react/portal'
import { Tour, useTour } from '@ark-ui/react/tour'
import { SparklesIcon, XIcon } from 'lucide-react'
import button from 'styles/button.module.css'
import styles from 'styles/tour.module.css'

const steps: Tour.StepDetails[] = [
  {
    id: 'welcome',
    type: 'dialog',
    title: 'Welcome!',
    description: 'This tour demonstrates different step types: dialog, tooltip, and floating.',
    actions: [{ label: 'Start Tour', action: 'next' }],
  },
  {
    id: 'tooltip-step',
    type: 'tooltip',
    title: 'Tooltip Step',
    description: 'This step appears as a tooltip anchored to a specific element.',
    target: () => document.querySelector<HTMLElement>('#target-element'),
    actions: [
      { label: 'Back', action: 'prev' },
      { label: 'Next', action: 'next' },
    ],
  },
  {
    id: 'floating-step',
    type: 'floating',
    placement: 'bottom-end',
    title: 'Floating Step',
    description: 'This step floats at a fixed position on the screen, independent of any target.',
    actions: [
      { label: 'Back', action: 'prev' },
      { label: 'Next', action: 'next' },
    ],
  },
  {
    id: 'complete',
    type: 'dialog',
    title: 'Tour Complete!',
    description: 'You have seen all the different step types available.',
    actions: [{ label: 'Done', action: 'dismiss' }],
  },
]

export const MixedTypes = () => {
  const tour = useTour({ steps })

  return (
    <div className={styles.Root}>
      <button type="button" data-variant="surface" className={button.Root} onClick={() => tour.start()}>
        <SparklesIcon /> Start Tour
      </button>

      <div id="target-element" className={styles.Target}>
        Target Element
      </div>

      <Tour.Root tour={tour}>
        <Portal>
          <Tour.Backdrop className={styles.Backdrop} />
          <Tour.Spotlight className={styles.Spotlight} />
          <Tour.Positioner className={styles.Positioner}>
            <Tour.Content className={styles.Content}>
              <Tour.Arrow className={styles.Arrow}>
                <Tour.ArrowTip className={styles.ArrowTip} />
              </Tour.Arrow>
              <Tour.CloseTrigger className={styles.CloseTrigger}>
                <XIcon />
              </Tour.CloseTrigger>
              <Tour.ProgressText className={styles.ProgressText} />
              <Tour.Title className={styles.Title} />
              <Tour.Description className={styles.Description} />
              <Tour.Control className={styles.Control}>
                <Tour.Actions>
                  {(actions) =>
                    actions.map((action) => (
                      <Tour.ActionTrigger className={styles.ActionTrigger} key={action.label} action={action} />
                    ))
                  }
                </Tour.Actions>
              </Tour.Control>
            </Tour.Content>
          </Tour.Positioner>
        </Portal>
      </Tour.Root>
    </div>
  )
}
```

### Progress

Display a visual progress indicator at the bottom of the tour content showing how far along the user is.

```tsx
import { Portal } from '@ark-ui/react/portal'
import { Tour, useTour } from '@ark-ui/react/tour'
import { SparklesIcon, XIcon } from 'lucide-react'
import button from 'styles/button.module.css'
import styles from 'styles/tour.module.css'

const steps: Tour.StepDetails[] = [
  {
    id: 'step-1',
    type: 'tooltip',
    title: 'Progress Tracking',
    description: 'Watch the progress bar at the bottom as you navigate.',
    target: () => document.querySelector<HTMLElement>('#progress-1'),
    actions: [{ label: 'Next', action: 'next' }],
  },
  {
    id: 'step-2',
    type: 'tooltip',
    title: 'Halfway There',
    description: 'The progress bar shows how far along you are.',
    target: () => document.querySelector<HTMLElement>('#progress-2'),
    actions: [
      { label: 'Back', action: 'prev' },
      { label: 'Next', action: 'next' },
    ],
  },
  {
    id: 'step-3',
    type: 'tooltip',
    title: 'Almost Done',
    description: 'One more step to complete the tour.',
    target: () => document.querySelector<HTMLElement>('#progress-3'),
    actions: [
      { label: 'Back', action: 'prev' },
      { label: 'Next', action: 'next' },
    ],
  },
  {
    id: 'step-4',
    type: 'tooltip',
    title: 'Complete!',
    description: 'You have completed all the steps.',
    target: () => document.querySelector<HTMLElement>('#progress-4'),
    actions: [
      { label: 'Back', action: 'prev' },
      { label: 'Finish', action: 'dismiss' },
    ],
  },
]

export const ProgressBar = () => {
  const tour = useTour({ steps })

  return (
    <div className={styles.Root}>
      <button type="button" data-variant="surface" className={button.Root} onClick={() => tour.start()}>
        <SparklesIcon /> Start Tour
      </button>

      <div className={styles.ActionButtons}>
        <div id="progress-1" className={styles.Target}>
          Step 1
        </div>
        <div id="progress-2" className={styles.Target}>
          Step 2
        </div>
        <div id="progress-3" className={styles.Target}>
          Step 3
        </div>
        <div id="progress-4" className={styles.Target}>
          Step 4
        </div>
      </div>

      <Tour.Root tour={tour}>
        <Portal>
          <Tour.Backdrop className={styles.Backdrop} />
          <Tour.Spotlight className={styles.Spotlight} />
          <Tour.Positioner className={styles.Positioner}>
            <Tour.Content className={styles.Content}>
              <Tour.Arrow className={styles.Arrow}>
                <Tour.ArrowTip className={styles.ArrowTip} />
              </Tour.Arrow>
              <Tour.CloseTrigger className={styles.CloseTrigger}>
                <XIcon />
              </Tour.CloseTrigger>
              <Tour.ProgressText className={styles.ProgressText} />
              <Tour.Title className={styles.Title} />
              <Tour.Description className={styles.Description} />
              <Tour.Control className={styles.Control}>
                <Tour.Actions>
                  {(actions) =>
                    actions.map((action) => (
                      <Tour.ActionTrigger className={styles.ActionTrigger} key={action.label} action={action} />
                    ))
                  }
                </Tour.Actions>
              </Tour.Control>
              <div className={styles.ProgressBarBottom}>
                <div className={styles.ProgressFill} style={{ width: `${tour.getProgressPercent()}%` }} />
              </div>
            </Tour.Content>
          </Tour.Positioner>
        </Portal>
      </Tour.Root>
    </div>
  )
}
```

### Skip

Allow users to skip the entire tour at any step by adding a skip action.

```tsx
import { Portal } from '@ark-ui/react/portal'
import { Tour, useTour } from '@ark-ui/react/tour'
import { SparklesIcon, XIcon } from 'lucide-react'
import button from 'styles/button.module.css'
import styles from 'styles/tour.module.css'

const steps: Tour.StepDetails[] = [
  {
    id: 'step-1',
    type: 'tooltip',
    title: 'First Feature',
    description: 'You can skip this tour at any time using the Skip button.',
    target: () => document.querySelector<HTMLElement>('#item-1'),
    actions: [
      { label: 'Skip', action: 'dismiss' },
      { label: 'Next', action: 'next' },
    ],
  },
  {
    id: 'step-2',
    type: 'tooltip',
    title: 'Second Feature',
    description: 'Continue or skip to end the tour early.',
    target: () => document.querySelector<HTMLElement>('#item-2'),
    actions: [
      { label: 'Skip', action: 'dismiss' },
      { label: 'Back', action: 'prev' },
      { label: 'Next', action: 'next' },
    ],
  },
  {
    id: 'step-3',
    type: 'tooltip',
    title: 'Final Feature',
    description: 'This is the last step of the tour.',
    target: () => document.querySelector<HTMLElement>('#item-3'),
    actions: [
      { label: 'Back', action: 'prev' },
      { label: 'Finish', action: 'dismiss' },
    ],
  },
]

export const SkipTour = () => {
  const tour = useTour({ steps })

  return (
    <div className={styles.Root}>
      <button type="button" data-variant="surface" className={button.Root} onClick={() => tour.start()}>
        <SparklesIcon /> Start Tour
      </button>

      <div className={styles.ActionButtons}>
        <div id="item-1" className={styles.Target}>
          Item 1
        </div>
        <div id="item-2" className={styles.Target}>
          Item 2
        </div>
        <div id="item-3" className={styles.Target}>
          Item 3
        </div>
      </div>

      <Tour.Root tour={tour}>
        <Portal>
          <Tour.Backdrop className={styles.Backdrop} />
          <Tour.Spotlight className={styles.Spotlight} />
          <Tour.Positioner className={styles.Positioner}>
            <Tour.Content className={styles.Content}>
              <Tour.Arrow className={styles.Arrow}>
                <Tour.ArrowTip className={styles.ArrowTip} />
              </Tour.Arrow>
              <Tour.CloseTrigger className={styles.CloseTrigger}>
                <XIcon />
              </Tour.CloseTrigger>
              <Tour.ProgressText className={styles.ProgressText} />
              <Tour.Title className={styles.Title} />
              <Tour.Description className={styles.Description} />
              <Tour.Control className={styles.Control}>
                <Tour.Actions>
                  {(actions) =>
                    actions.map((action) => (
                      <Tour.ActionTrigger className={styles.ActionTrigger} key={action.label} action={action} />
                    ))
                  }
                </Tour.Actions>
              </Tour.Control>
            </Tour.Content>
          </Tour.Positioner>
        </Portal>
      </Tour.Root>
    </div>
  )
}
```

### Keyboard Navigation

Enable arrow key navigation between tour steps using the `keyboardNavigation` prop.

```tsx
import { Portal } from '@ark-ui/react/portal'
import { Tour, useTour } from '@ark-ui/react/tour'
import { KeyboardIcon, SparklesIcon, XIcon } from 'lucide-react'
import button from 'styles/button.module.css'
import styles from 'styles/tour.module.css'

const steps: Tour.StepDetails[] = [
  {
    id: 'step-1',
    type: 'tooltip',
    title: 'Keyboard Navigation',
    description: 'Press the right arrow key (→) to go to the next step.',
    target: () => document.querySelector<HTMLElement>('#key-1'),
    actions: [{ label: 'Next', action: 'next' }],
  },
  {
    id: 'step-2',
    type: 'tooltip',
    title: 'Go Back',
    description: 'Press the left arrow key (←) to go back.',
    target: () => document.querySelector<HTMLElement>('#key-2'),
    actions: [
      { label: 'Back', action: 'prev' },
      { label: 'Next', action: 'next' },
    ],
  },
  {
    id: 'step-3',
    type: 'tooltip',
    title: 'Close Tour',
    description: 'Press Escape to close the tour at any time.',
    target: () => document.querySelector<HTMLElement>('#key-3'),
    actions: [
      { label: 'Back', action: 'prev' },
      { label: 'Finish', action: 'dismiss' },
    ],
  },
]

export const KeyboardNavigation = () => {
  const tour = useTour({ steps, keyboardNavigation: true })

  return (
    <div className={styles.Root}>
      <button type="button" data-variant="surface" className={button.Root} onClick={() => tour.start()}>
        <SparklesIcon /> Start Tour
      </button>

      <p className={styles.Hint}>
        <KeyboardIcon /> Use arrow keys to navigate, Escape to close
      </p>

      <div className={styles.ActionButtons}>
        <div id="key-1" className={styles.Target}>
          Step 1
        </div>
        <div id="key-2" className={styles.Target}>
          Step 2
        </div>
        <div id="key-3" className={styles.Target}>
          Step 3
        </div>
      </div>

      <Tour.Root tour={tour}>
        <Portal>
          <Tour.Backdrop className={styles.Backdrop} />
          <Tour.Spotlight className={styles.Spotlight} />
          <Tour.Positioner className={styles.Positioner}>
            <Tour.Content className={styles.Content}>
              <Tour.Arrow className={styles.Arrow}>
                <Tour.ArrowTip className={styles.ArrowTip} />
              </Tour.Arrow>
              <Tour.CloseTrigger className={styles.CloseTrigger}>
                <XIcon />
              </Tour.CloseTrigger>
              <Tour.ProgressText className={styles.ProgressText} />
              <Tour.Title className={styles.Title} />
              <Tour.Description className={styles.Description} />
              <Tour.Control className={styles.Control}>
                <Tour.Actions>
                  {(actions) =>
                    actions.map((action) => (
                      <Tour.ActionTrigger className={styles.ActionTrigger} key={action.label} action={action} />
                    ))
                  }
                </Tour.Actions>
              </Tour.Control>
            </Tour.Content>
          </Tour.Positioner>
        </Portal>
      </Tour.Root>
    </div>
  )
}
```

### Events

Listen to tour lifecycle events like `onStepChange` and `onStatusChange` to track user progress.

```tsx
import { Portal } from '@ark-ui/react/portal'
import { Tour, useTour } from '@ark-ui/react/tour'
import { SparklesIcon, XIcon } from 'lucide-react'
import { useState } from 'react'
import button from 'styles/button.module.css'
import styles from 'styles/tour.module.css'

const steps: Tour.StepDetails[] = [
  {
    id: 'step-1',
    type: 'tooltip',
    title: 'First Step',
    description: 'Watch the event log below as you navigate.',
    target: () => document.querySelector<HTMLElement>('#event-1'),
    actions: [{ label: 'Next', action: 'next' }],
  },
  {
    id: 'step-2',
    type: 'tooltip',
    title: 'Second Step',
    description: 'Each step change triggers an event.',
    target: () => document.querySelector<HTMLElement>('#event-2'),
    actions: [
      { label: 'Back', action: 'prev' },
      { label: 'Next', action: 'next' },
    ],
  },
  {
    id: 'step-3',
    type: 'tooltip',
    title: 'Final Step',
    description: 'Complete the tour to see the status change.',
    target: () => document.querySelector<HTMLElement>('#event-3'),
    actions: [
      { label: 'Back', action: 'prev' },
      { label: 'Finish', action: 'dismiss' },
    ],
  },
]

export const Events = () => {
  const [logs, setLogs] = useState<string[]>([])

  const addLog = (message: string) => {
    setLogs((prev) => [...prev, message])
  }

  const tour = useTour({
    steps,
    onStepChange(details) {
      addLog(`Step changed: ${details.stepId}`)
    },
    onStatusChange(details) {
      addLog(`Status: ${details.status}`)
    },
  })

  return (
    <div className={styles.Root}>
      <button type="button" data-variant="surface" className={button.Root} onClick={() => tour.start()}>
        <SparklesIcon /> Start Tour
      </button>

      <div className={styles.ActionButtons}>
        <div id="event-1" className={styles.Target}>
          Step 1
        </div>
        <div id="event-2" className={styles.Target}>
          Step 2
        </div>
        <div id="event-3" className={styles.Target}>
          Step 3
        </div>
      </div>

      <div className={styles.EventLog}>
        <strong>Event Log:</strong>
        {logs.length === 0 ? (
          <div className={styles.EventLogItem}>Start the tour to see events</div>
        ) : (
          logs.map((log, i) => (
            <div key={i} className={styles.EventLogItem}>
              {log}
            </div>
          ))
        )}
      </div>

      <Tour.Root tour={tour}>
        <Portal>
          <Tour.Backdrop className={styles.Backdrop} />
          <Tour.Spotlight className={styles.Spotlight} />
          <Tour.Positioner className={styles.Positioner}>
            <Tour.Content className={styles.Content}>
              <Tour.Arrow className={styles.Arrow}>
                <Tour.ArrowTip className={styles.ArrowTip} />
              </Tour.Arrow>
              <Tour.CloseTrigger className={styles.CloseTrigger}>
                <XIcon />
              </Tour.CloseTrigger>
              <Tour.ProgressText className={styles.ProgressText} />
              <Tour.Title className={styles.Title} />
              <Tour.Description className={styles.Description} />
              <Tour.Control className={styles.Control}>
                <Tour.Actions>
                  {(actions) =>
                    actions.map((action) => (
                      <Tour.ActionTrigger className={styles.ActionTrigger} key={action.label} action={action} />
                    ))
                  }
                </Tour.Actions>
              </Tour.Control>
            </Tour.Content>
          </Tour.Positioner>
        </Portal>
      </Tour.Root>
    </div>
  )
}
```

### Wait for Click

Use the `effect` function with `waitForEvent` to wait for user interaction before proceeding to the next step.

```tsx
import { Portal } from '@ark-ui/react/portal'
import { Tour, useTour, waitForEvent } from '@ark-ui/react/tour'
import { SparklesIcon, XIcon } from 'lucide-react'
import button from 'styles/button.module.css'
import styles from 'styles/tour.module.css'

const steps: Tour.StepDetails[] = [
  {
    id: 'intro',
    type: 'dialog',
    title: 'Interactive Tutorial',
    description: 'This tour will guide you through actions. You must complete each step to proceed.',
    actions: [{ label: 'Begin', action: 'next' }],
  },
  {
    id: 'click-add',
    type: 'tooltip',
    title: 'Click the Add Button',
    description: 'Click the "Add Item" button to continue.',
    target: () => document.querySelector<HTMLElement>('#btn-add'),
    effect({ next, target, show }) {
      show()
      const [promise, cancel] = waitForEvent(target, 'click')
      promise.then(() => next())
      return cancel
    },
  },
  {
    id: 'click-edit',
    type: 'tooltip',
    title: 'Click the Edit Button',
    description: 'Now click the "Edit" button.',
    target: () => document.querySelector<HTMLElement>('#btn-edit'),
    effect({ next, target, show }) {
      show()
      const [promise, cancel] = waitForEvent(target, 'click')
      promise.then(() => next())
      return cancel
    },
  },
  {
    id: 'click-delete',
    type: 'tooltip',
    title: 'Click the Delete Button',
    description: 'Finally, click the "Delete" button.',
    target: () => document.querySelector<HTMLElement>('#btn-delete'),
    effect({ next, target, show }) {
      show()
      const [promise, cancel] = waitForEvent(target, 'click')
      promise.then(() => next())
      return cancel
    },
  },
  {
    id: 'complete',
    type: 'dialog',
    title: 'Well Done!',
    description: 'You completed all the interactive steps.',
    actions: [{ label: 'Finish', action: 'dismiss' }],
  },
]

export const WaitForClick = () => {
  const tour = useTour({ steps })

  return (
    <div className={styles.Root}>
      <button type="button" data-variant="surface" className={button.Root} onClick={() => tour.start()}>
        <SparklesIcon /> Start Interactive Tour
      </button>

      <div className={styles.ActionButtons}>
        <button id="btn-add" type="button" className={button.Root}>
          Add Item
        </button>
        <button id="btn-edit" type="button" className={button.Root}>
          Edit
        </button>
        <button id="btn-delete" type="button" className={button.Root}>
          Delete
        </button>
      </div>

      <Tour.Root tour={tour}>
        <Portal>
          <Tour.Backdrop className={styles.Backdrop} />
          <Tour.Spotlight className={styles.Spotlight} />
          <Tour.Positioner className={styles.Positioner}>
            <Tour.Content className={styles.Content}>
              <Tour.Arrow className={styles.Arrow}>
                <Tour.ArrowTip className={styles.ArrowTip} />
              </Tour.Arrow>
              <Tour.CloseTrigger className={styles.CloseTrigger}>
                <XIcon />
              </Tour.CloseTrigger>
              <Tour.ProgressText className={styles.ProgressText} />
              <Tour.Title className={styles.Title} />
              <Tour.Description className={styles.Description} />
              <Tour.Control className={styles.Control}>
                <Tour.Actions>
                  {(actions) =>
                    actions.map((action) => (
                      <Tour.ActionTrigger className={styles.ActionTrigger} key={action.label} action={action} />
                    ))
                  }
                </Tour.Actions>
              </Tour.Control>
            </Tour.Content>
          </Tour.Positioner>
        </Portal>
      </Tour.Root>
    </div>
  )
}
```

### Wait for Input

Create form tutorials that wait for users to enter valid input before advancing.

```tsx
import { Portal } from '@ark-ui/react/portal'
import { Tour, useTour, waitForEvent } from '@ark-ui/react/tour'
import { SparklesIcon, XIcon } from 'lucide-react'
import button from 'styles/button.module.css'
import styles from 'styles/tour.module.css'

const steps: Tour.StepDetails[] = [
  {
    id: 'intro',
    type: 'dialog',
    title: 'Form Tutorial',
    description: 'Learn how to fill out the form by following the guided steps.',
    actions: [{ label: 'Start', action: 'next' }],
  },
  {
    id: 'enter-name',
    type: 'tooltip',
    title: 'Enter Your Name',
    description: 'Type your name in the input field to continue.',
    target: () => document.querySelector<HTMLInputElement>('#input-name'),
    effect({ next, target, show }) {
      show()
      const [promise, cancel] = waitForEvent<HTMLInputElement>(target, 'input', {
        predicate: (el) => el.value.trim().length >= 2,
      })
      promise.then(() => next())
      return cancel
    },
  },
  {
    id: 'enter-email',
    type: 'tooltip',
    title: 'Enter Your Email',
    description: 'Now enter a valid email address.',
    target: () => document.querySelector<HTMLInputElement>('#input-email'),
    effect({ next, target, show }) {
      show()
      const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
      const [promise, cancel] = waitForEvent<HTMLInputElement>(target, 'input', {
        predicate: (el) => emailRegex.test(el.value),
      })
      promise.then(() => next())
      return cancel
    },
  },
  {
    id: 'check-terms',
    type: 'tooltip',
    title: 'Accept Terms',
    description: 'Check the checkbox to accept the terms.',
    target: () => document.querySelector<HTMLInputElement>('#checkbox-terms'),
    effect({ next, target, show }) {
      show()
      const [promise, cancel] = waitForEvent<HTMLInputElement>(target, 'change', {
        predicate: (el) => el.checked,
      })
      promise.then(() => next())
      return cancel
    },
  },
  {
    id: 'complete',
    type: 'dialog',
    title: 'Form Complete!',
    description: 'You have successfully filled out the form.',
    actions: [{ label: 'Done', action: 'dismiss' }],
  },
]

export const WaitForInput = () => {
  const tour = useTour({ steps })

  return (
    <div className={styles.Root}>
      <button type="button" data-variant="surface" className={button.Root} onClick={() => tour.start()}>
        <SparklesIcon /> Start Form Tutorial
      </button>

      <div className={styles.Form}>
        <div className={styles.FormField}>
          <label htmlFor="input-name">Name</label>
          <input id="input-name" type="text" placeholder="Enter your name" className={styles.Input} />
        </div>
        <div className={styles.FormField}>
          <label htmlFor="input-email">Email</label>
          <input id="input-email" type="email" placeholder="Enter your email" className={styles.Input} />
        </div>
        <div className={styles.FormFieldInline}>
          <input id="checkbox-terms" type="checkbox" className={styles.Checkbox} />
          <label htmlFor="checkbox-terms">I accept the terms and conditions</label>
        </div>
      </div>

      <Tour.Root tour={tour}>
        <Portal>
          <Tour.Backdrop className={styles.Backdrop} />
          <Tour.Spotlight className={styles.Spotlight} />
          <Tour.Positioner className={styles.Positioner}>
            <Tour.Content className={styles.Content}>
              <Tour.Arrow className={styles.Arrow}>
                <Tour.ArrowTip className={styles.ArrowTip} />
              </Tour.Arrow>
              <Tour.CloseTrigger className={styles.CloseTrigger}>
                <XIcon />
              </Tour.CloseTrigger>
              <Tour.ProgressText className={styles.ProgressText} />
              <Tour.Title className={styles.Title} />
              <Tour.Description className={styles.Description} />
              <Tour.Control className={styles.Control}>
                <Tour.Actions>
                  {(actions) =>
                    actions.map((action) => (
                      <Tour.ActionTrigger className={styles.ActionTrigger} key={action.label} action={action} />
                    ))
                  }
                </Tour.Actions>
              </Tour.Control>
            </Tour.Content>
          </Tour.Positioner>
        </Portal>
      </Tour.Root>
    </div>
  )
}
```

### Wait for Element

Wait for dynamically rendered elements to appear in the DOM before showing a step.

```tsx
import { Portal } from '@ark-ui/react/portal'
import { Tour, useTour, waitForElement, waitForEvent } from '@ark-ui/react/tour'
import { PlusIcon, SparklesIcon, XIcon } from 'lucide-react'
import { useState } from 'react'
import button from 'styles/button.module.css'
import styles from 'styles/tour.module.css'

const steps: Tour.StepDetails[] = [
  {
    id: 'intro',
    type: 'dialog',
    title: 'Dynamic Elements',
    description: 'This tour demonstrates waiting for elements that appear dynamically.',
    actions: [{ label: 'Start', action: 'next' }],
  },
  {
    id: 'add-item',
    type: 'tooltip',
    title: 'Add an Item',
    description: 'Click the button to add a new item to the list.',
    target: () => document.querySelector<HTMLElement>('#btn-add-item'),
    effect({ next, target, show }) {
      show()
      const [promise, cancel] = waitForEvent(target, 'click')
      promise.then(() => next())
      return cancel
    },
  },
  {
    id: 'new-item',
    type: 'tooltip',
    title: 'New Item Added!',
    description: 'The tour waited for this element to appear before showing this step.',
    target: () => document.querySelector<HTMLElement>('[data-item="new"]'),
    effect({ show }) {
      const [promise, cancel] = waitForElement(() => document.querySelector<HTMLElement>('[data-item="new"]'), {
        timeout: 5000,
      })
      promise.then(() => show())
      return () => cancel()
    },
    actions: [{ label: 'Next', action: 'next' }],
  },
  {
    id: 'complete',
    type: 'dialog',
    title: 'Tour Complete',
    description: 'You learned how to use waitForElement for dynamic content.',
    actions: [{ label: 'Done', action: 'dismiss' }],
  },
]

export const WaitForElement = () => {
  const tour = useTour({ steps })
  const [items, setItems] = useState<string[]>(['Item 1', 'Item 2'])

  const addItem = () => {
    setItems((prev) => [...prev, `Item ${prev.length + 1}`])
  }

  return (
    <div className={styles.Root}>
      <button type="button" data-variant="surface" className={button.Root} onClick={() => tour.start()}>
        <SparklesIcon /> Start Tour
      </button>

      <button id="btn-add-item" type="button" className={button.Root} onClick={addItem}>
        <PlusIcon /> Add Item
      </button>

      <div className={styles.List}>
        {items.map((item, index) => (
          <div
            key={item}
            className={styles.ListItem}
            data-item={index === items.length - 1 && items.length > 2 ? 'new' : undefined}
          >
            {item}
          </div>
        ))}
      </div>

      <Tour.Root tour={tour}>
        <Portal>
          <Tour.Backdrop className={styles.Backdrop} />
          <Tour.Spotlight className={styles.Spotlight} />
          <Tour.Positioner className={styles.Positioner}>
            <Tour.Content className={styles.Content}>
              <Tour.Arrow className={styles.Arrow}>
                <Tour.ArrowTip className={styles.ArrowTip} />
              </Tour.Arrow>
              <Tour.CloseTrigger className={styles.CloseTrigger}>
                <XIcon />
              </Tour.CloseTrigger>
              <Tour.ProgressText className={styles.ProgressText} />
              <Tour.Title className={styles.Title} />
              <Tour.Description className={styles.Description} />
              <Tour.Control className={styles.Control}>
                <Tour.Actions>
                  {(actions) =>
                    actions.map((action) => (
                      <Tour.ActionTrigger className={styles.ActionTrigger} key={action.label} action={action} />
                    ))
                  }
                </Tour.Actions>
              </Tour.Control>
            </Tour.Content>
          </Tour.Positioner>
        </Portal>
      </Tour.Root>
    </div>
  )
}
```

### Async

Load data asynchronously and update step content before displaying it using the `effect` function with `show()` and
`update()`.

```tsx
import { Portal } from '@ark-ui/react/portal'
import { Tour, useTour } from '@ark-ui/react/tour'
import { SparklesIcon, XIcon } from 'lucide-react'
import button from 'styles/button.module.css'
import styles from 'styles/tour.module.css'

const steps: Tour.StepDetails[] = [
  {
    id: 'intro',
    type: 'dialog',
    title: 'Async Data Loading',
    description: 'This tour demonstrates loading data before showing a step.',
    actions: [{ label: 'Next', action: 'next' }],
  },
  {
    id: 'user-info',
    type: 'tooltip',
    title: 'Loading...',
    description: 'Fetching user data...',
    target: () => document.querySelector<HTMLElement>('#user-card'),
    actions: [
      { label: 'Back', action: 'prev' },
      { label: 'Next', action: 'next' },
    ],
    effect({ show, update }) {
      const controller = new AbortController()

      fetch('https://api.github.com/users/segunadebayo', { signal: controller.signal })
        .then((res) => res.json())
        .then((data) => {
          update({
            title: `Welcome, ${data.name || data.login}!`,
            description: `You have ${data.public_repos} public repositories and ${data.followers} followers.`,
          })
          show()
        })
        .catch(() => {
          update({
            title: 'User Profile',
            description: 'Could not load user data. Please try again.',
          })
          show()
        })

      return () => controller.abort()
    },
  },
  {
    id: 'complete',
    type: 'dialog',
    title: 'Tour Complete',
    description: 'The async step loaded data from the GitHub API before displaying.',
    actions: [{ label: 'Done', action: 'dismiss' }],
  },
]

export const AsyncStep = () => {
  const tour = useTour({ steps })

  return (
    <div className={styles.Root}>
      <button type="button" data-variant="surface" className={button.Root} onClick={() => tour.start()}>
        <SparklesIcon /> Start Tour
      </button>

      <div id="user-card" className={styles.Target}>
        User Profile Card
      </div>

      <Tour.Root tour={tour}>
        <Portal>
          <Tour.Backdrop className={styles.Backdrop} />
          <Tour.Spotlight className={styles.Spotlight} />
          <Tour.Positioner className={styles.Positioner}>
            <Tour.Content className={styles.Content}>
              <Tour.Arrow className={styles.Arrow}>
                <Tour.ArrowTip className={styles.ArrowTip} />
              </Tour.Arrow>
              <Tour.CloseTrigger className={styles.CloseTrigger}>
                <XIcon />
              </Tour.CloseTrigger>
              <Tour.ProgressText className={styles.ProgressText} />
              <Tour.Title className={styles.Title} />
              <Tour.Description className={styles.Description} />
              <Tour.Control className={styles.Control}>
                <Tour.Actions>
                  {(actions) =>
                    actions.map((action) => (
                      <Tour.ActionTrigger className={styles.ActionTrigger} key={action.label} action={action} />
                    ))
                  }
                </Tour.Actions>
              </Tour.Control>
            </Tour.Content>
          </Tour.Positioner>
        </Portal>
      </Tour.Root>
    </div>
  )
}
```

## Guides

### Step Types

The tour machine supports different types of steps, allowing you to create a diverse and interactive tour experience.
The available step types are defined in the `StepType` type:

- `tooltip`: Displays the step content as a tooltip, typically positioned near the target element.

- `dialog`: Shows the step content in a modal dialog centered on screen, useful for starting or ending the tour. This
  usually don't have a `target` defined.

- `floating`: Presents the step content as a floating element, which can be positioned flexibly on the screen. This
  usually don't have a `target` defined.

- `wait`: A special type that waits for a specific condition before proceeding to the next step.

```tsx
const steps: TourStepDetails[] = [
  {
    id: 'step-1',
    type: 'tooltip',
    placement: 'top-start',
    target: () => document.querySelector('#target-1'),
    title: 'Tooltip Step',
    description: 'This is a tooltip step',
  },
  {
    id: 'step-2',
    type: 'dialog',
    title: 'Dialog Step',
    description: 'This is a dialog step',
  },
  {
    id: 'step-3',
    type: 'floating',
    placement: 'top-start',
    title: 'Floating Step',
    description: 'This is a floating step',
  },
  {
    id: 'step-4',
    type: 'wait',
    title: 'Wait Step',
    description: 'This is a wait step',
    effect({ next }) {
      // do something and go next
      // you can also return a cleanup
    },
  },
]
```

### Actions

Every step supports a list of actions that are rendered in the step footer.Use the `actions` property to define each
action.

```tsx
const steps: TourStepDetails[] = [
  {
    id: 'step-1',
    type: 'dialog',
    title: 'Dialog Step',
    description: 'This is a dialog step',
    actions: [{ label: 'Show me a tour!', action: 'next' }],
  },
]
```

### Tooltip Placement

Use the `placement` property to define the placement of the tooltip.

```tsx {5}
const steps: TourStepDetails[] = [
  {
    id: 'step-1',
    type: 'tooltip',
    placement: 'top-start',
    // ...
  },
]
```

### Hide Arrow

Set `arrow: false` in the step property to hide the tooltip arrow. This is only useful for tooltip steps.

```tsx {5}
const steps: TourStepDetails[] = [
  {
    id: 'step-1',
    type: 'tooltip',
    arrow: false,
  },
]
```

### Hide Backdrop

Set `backdrop: false` in the step property to hide the backdrop. This applies to all step types except the `wait` step.

```tsx {5}
const steps: TourStepDetails[] = [
  {
    id: 'step-1',
    type: 'dialog',
    backdrop: false,
  },
]
```

### Effects

Step effects are functions that are called before a step is opened. They are useful for adding custom logic to a step.

This function provides the following methods:

- `next()`: Call this method to move to the next step.
- `show()`: Call this method to show the current step.
- `update(details: StepDetails)`: Call this method to update the details of the current step (say, after data has been
  fetched).

```tsx
const steps: TourStepDetails[] = [
  {
    id: 'step-1',
    type: 'tooltip',
    effect({ next, show, update }) {
      fetchData().then((res) => {
        // update the step details
        update({ title: res.title })
        // then show show the step
        show()
      })

      return () => {
        // cleanup fetch data
      }
    },
  },
]
```

### Wait

Wait steps are useful when you need to wait for a specific condition before proceeding to the next step.

Use the step `effect` function to perform an action and then call `next()` to move to the next step.

> **Note:** You cannot call `show()` in a wait step.

```tsx
const steps: TourStepDetails[] = [
  {
    id: 'step-1',
    type: 'wait',
    effect({ next }) {
      const button = document.querySelector('#button')
      const listener = () => next()
      button.addEventListener('click', listener)
      return () => button.removeEventListener('click', listener)
    },
  },
]
```

### Styling

Ensure the `box-sizing` is set to `border-box` for the means of measuring the tour target.

```css
* {
  box-sizing: border-box;
}
```

Ensure the `body` has a `position` of `relative`.

```css
body {
  position: relative;
}
```

## API Reference

### Props

### Root

#### Props

**`tour`**
Type: `UseTourReturn`
Required: true
Default Value: `undefined`
Description: undefined

**`hideMode`**
Type: `HideMode`
Required: false
Default Value: `'display-none'`
Description: How to hide content when mounted but not present.
- `'display-none'`: HTML `hidden` attribute. Effects stay alive.
- `'activity'`: React 19 `<Activity mode="hidden">`. Effects pause. Requires React 19+.

**`immediate`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Whether to synchronize the present change immediately or defer it to the next frame

**`lazyMount`**
Type: `boolean`
Required: false
Default Value: `false`
Description: Whether to enable lazy mounting

**`onExitComplete`**
Type: `VoidFunction`
Required: false
Default Value: `undefined`
Description: Function called when the animation ends in the closed state

**`present`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Whether the node is present (controlled by the user)

**`skipAnimationOnMount`**
Type: `boolean`
Required: false
Default Value: `false`
Description: Whether to allow the initial presence animation.

**`unmountOnExit`**
Type: `boolean`
Required: false
Default Value: `false`
Description: Whether to unmount on exit.

### ActionTrigger

#### Props

**`action`**
Type: `StepAction`
Required: true
Default Value: `undefined`
Description: undefined

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

#### Data Attributes

**`data-scope`**: tour
**`data-part`**: action-trigger
**`data-type`**: The type of the item
**`data-disabled`**: Present when disabled

### Arrow

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

### ArrowTip

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

### Backdrop

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

#### Data Attributes

**`data-scope`**: tour
**`data-part`**: backdrop
**`data-state`**: "open" | "closed"
**`data-type`**: The type of the item

### CloseTrigger

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

#### Data Attributes

**`data-scope`**: tour
**`data-part`**: close-trigger
**`data-type`**: The type of the item

### Content

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

#### Data Attributes

**`data-scope`**: tour
**`data-part`**: content
**`data-state`**: "open" | "closed"
**`data-nested`**: popover
**`data-has-nested`**: popover
**`data-type`**: The type of the item
**`data-placement`**: The placement of the content
**`data-side`**: The side of the trigger that the content is positioned on
**`data-step`**: 

### Control

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

### Description

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

#### Data Attributes

**`data-scope`**: tour
**`data-part`**: description
**`data-placement`**: The placement of the description
**`data-side`**: The side of the trigger that the description is positioned on

### Positioner

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

#### Data Attributes

**`data-scope`**: tour
**`data-part`**: positioner
**`data-type`**: The type of the item
**`data-placement`**: The placement of the positioner
**`data-side`**: The side of the trigger that the positioner is positioned on

### ProgressText

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

### Spotlight

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

### Title

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

#### Data Attributes

**`data-scope`**: tour
**`data-part`**: title
**`data-placement`**: The placement of the title
**`data-side`**: The side of the trigger that the title is positioned on

### Context

**API:**

| Property | Type | Description |
|----------|------|-------------|
| `open` | `boolean` | Whether the tour is open |
| `totalSteps` | `number` | The total number of steps |
| `stepIndex` | `number` | The index of the current step |
| `step` | `StepDetails | null` | The current step details |
| `hasNextStep` | `boolean` | Whether there is a next step |
| `hasPrevStep` | `boolean` | Whether there is a previous step |
| `firstStep` | `boolean` | Whether the current step is the first step |
| `lastStep` | `boolean` | Whether the current step is the last step |
| `addStep` | `(step: StepDetails) => void` | Add a new step to the tour |
| `removeStep` | `(id: string) => void` | Remove a step from the tour |
| `updateStep` | `(id: string, stepOverrides: Partial<StepDetails>) => void` | Update a step in the tour with partial details |
| `setSteps` | `(steps: StepDetails[]) => void` | Set the steps of the tour |
| `setStep` | `(id: string) => void` | Set the current step of the tour |
| `start` | `(id?: string) => void` | Start the tour at a specific step (or the first step if not provided) |
| `isValidStep` | `(id: string) => boolean` | Check if a step is valid |
| `isCurrentStep` | `(id: string) => boolean` | Check if a step is visible |
| `next` | `VoidFunction` | Move to the next step |
| `prev` | `VoidFunction` | Move to the previous step |
| `getProgressText` | `() => string` | Returns the progress text |
| `getProgressPercent` | `() => number` | Returns the progress percent |
