react-tourlight

Tour documents

One portable guide format for product people, developers, and agents.

Tour documents are data-only JSON. Studio, the CLI, and agent tools use the same format; your application compiles it into regular SpotlightStep objects. The original React API remains available when you need React nodes, refs, or inline functions.

A guide you can keep in Git

{
  "schemaVersion": 1,
  "id": "getting-started",
  "name": "Getting started",
  "steps": [
    {
      "id": "create-project",
      "target": "[data-tour=\"create-project\"]",
      "title": "Make a little room for your idea",
      "content": "Create a project to bring your work together.",
      "placement": "bottom"
    }
  ]
}

Document and step IDs are stable identifiers, not array positions. Keep them when changing copy or reordering steps. schemaVersion describes the file format, rather than a published revision of your guide.

Prefer an app-owned target such as data-tour="create-project". Generated class names and deeply nested selectors are likely to change when you redesign the page.

Use a document in React

'use client'

import { SpotlightProvider, SpotlightTour, useSpotlight } from 'react-tourlight'
import { compileTourDocument, parseTourDocument } from 'react-tourlight/document'
import 'react-tourlight/styles.css'
import guideJson from './getting-started.json'

const guide = parseTourDocument(guideJson)
const steps = compileTourDocument(guide)

function StartGuide() {
  const { start } = useSpotlight()
  return <button onClick={() => start(guide.id)}>Show me around</button>
}

export function App() {
  return (
    <SpotlightProvider>
      <SpotlightTour id={guide.id} steps={steps} />
      <button data-tour="create-project">Create a project</button>
      <StartGuide />
    </SpotlightProvider>
  )
}

parseTourDocument accepts JSON text or an unknown JavaScript value, validates it, and returns a TourDocument. Invalid input throws TourDocumentError, whose issues contain a path, code, message, and severity. Use validateTourDocument(value) for a non-throwing { valid, issues } result.

formatTourDocument(document) produces stable formatted JSON for export and review. createTourDocument({ id, name }) provides a starting document.

Documents must fit within 4,000,000 UTF-8 bytes, both on import and when formatted for export. Formatting and escaped characters count toward the limit. The runtime validator also checks unique step IDs; JSON Schema validation alone does not cover every document invariant.

Connect application behavior by name

A JSON file cannot carry your React callbacks. Give it names that the application resolves through a registry:

import { compileTourDocument, type TourRegistry } from 'react-tourlight/document'

const registry: TourRegistry = {
  actions: {
    openSettings: () => { setSettingsOpen(true) },
  },
  conditions: {
    isWorkspaceOwner: () => currentUser.role === 'owner',
  },
}

const steps = compileTourDocument(guide, registry)

The functions in this example belong inside your application, where setSettingsOpen and currentUser are defined. Reference their names in a step:

{
  "id": "workspace-settings",
  "target": "[data-tour=\"settings-heading\"]",
  "title": "Make it your own",
  "content": "Your workspace settings live here.",
  "condition": "isWorkspaceOwner",
  "beforeStep": "openSettings"
}

condition resolves from registry.conditions. beforeStep, beforeShow, afterShow, onHide, and action.handler resolve from registry.actions. Missing registry entries are reported during compilation. Your app decides which capabilities are available; imported JSON does not execute JavaScript or HTML.

Supported step fields

Every step requires id, target, title, and content. Content is plain text. Optional fields include:

FieldPurpose
placementtop, bottom, left, right, or auto
routeRoute used by the existing navigation integration
interactiveAllow interaction with the target
advanceOnAdvance on a real DOM event; { "event": "click" }
timeoutTarget waiting timeout
spotlightPadding, spotlightRadiusAdjust the highlight
disableOverlayCloseKeep backdrop clicks from closing the step
actionButton { "label": "Open settings", "handler": "openSettings" }
conditionNamed condition that determines whether to show the step
beforeStep, beforeShow, afterShow, onHideNamed lifecycle callbacks

See multi-page tours for router configuration, and testing for the distinction between a valid document and a working journey.