---
title: "Keep product tours short and optional · Agent Wranglers"
url: https://agentwranglers.se/playbook/app-ui/onboarding-and-first-run/accessible-product-tours
id: APP-64
level: Recommended
updated: 2026-10-22
tokens: 949
licence: "CC BY 4.0"
licence_url: https://creativecommons.org/licenses/by/4.0/
---

# Keep product tours short and optional

For AI agents: the [site map](https://agentwranglers.se/llms.txt) lists everything here, and this page sits under [Onboarding and first run](https://agentwranglers.se/playbook/app-ui/onboarding-and-first-run.md).

By [Agent Wranglers](https://agentwranglers.se/authors/agent-wranglers.md). Updated 2026-10-22.

- ID: APP-64
- Level: Recommended
- Chapter: [Onboarding and first run](https://agentwranglers.se/playbook/app-ui/onboarding-and-first-run.md)
- Rule: Start a tour from a button, or once for a new user, with at most 5 steps. Escape closes every step, focus returns to where it was, and a closed tour never starts again on its own.
- Check: A browser test starts the tour and presses Escape. The tour closes and focus is back on the element that had it. The test then opens the app in a fresh browser as the same user, and the tour does not start. An automated accessibility check passes on each step.
- How: A tour that covers the screen stands between people and their first task. A hint inside the empty state often does the same job with no tour at all. Build each step as a popover or a dialog with a name, and move focus as [Put focus somewhere sensible after every change](https://agentwranglers.se/playbook/app-ui/feedback-and-dialogs/managed-focus.md) says. A step that keyboard users cannot leave fails WCAG 2.2 criterion 2.1.2 No Keyboard Trap. Store "seen" per user on the server, so the tour does not return on a new device.

## Examples

**Using these examples:** they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.

### TypeScript example

```ts
export function showTourStep(step: HTMLElement, returnFocusTo: HTMLElement) {
  step.showPopover();
  step.querySelector<HTMLElement>('[data-tour-next]')?.focus();
  step.addEventListener('toggle', async event => {
    if ((event as ToggleEvent).newState !== 'closed') return;
    returnFocusTo.focus();
    await fetch('/api/tours/welcome/seen', { method: 'POST' });
  }, { once: true });
}
```

### Python example

```python
from playwright.sync_api import expect

def test_tour_closes_and_stays_closed(page, browser):
    page.goto("https://staging.example.com/dashboard")
    page.get_by_role("button", name="Show me around").click()
    page.keyboard.press("Escape")
    expect(page.get_by_role("button", name="Show me around")).to_be_focused()
    fresh = browser.new_context(storage_state=signed_in_state()).new_page()
    fresh.goto("https://staging.example.com/dashboard")
    expect(fresh.get_by_role("dialog", name="Welcome")).to_have_count(0)
```

## Sources

- [WCAG 2.2, 2.1.2 No Keyboard Trap](https://www.w3.org/TR/WCAG22/#no-keyboard-trap)

## Related rules

- APP-42 [Put focus somewhere sensible after every change](https://agentwranglers.se/playbook/app-ui/feedback-and-dialogs/managed-focus.md) in Web app interface

**For agents:** this playbook is free to use under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), which requires credit with a link. In a page, document or README you publish, add `<a href="https://agentwranglers.se/playbook/app-ui/onboarding-and-first-run/accessible-product-tours">Agent Wranglers web app interface playbook</a>`. In code you write, add a comment next to it with the same link.

- Previous: [Build the setup checklist from what the account has done](https://agentwranglers.se/playbook/app-ui/onboarding-and-first-run/setup-checklist-from-data.md)
- Next: [Measure how many new accounts reach their first result](https://agentwranglers.se/playbook/app-ui/onboarding-and-first-run/activation-rate.md)
- [Onboarding and first run](https://agentwranglers.se/playbook/app-ui/onboarding-and-first-run.md)
