flowsterix-best-practices

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Flowsterix Best Practices

Flowsterix 最佳实践

Flowsterix is a state machine-based guided tour library for React applications. Flows are declarative step sequences with automatic progression rules, lifecycle hooks, and persistence.
Flowsterix 是一个基于状态机的React应用引导流程库。引导流是声明式的步骤序列,具备自动推进规则、生命周期钩子和状态持久化能力。

Quick Start

快速开始

Installation

安装

bash
undefined
bash
undefined

Core packages

核心包

npm install @flowsterix/core @flowsterix/react motion
npm install @flowsterix/core @flowsterix/react motion

Recommended: Add preconfigured shadcn components

推荐:添加预配置的shadcn组件


**Prefer the shadcn components** - they provide polished, accessible UI out of the box and follow the design patterns shown in the examples.

**优先使用shadcn组件** - 它们提供了开箱即用的精致、可访问的UI,并且遵循示例中展示的设计模式。

Minimal Example

最简示例

tsx
import { createFlow, type FlowDefinition } from '@flowsterix/core'
import { TourProvider, TourHUD } from '@flowsterix/react'
import type { ReactNode } from 'react'

const onboardingFlow: FlowDefinition<ReactNode> = createFlow({
  id: 'onboarding',
  version: { major: 1, minor: 0 },
  autoStart: true,
  steps: [
    {
      id: 'welcome',
      target: 'screen',
      advance: [{ type: 'manual' }],
      content: <p>Welcome to our app!</p>,
    },
    {
      id: 'feature',
      target: { selector: '[data-tour-target="main-feature"]' },
      advance: [{ type: 'event', event: 'click', on: 'target' }],
      content: <p>Click this button to continue</p>,
    },
  ],
})

export function App({ children }) {
  return (
    <TourProvider flows={[onboardingFlow]} storageNamespace="my-app">
      <TourHUD overlay={{ showRing: true }} />
      {children}
    </TourProvider>
  )
}
tsx
import { createFlow, type FlowDefinition } from '@flowsterix/core'
import { TourProvider, TourHUD } from '@flowsterix/react'
import type { ReactNode } from 'react'

const onboardingFlow: FlowDefinition<ReactNode> = createFlow({
  id: 'onboarding',
  version: { major: 1, minor: 0 },
  autoStart: true,
  steps: [
    {
      id: 'welcome',
      target: 'screen',
      advance: [{ type: 'manual' }],
      content: <p>欢迎使用我们的应用!</p>,
    },
    {
      id: 'feature',
      target: { selector: '[data-tour-target="main-feature"]' },
      advance: [{ type: 'event', event: 'click', on: 'target' }],
      content: <p>点击此按钮继续</p>,
    },
  ],
})

export function App({ children }) {
  return (
    <TourProvider flows={[onboardingFlow]} storageNamespace="my-app">
      <TourHUD overlay={{ showRing: true }} />
      {children}
    </TourProvider>
  )
}

Core Concepts

核心概念

FlowDefinition

FlowDefinition

tsx
createFlow({
  id: string,                          // Unique identifier
  version: { major: number, minor: number },  // For storage migrations
  steps: Step[],                       // Array of tour steps
  dialogs?: Record<string, DialogConfig>, // Dialog configurations (see Radix Dialog Integration)
  autoStart?: boolean,                 // Start on mount (default: false)
  resumeStrategy?: 'chain' | 'current', // How to run onResume hooks
  hud?: FlowHudOptions,                // UI configuration
  migrate?: (ctx) => FlowState | null, // Version migration handler
})
tsx
createFlow({
  id: string,                          // 唯一标识符
  version: { major: number, minor: number },  // 用于存储迁移
  steps: Step[],                       // 引导步骤数组
  dialogs?: Record<string, DialogConfig>, // 对话框配置(参见Radix Dialog集成)
  autoStart?: boolean,                 // 挂载时启动(默认:false)
  resumeStrategy?: 'chain' | 'current', // onResume钩子的执行方式
  hud?: FlowHudOptions,                // UI配置
  migrate?: (ctx) => FlowState | null, // 版本迁移处理函数
})

Step Anatomy

步骤结构

tsx
{
  id: string,                    // Unique within flow
  target: StepTarget,            // What to highlight
  content: ReactNode,            // Popover content
  dialogId?: string,             // Reference to flow.dialogs entry (auto-opens dialog)
  advance?: AdvanceRule[],       // When to move to next step
  placement?: StepPlacement,     // Popover position
  route?: string | RegExp,       // Only show on matching routes
  waitFor?: StepWaitFor,         // Block until condition met
  targetBehavior?: StepTargetBehavior,  // Scroll/visibility handling
  onEnter?: (ctx) => void,       // Fires when step activates
  onResume?: (ctx) => void,      // Fires when resuming from storage
  onExit?: (ctx) => void,        // Fires when leaving step
  controls?: { back?, next? },   // Button visibility
}
tsx
{
  id: string,                    // 在当前流内唯一
  target: StepTarget,            // 要高亮的目标
  content: ReactNode,            // 弹出框内容
  dialogId?: string,             // 关联flow.dialogs中的配置项(自动打开对话框)
  advance?: AdvanceRule[],       // 推进到下一步的触发规则
  placement?: StepPlacement,     // 弹出框位置
  route?: string | RegExp,       // 仅在匹配的路由上显示
  waitFor?: StepWaitFor,         // 满足条件前阻塞步骤
  targetBehavior?: StepTargetBehavior,  // 滚动/可见性处理
  onEnter?: (ctx) => void,       // 步骤激活时触发
  onResume?: (ctx) => void,      // 从存储恢复时触发
  onExit?: (ctx) => void,        // 离开步骤时触发
  controls?: { back?, next? },   // 按钮可见性配置
}

Step Targets

步骤目标

tsx
// Full-screen overlay (no element highlight)
target: 'screen'

// CSS selector (recommended: use data attributes)
target: {
  selector: '[data-tour-target="feature"]'
}

// Dynamic node resolution
target: {
  getNode: () => document.querySelector('.dynamic-el')
}
Always use
data-tour-target
attributes
instead of CSS classes for stability.
tsx
// 全屏遮罩(无元素高亮)
target: 'screen'

// CSS选择器(推荐:使用data属性)
target: {
  selector: '[data-tour-target="feature"]'
}

// 动态节点解析
target: {
  getNode: () => document.querySelector('.dynamic-el')
}
始终使用
data-tour-target
属性
而非CSS类,以保证稳定性。

Advance Rules

推进规则

Rules define when a step automatically progresses. First matching rule wins.
TypeUsageExample
manual
Next button only
{ type: 'manual' }
event
DOM event on target
{ type: 'event', event: 'click', on: 'target' }
delay
Timer-based
{ type: 'delay', ms: 3000 }
route
URL change
{ type: 'route', to: '/dashboard' }
predicate
Polling condition
{ type: 'predicate', check: (ctx) => isReady() }
tsx
// Combine rules for flexibility
advance: [
  { type: 'event', event: 'click', on: 'target' },
  { type: 'delay', ms: 10000 }, // Fallback after 10s
]
规则定义步骤自动推进的时机,匹配到的第一条规则生效。
类型用法示例
manual
仅通过下一步按钮
{ type: 'manual' }
event
目标元素上的DOM事件
{ type: 'event', event: 'click', on: 'target' }
delay
基于计时器
{ type: 'delay', ms: 3000 }
route
URL变更时
{ type: 'route', to: '/dashboard' }
predicate
轮询条件满足时
{ type: 'predicate', check: (ctx) => isReady() }
tsx
// 组合规则以提升灵活性
advance: [
  { type: 'event', event: 'click', on: 'target' },
  { type: 'delay', ms: 10000 }, // 10秒后自动推进作为备选
]

React Integration

React集成

TourProvider Props

TourProvider 属性

tsx
<TourProvider
  flows={[flow1, flow2]} // Flow definitions
  storageNamespace="my-app" // localStorage key prefix
  persistOnChange={true} // Auto-save state changes
  backdropInteraction="block" // 'block' | 'passthrough'
  lockBodyScroll={false} // Prevent page scroll
  labels={{
    // Customize UI text for internationalization
    back: 'Zurück',
    next: 'Weiter',
    finish: 'Fertig',
    skip: 'Tour überspringen',
    // See Internationalization section for full list
  }}
  analytics={{
    // Event handlers
    onFlowStart: (p) => track('tour_start', p),
    onStepEnter: (p) => track('step_view', p),
  }}
/>
tsx
<TourProvider
  flows={[flow1, flow2]} // 引导流定义
  storageNamespace="my-app" // localStorage键前缀
  persistOnChange={true} // 自动保存状态变更
  backdropInteraction="block" // 'block' | 'passthrough'
  lockBodyScroll={false} // 禁止页面滚动
  labels={{
    // 自定义UI文本以支持国际化
    back: 'Zurück',
    next: 'Weiter',
    finish: 'Fertig',
    skip: 'Tour überspringen',
    // 完整列表请参见国际化章节
  }}
  analytics={{
    // 事件处理函数
    onFlowStart: (p) => track('tour_start', p),
    onStepEnter: (p) => track('step_view', p),
  }}
/>

useTour Hook

useTour 钩子

tsx
const {
  activeFlowId, // Currently active flow ID or null
  state, // FlowState: status, stepIndex, version
  activeStep, // Current Step object
  startFlow, // (flowId, options?) => start a flow
  next, // () => advance to next step
  back, // () => go to previous step
  pause, // () => pause the flow
  cancel, // (reason?) => cancel the flow
  complete, // () => mark flow complete
  advanceStep, // (stepId) => FlowState | null — advance only if on that step
} = useTour()
tsx
const {
  activeFlowId, // 当前激活的引导流ID或null
  state, // FlowState:状态、步骤索引、版本
  activeStep, // 当前步骤对象
  startFlow, // (flowId, options?) => 启动引导流
  next, // () => 推进到下一步
  back, // () => 返回上一步
  pause, // () => 暂停引导流
  cancel, // (reason?) => 取消引导流
  complete, // () => 标记引导流完成
  advanceStep, // (stepId) => FlowState | null — 仅当当前在指定步骤时推进
} = useTour()

Conditional Advance with advanceStep

使用advanceStep实现条件推进

Use
advanceStep(stepId)
when you want to advance the tour only if the user is currently on a specific step. This is useful for components that trigger tour progression as a side effect.
tsx
const { advanceStep } = useTour()

// In a logo upload component:
const handleLogoUpload = async (file: File) => {
  await uploadLogo(file)
  advanceStep('change-logo') // Only advances if tour is on 'change-logo' step
}
Behavior:
  • If currently on the specified step → advances to next (or completes if last step)
  • If on a different step → silent no-op (returns current state)
  • If stepId doesn't exist → silent no-op (not an error)
  • If no active flow → returns
    null
    (safe to call without checking flow state)
当你希望仅在用户当前处于特定步骤时推进引导流程时,使用
advanceStep(stepId)
。这在组件将引导推进作为副作用触发时非常有用。
tsx
const { advanceStep } = useTour()

// 在Logo上传组件中:
const handleLogoUpload = async (file: File) => {
  await uploadLogo(file)
  advanceStep('change-logo') // 仅当引导流程处于'change-logo'步骤时才推进
}
行为:
  • 如果当前处于指定步骤 → 推进到下一步(如果是最后一步则标记完成)
  • 如果处于其他步骤 → 静默无操作(返回当前状态)
  • 如果stepId不存在 → 静默无操作(不会报错)
  • 如果没有激活的引导流 → 返回
    null
    (无需检查流状态即可安全调用)

TourHUD Configuration

TourHUD 配置

tsx
<TourHUD
  overlay={{
    padding: 12, // Padding around highlight
    radius: 12, // Border radius of cutout
    showRing: true, // Glow effect around target
    blurAmount: 6, // Backdrop blur (px)
  }}
  popover={{
    maxWidth: 360,
    offset: 32, // Distance from target
  }}
  controls={{
    showSkip: true,
    skipMode: 'hold', // 'click' | 'hold' (hold-to-confirm)
  }}
  progress={{
    show: true,
    variant: 'dots', // 'dots' | 'bar' | 'fraction'
  }}
  mobile={{
    enabled: true, // Enable mobile drawer (default: true)
    breakpoint: 640, // Width threshold for mobile (default: 640)
    defaultSnapPoint: 'expanded', // Initial state (default: 'expanded')
    snapPoints: ['minimized', 'expanded'], // Available states
    allowMinimize: true, // Allow swipe down to minimize
  }}
/>
tsx
<TourHUD
  overlay={{
    padding: 12, // 高亮区域周围的内边距
    radius: 12, // 镂空区域的圆角
    showRing: true, // 目标周围的发光效果
    blurAmount: 6, // 背景模糊程度(像素)
  }}
  popover={{
    maxWidth: 360,
    offset: 32, // 与目标的距离
  }}
  controls={{
    showSkip: true,
    skipMode: 'hold', // 'click' | 'hold'(长按确认)
  }}
  progress={{
    show: true,
    variant: 'dots', // 'dots' | 'bar' | 'fraction'
  }}
  mobile={{
    enabled: true, // 启用移动端抽屉(默认:true)
    breakpoint: 640, // 移动端宽度阈值(默认:640)
    defaultSnapPoint: 'expanded', // 初始状态(默认:'expanded')
    snapPoints: ['minimized', 'expanded'], // 可用状态
    allowMinimize: true, // 允许向下滑动最小化
  }}
/>

Mobile Drawer

移动端抽屉

On viewports ≤640px,
TourHUD
automatically renders a bottom sheet drawer instead of a floating popover. Users can swipe to minimize (see highlighted target) or expand (read content).
Snap Points:
  • minimized
    (~100px) - Shows step indicator + nav buttons only
  • peek
    (~40% of expanded) - Optional middle state for summaries
  • expanded
    (auto) - Sized to content, capped at
    maxHeightRatio
Gestures:
  • Swipe down → minimize
  • Swipe up → expand
  • Tap handle → toggle between states
Behavior:
  • Auto-sizes to content - Drawer height matches content + chrome (handle, header, controls)
  • Capped at max - Won't exceed
    maxHeightRatio
    of viewport (default 85%)
  • No flicker - Starts small, animates up once content is measured
  • Resets to
    expanded
    on step transitions
  • Content crossfades between steps
  • Safe area insets for notched phones
  • aria-live
    announcement when minimized
Constrained Scroll Lock: When body scroll lock is enabled and the highlighted target exceeds viewport height, constrained scroll lock allows scrolling within target bounds only:
  • Target fits in viewport → normal scroll lock (
    overflow: hidden
    )
  • Target exceeds viewport → scroll constrained to target bounds (user can see entire element)
tsx
// Auto-size with custom max height
<TourHUD
  mobile={{
    maxHeightRatio: 0.7, // Cap at 70% viewport
  }}
/>

// Enable three-state drawer with peek
<TourHUD
  mobile={{
    snapPoints: ['minimized', 'peek', 'expanded'],
    defaultSnapPoint: 'expanded',
  }}
/>
在视口宽度≤640px时,
TourHUD
会自动渲染底部抽屉而非浮动弹出框。用户可以滑动来最小化(查看高亮目标)或展开(阅读内容)。
停靠点:
  • minimized
    (约100px)- 仅显示步骤指示器和导航按钮
  • peek
    (约为展开状态的40%)- 可选的中间状态,用于展示摘要
  • expanded
    (自动)- 根据内容自动调整大小,上限为
    maxHeightRatio
手势操作:
  • 向下滑动 → 最小化
  • 向上滑动 → 展开
  • 点击手柄 → 在状态间切换
行为:
  • 自动适配内容大小 - 抽屉高度匹配内容和控件(手柄、标题、导航)
  • 最大高度限制 - 不会超过视口的
    maxHeightRatio
    (默认85%)
  • 无闪烁切换 - 初始尺寸较小,测量内容后动画展开
  • 步骤切换时重置为
    expanded
    状态
  • 步骤内容之间淡入淡出切换
  • 为刘海屏手机适配安全区域
  • 最小化时通过
    aria-live
    播报状态
约束滚动锁定: 当启用页面滚动锁定且高亮目标超出视口高度时,约束滚动锁定仅允许在目标元素范围内滚动:
  • 目标在视口内 → 正常滚动锁定(
    overflow: hidden
  • 目标超出视口 → 滚动被限制在目标元素范围内(用户可以看到完整元素)
tsx
// 自动适配大小并设置自定义最大高度
<TourHUD
  mobile={{
    maxHeightRatio: 0.7, // 上限为视口的70%
  }}
/>

// 启用包含peek状态的三态抽屉
<TourHUD
  mobile={{
    snapPoints: ['minimized', 'peek', 'expanded'],
    defaultSnapPoint: 'expanded',
  }}
/>

Common Mistakes

常见错误

  1. Missing
    data-tour-target
    attributes
    - Tour cannot find elements
    tsx
    // Bad: fragile to styling changes
    target: {
      selector: '.btn-primary'
    }
    
    // Good: semantic and stable
    target: {
      selector: '[data-tour-target="submit-btn"]'
    }
  2. No
    waitFor
    for async content
    - Step shows before content ready
    tsx
    // Add waitFor when targeting dynamically loaded elements
    waitFor: { selector: '[data-tour-target="api-result"]', timeout: 8000 }
  3. Ignoring sticky headers - Target scrolls behind fixed navigation
    tsx
    targetBehavior: {
      scrollMargin: { top: 80 },  // Height of sticky header
      scrollMode: 'start',
      scrollDurationMs: 350,      // Keep scroll timing aligned with HUD motion
    }
  4. Wrong version format - Use object, not number
    tsx
    // Bad
    version: 1
    
    // Good
    version: { major: 1, minor: 0 }
  5. Forgetting
    onResume
    hooks
    - UI state not restored after reload
    tsx
    // Bad: UI broken after page reload
    onEnter: () => ensureMenuOpen(),
    
    // Good: Both hooks restore UI state
    onEnter: () => ensureMenuOpen(),
    onResume: () => ensureMenuOpen(),
    onExit: () => ensureMenuClosed(),
  1. 缺失
    data-tour-target
    属性
    - 引导流程无法找到元素
    tsx
    // 错误:易受样式变更影响
    target: {
      selector: '.btn-primary'
    }
    
    // 正确:语义化且稳定
    target: {
      selector: '[data-tour-target="submit-btn"]'
    }
  2. 未为异步内容设置
    waitFor
    - 步骤在内容准备好之前就显示
    tsx
    // 当目标是动态加载的元素时,添加waitFor
    waitFor: { selector: '[data-tour-target="api-result"]', timeout: 8000 }
  3. 忽略粘性头部 - 目标元素滚动到固定导航栏后方
    tsx
    targetBehavior: {
      scrollMargin: { top: 80 },  // 粘性头部的高度
      scrollMode: 'start',
      scrollDurationMs: 350,      // 保持滚动动画与HUD动效同步
    }
  4. 版本格式错误 - 使用对象而非数字
    tsx
    // 错误
    version: 1
    
    // 正确
    version: { major: 1, minor: 0 }
  5. 忘记添加
    onResume
    钩子
    - 页面重新加载后UI状态未恢复
    tsx
    // 错误:页面重新加载后UI异常
    onEnter: () => ensureMenuOpen(),
    
    // 正确:两个钩子都能恢复UI状态
    onEnter: () => ensureMenuOpen(),
    onResume: () => ensureMenuOpen(),
    onExit: () => ensureMenuClosed(),

Scroll Synchronization for Long Jumps

长距离滚动同步

When consecutive steps are far apart on the page, set a fixed
scrollDurationMs
on the step:
tsx
{
  id: 'architecture',
  target: { selector: '[data-tour-target="architecture"]' },
  targetBehavior: {
    scrollMode: 'center',
    scrollDurationMs: 350,
  },
}
Guidelines:
  • Use
    250-450ms
    for most landing pages. Start with
    350ms
    .
  • Keep page-level CSS smooth scroll if you want; when
    scrollDurationMs
    is set, Flowsterix temporarily bypasses global CSS smooth scrolling so timing stays deterministic.
  • During long jumps, overlay highlight and popover stay anchored to the previous on-screen position until the next target enters the viewport, then transition to the new target.
  • Use
    scrollMode: 'preserve'
    for minimal movement,
    center
    for guided storytelling, or
    start
    when sticky headers need strict top alignment.
当连续步骤在页面上相距较远时,为步骤设置固定的
scrollDurationMs
tsx
{
  id: 'architecture',
  target: { selector: '[data-tour-target="architecture"]' },
  targetBehavior: {
    scrollMode: 'center',
    scrollDurationMs: 350,
  },
}
指导原则:
  • 大多数落地页使用
    250-450ms
    ,推荐从
    350ms
    开始。
  • 如果需要可以保留页面级CSS平滑滚动;当设置了
    scrollDurationMs
    时,Flowsterix会临时绕过全局CSS平滑滚动,以保证动画时间的确定性。
  • 在长距离滚动过程中,遮罩高亮和弹出框会锚定在之前的屏幕位置,直到下一个目标进入视口,然后过渡到新目标。
  • 使用
    scrollMode: 'preserve'
    实现最小移动,
    center
    用于引导式叙事,
    start
    用于粘性头部需要严格顶部对齐的场景。

Shadcn Components

Shadcn组件

The shadcn registry provides preconfigured, polished components. Always prefer these over custom implementations.
Important: The tour components require shadcn CSS variables (
--popover
,
--border
,
--destructive
, etc.). If you're not using shadcn/ui, see CSS Setup for the required variables.
Shadcn注册表提供了预配置的精致组件。始终优先使用这些组件而非自定义实现。
重要提示:引导组件依赖shadcn CSS变量(
--popover
--border
--destructive
等)。如果未使用shadcn/ui,请参见CSS设置获取所需变量。

Available Components

可用组件

ComponentInstall CommandUsage
tour-hud
npx shadcn@latest add https://flowsterix.com/r/tour-hud.json
Full HUD with overlay & popover
step-content
npx shadcn@latest add https://flowsterix.com/r/step-content.json
Step layout primitives
mobile-drawer
npx shadcn@latest add https://flowsterix.com/r/mobile-drawer.json
Bottom sheet for mobile
mobile-drawer-handle
npx shadcn@latest add https://flowsterix.com/r/mobile-drawer-handle.json
Swipe handle for drawer
组件安装命令用途
tour-hud
npx shadcn@latest add https://flowsterix.com/r/tour-hud.json
包含遮罩和弹出框的完整HUD
step-content
npx shadcn@latest add https://flowsterix.com/r/step-content.json
步骤布局基础组件
mobile-drawer
npx shadcn@latest add https://flowsterix.com/r/mobile-drawer.json
移动端底部抽屉
mobile-drawer-handle
npx shadcn@latest add https://flowsterix.com/r/mobile-drawer-handle.json
抽屉的滑动手柄

Step Content Primitives

步骤内容基础组件

Use these components for consistent step styling:
tsx
import {
  StepContent,
  StepTitle,
  StepText,
  StepHint,
} from '@/components/step-content'

content: (
  <StepContent>
    <StepTitle>Feature Discovery</StepTitle>
    <StepText>
      This is the main explanation text with muted styling.
    </StepText>
    <StepHint>Click the button to continue.</StepHint>
  </StepContent>
)
  • StepContent
    - Grid container with proper spacing
  • StepTitle
    - Semibold heading (supports
    size="lg"
    for welcome screens)
  • StepText
    - Muted paragraph text
  • StepHint
    - Italic hint text for user instructions
使用这些组件实现一致的步骤样式:
tsx
import {
  StepContent,
  StepTitle,
  StepText,
  StepHint,
} from '@/components/step-content'

content: (
  <StepContent>
    <StepTitle>功能探索</StepTitle>
    <StepText>
      这是带有弱化样式的主要说明文本。
    </StepText>
    <StepHint>点击按钮继续。</StepHint>
  </StepContent>
)
  • StepContent
    - 带合适间距的网格容器
  • StepTitle
    - 半粗体标题(支持
    size="lg"
    用于欢迎页面)
  • StepText
    - 弱化样式的段落文本
  • StepHint
    - 斜体提示文本,用于用户操作指引

Radix Dialog Integration

Radix Dialog集成

Use
useRadixTourDialog
for declarative dialog control during tours.
使用
useRadixTourDialog
在引导流程中声明式控制对话框。

Setup

配置步骤

tsx
import { createFlow } from '@flowsterix/core'
import { useRadixTourDialog } from '@flowsterix/react'
import * as Dialog from '@radix-ui/react-dialog'

// 1. Configure dialogs in flow definition
const flow = createFlow({
  id: 'onboarding',
  version: { major: 1, minor: 0 },
  dialogs: {
    settings: {
      autoOpen: true,                    // Open when entering dialog steps
      autoClose: 'differentDialog',      // Close when moving to non-dialog step
      onDismissGoToStepId: 'settings-trigger', // Where to go if user closes dialog
    },
  },
  steps: [
    { id: 'settings-trigger', target: '#settings-btn', content: 'Click here' },
    { id: 'settings-tab1', dialogId: 'settings', target: '#tab1', content: 'First tab' },
    { id: 'settings-tab2', dialogId: 'settings', target: '#tab2', content: 'Second tab' },
    // Dialog stays open for consecutive steps with same dialogId
    { id: 'done', target: 'screen', content: 'All done' },
    // Dialog auto-closes when entering 'done' (no dialogId)
  ],
})

// 2. Use hook in your dialog component
function SettingsDialog({ children }) {
  const { dialogProps, contentProps } = useRadixTourDialog({ dialogId: 'settings' })

  return (
    <Dialog.Root {...dialogProps}>
      <Dialog.Trigger data-tour-target="settings-trigger">Settings</Dialog.Trigger>
      <Dialog.Portal>
        <Dialog.Overlay />
        <Dialog.Content {...contentProps} data-tour-target="settings-dialog">
          {children}
        </Dialog.Content>
      </Dialog.Portal>
    </Dialog.Root>
  )
}
tsx
import { createFlow } from '@flowsterix/core'
import { useRadixTourDialog } from '@flowsterix/react'
import * as Dialog from '@radix-ui/react-dialog'

// 1. 在引导流定义中配置对话框
const flow = createFlow({
  id: 'onboarding',
  version: { major: 1, minor: 0 },
  dialogs: {
    settings: {
      autoOpen: true,                    // 进入对话框步骤时自动打开
      autoClose: 'differentDialog',      // 切换到非对话框步骤时自动关闭
      onDismissGoToStepId: 'settings-trigger', // 用户关闭对话框后跳转的步骤ID
    },
  },
  steps: [
    { id: 'settings-trigger', target: '#settings-btn', content: '点击此处' },
    { id: 'settings-tab1', dialogId: 'settings', target: '#tab1', content: '第一个标签页' },
    { id: 'settings-tab2', dialogId: 'settings', target: '#tab2', content: '第二个标签页' },
    // 连续步骤使用相同dialogId时,对话框保持打开
    { id: 'done', target: 'screen', content: '全部完成' },
    // 进入'done'步骤时(无dialogId),对话框自动关闭
  ],
})

// 2. 在对话框组件中使用钩子
function SettingsDialog({ children }) {
  const { dialogProps, contentProps } = useRadixTourDialog({ dialogId: 'settings' })

  return (
    <Dialog.Root {...dialogProps}>
      <Dialog.Trigger data-tour-target="settings-trigger">设置</Dialog.Trigger>
      <Dialog.Portal>
        <Dialog.Overlay />
        <Dialog.Content {...contentProps} data-tour-target="settings-dialog">
          {children}
        </Dialog.Content>
      </Dialog.Portal>
    </Dialog.Root>
  )
}

Dialog Configuration Options

对话框配置选项

tsx
dialogs: {
  myDialog: {
    // Auto-open behavior (default: true for both)
    autoOpen: {
      onEnter: true,   // Open when entering a step with this dialogId
      onResume: true,  // Open when resuming to a step with this dialogId
    },
    // Or disable all auto-open:
    autoOpen: false,

    // Auto-close behavior (default: 'differentDialog')
    autoClose: 'differentDialog', // Close when next step has different/no dialogId
    autoClose: 'always',          // Always close on step exit
    autoClose: 'never',           // Manual close only

    // Required: where to navigate when user dismisses dialog
    onDismissGoToStepId: 'some-step-id',
  },
}
tsx
dialogs: {
  myDialog: {
    // 自动打开行为(默认:两者均为true)
    autoOpen: {
      onEnter: true,   // 进入带有此dialogId的步骤时打开
      onResume: true,  // 恢复到带有此dialogId的步骤时打开
    },
    // 或者禁用所有自动打开:
    autoOpen: false,

    // 自动关闭行为(默认:'differentDialog')
    autoClose: 'differentDialog', // 下一步骤的dialogId不同或无时关闭
    autoClose: 'always',          // 离开步骤时始终关闭
    autoClose: 'never',           // 仅手动关闭

    // 必填:用户关闭对话框后跳转的步骤ID
    onDismissGoToStepId: 'some-step-id',
  },
}

Focus Management: useRadixDialogAdapter

焦点管理:useRadixDialogAdapter

For dialogs without tour integration that still need focus handling during tours:
tsx
import { useRadixDialogAdapter } from '@flowsterix/react'

function SimpleDialog({ children }) {
  const { dialogProps, contentProps } = useRadixDialogAdapter({
    disableEscapeClose: true,
  })

  return (
    <Dialog.Root {...dialogProps}>
      <Dialog.Content {...contentProps}>{children}</Dialog.Content>
    </Dialog.Root>
  )
}
对于未集成引导流程但仍需在引导期间处理焦点的对话框:
tsx
import { useRadixDialogAdapter } from '@flowsterix/react'

function SimpleDialog({ children }) {
  const { dialogProps, contentProps } = useRadixDialogAdapter({
    disableEscapeClose: true,
  })

  return (
    <Dialog.Root {...dialogProps}>
      <Dialog.Content {...contentProps}>{children}</Dialog.Content>
    </Dialog.Root>
  )
}

Lifecycle Hooks

生命周期钩子

Lifecycle hooks synchronize UI state with tour progression. Use them when steps target elements inside collapsible panels, modals, drawers, or other dynamic UI.
生命周期钩子用于同步UI状态与引导流程进度。当步骤目标位于可折叠面板、模态框、抽屉或其他动态UI内部时,请使用这些钩子。

When to Use Each Hook

各钩子的适用场景

HookFires WhenPurpose
onEnter
Step activates (fresh start)Open UI, prepare state
onResume
Step restores from storageRestore UI after page reload
onExit
Leaving step (next/back/skip)Clean up, close UI
钩子触发时机用途
onEnter
步骤激活时(首次进入)打开UI、准备状态
onResume
从存储恢复步骤时页面重新加载后恢复UI状态
onExit
离开步骤时(下一步/上一步/跳过)清理、关闭UI

Common Patterns

常见模式

1. Opening/Closing Drawers & Menus
tsx
// Helper functions to toggle menu state
const ensureMenuOpen = () => {
  const panel = document.querySelector('[data-tour-target="menu-panel"]')
  if (!(panel instanceof HTMLElement)) return
  const isClosed = panel.classList.contains('-translate-x-full')
  if (isClosed) {
    document.querySelector('[data-tour-target="menu-button"]')?.click()
  }
}

const ensureMenuClosed = () => {
  const panel = document.querySelector('[data-tour-target="menu-panel"]')
  if (!(panel instanceof HTMLElement)) return
  const isClosed = panel.classList.contains('-translate-x-full')
  if (!isClosed) {
    panel.querySelector('[aria-label="Close menu"]')?.click()
  }
}
2. Step Targeting Element Inside Drawer
tsx
{
  id: 'menu-link',
  target: { selector: '[data-tour-target="api-link"]' },
  onEnter: () => ensureMenuOpen(),   // Open drawer on fresh entry
  onResume: () => ensureMenuOpen(),  // Open drawer on page reload
  onExit: () => ensureMenuClosed(),  // Close drawer when leaving
  advance: [{ type: 'route', to: '/api-demo' }],
  content: (
    <StepContent>
      <StepTitle>API Demo</StepTitle>
      <StepText>Click to explore the API features.</StepText>
    </StepContent>
  ),
}
3. Expanding Nested Accordions
tsx
const ensureAccordionExpanded = () => {
  ensureMenuOpen() // Parent must be open first
  const submenu = document.querySelector('[data-tour-target="submenu"]')
  if (submenu) return // Already expanded
  document.querySelector('[data-tour-target="accordion-toggle"]')?.click()
}

{
  id: 'submenu-item',
  target: { selector: '[data-tour-target="submenu"]' },
  onResume: () => ensureAccordionExpanded(),
  content: ...
}
4. Closing UI When Moving Away
tsx
{
  id: 'feature-grid',
  target: { selector: '#feature-grid' },
  onEnter: () => {
    setTimeout(() => ensureMenuClosed(), 0) // Allow menu click to register first
  },
  onResume: () => ensureMenuClosed(),
  content: ...
}
1. 打开/关闭抽屉与菜单
tsx
// 切换菜单状态的辅助函数
const ensureMenuOpen = () => {
  const panel = document.querySelector('[data-tour-target="menu-panel"]')
  if (!(panel instanceof HTMLElement)) return
  const isClosed = panel.classList.contains('-translate-x-full')
  if (isClosed) {
    document.querySelector('[data-tour-target="menu-button"]')?.click()
  }
}

const ensureMenuClosed = () => {
  const panel = document.querySelector('[data-tour-target="menu-panel"]')
  if (!(panel instanceof HTMLElement)) return
  const isClosed = panel.classList.contains('-translate-x-full')
  if (!isClosed) {
    panel.querySelector('[aria-label="Close menu"]')?.click()
  }
}
2. 目标位于抽屉内的步骤
tsx
{
  id: 'menu-link',
  target: { selector: '[data-tour-target="api-link"]' },
  onEnter: () => ensureMenuOpen(),   // 首次进入时打开抽屉
  onResume: () => ensureMenuOpen(),  // 页面重新加载后打开抽屉
  onExit: () => ensureMenuClosed(),  // 离开时关闭抽屉
  advance: [{ type: 'route', to: '/api-demo' }],
  content: (
    <StepContent>
      <StepTitle>API演示</StepTitle>
      <StepText>点击此处探索API功能。</StepText>
    </StepContent>
  ),
}
3. 展开嵌套折叠面板
tsx
const ensureAccordionExpanded = () => {
  ensureMenuOpen() // 首先确保父菜单已打开
  const submenu = document.querySelector('[data-tour-target="submenu"]')
  if (submenu) return // 已展开
  document.querySelector('[data-tour-target="accordion-toggle"]')?.click()
}

{
  id: 'submenu-item',
  target: { selector: '[data-tour-target="submenu"]' },
  onResume: () => ensureAccordionExpanded(),
  content: ...
}
4. 切换到其他步骤时关闭UI
tsx
{
  id: 'feature-grid',
  target: { selector: '#feature-grid' },
  onEnter: () => {
    setTimeout(() => ensureMenuClosed(), 0) // 等待菜单点击事件完成注册
  },
  onResume: () => ensureMenuClosed(),
  content: ...
}

Critical Rules

关键规则

  1. Always implement
    onResume
    when you have
    onEnter
    - Users may reload the page mid-tour
  2. Check state before acting - Don't toggle already-open menus
  3. Use
    setTimeout
    for sequential actions
    - Give previous clicks time to register
  4. Keep hooks idempotent - Safe to call multiple times
  1. 当实现
    onEnter
    时,务必同时实现
    onResume
    - 用户可能在引导过程中重新加载页面
  2. 操作前检查状态 - 不要重复切换已打开的菜单
  3. 使用
    setTimeout
    处理连续操作
    - 给之前的点击事件留出注册时间
  4. 保持钩子幂等性 - 多次调用也能保证安全

Step Placements

步骤位置选项

'auto' | 'top' | 'bottom' | 'left' | 'right' |
'top-start' | 'top-end' | 'bottom-start' | 'bottom-end' |
'left-start' | 'left-end' | 'right-start' | 'right-end' |
'auto-start' | 'auto-end'
'auto' | 'top' | 'bottom' | 'left' | 'right' |
'top-start' | 'top-end' | 'bottom-start' | 'bottom-end' |
'left-start' | 'left-end' | 'right-start' | 'right-end' |
'auto-start' | 'auto-end'

Route Gating

路由限制

Steps can be constrained to specific routes using the
route
property. The flow automatically pauses when the user navigates away and resumes when they return.
可以通过
route
属性将步骤限制在特定路由上。当用户导航到其他路由时,引导流会自动暂停,返回时自动恢复。

Route Mismatch Behavior

路由不匹配时的行为

tsx
{
  id: 'dashboard-feature',
  target: { selector: '[data-tour-target="widget"]' },
  route: '/dashboard',  // Step only active on /dashboard
  content: <p>This widget shows your stats</p>,
}
Behavior when user navigates away from
/dashboard
:
  1. Flow pauses immediately (overlay disappears)
  2. User can browse other pages freely
  3. When user returns to
    /dashboard
    , flow auto-resumes
tsx
{
  id: 'dashboard-feature',
  target: { selector: '[data-tour-target="widget"]' },
  route: '/dashboard',  // 仅在/dashboard路由上激活
  content: <p>此小组件展示您的统计数据</p>,
}
用户离开
/dashboard
路由时的行为:
  1. 引导流立即暂停(遮罩消失)
  2. 用户可以自由浏览其他页面
  3. 当用户返回
    /dashboard
    时,引导流自动恢复

Missing Target Behavior (No Route Defined)

未定义路由时的目标缺失行为

When a step has no
route
property
and the target element is missing:
  1. Grace period (400ms) - Allows async elements to mount
  2. If still missing → Flow pauses
  3. When user navigates to a different page → Flow resumes and re-checks
  4. If target found → Flow continues
  5. If still missing → Grace period → Pause again
This prevents showing broken UI when users accidentally navigate away.
当步骤未定义
route
属性
且目标元素缺失时:
  1. 宽限期(400ms)- 允许异步元素完成挂载
  2. 如果仍缺失 → 引导流暂停
  3. 当用户导航到其他页面 → 引导流恢复并重新检查
  4. 如果找到目标 → 引导流继续
  5. 如果仍缺失 → 宽限期 → 再次暂停
这可以避免用户意外导航时显示损坏的UI。

Route Patterns

路由匹配模式

tsx
// Exact match
route: '/dashboard'

// Regex pattern
route: /^\/users\/\d+$/

// With path parameters (use regex)
route: /^\/products\/[^/]+$/
tsx
// 精确匹配
route: '/dashboard'

// 正则表达式匹配
route: /^\/users\/\d+$/

// 带路径参数(使用正则)
route: /^\/products\/[^/]+$/

Internationalization (i18n)

国际化(i18n)

All user-facing text can be customized via the
labels
prop on
TourProvider
.
所有面向用户的文本都可以通过
TourProvider
labels
属性自定义。

Available Labels

可用标签

tsx
<TourProvider
  flows={[...]}
  labels={{
    // Button labels
    back: 'Back',
    next: 'Next',
    finish: 'Finish',
    skip: 'Skip tour',
    holdToConfirm: 'Hold to confirm',

    // Aria labels for screen readers
    ariaStepProgress: ({ current, total }) => `Step ${current} of ${total}`,
    ariaTimeRemaining: ({ ms }) => `${Math.ceil(ms / 1000)} seconds remaining`,
    ariaDelayProgress: 'Auto-advance progress',

    // Visible formatters
    formatTimeRemaining: ({ ms }) => `${Math.ceil(ms / 1000)}s remaining`,

    // Target issue messages (shown when target element is problematic)
    targetIssue: {
      missingTitle: 'Target not visible',
      missingBody: 'The target element is not currently visible...',
      missingHint: 'Showing the last known position until the element returns.',
      hiddenTitle: 'Target not visible',
      hiddenBody: 'The target element is not currently visible...',
      hiddenHint: 'Showing the last known position until the element returns.',
      detachedTitle: 'Target left the page',
      detachedBody: 'Navigate back to the screen that contains this element...',
    },
  }}
>
tsx
<TourProvider
  flows={[...]}
  labels={{
    // 按钮文本
    back: '返回',
    next: '下一步',
    finish: '完成',
    skip: '跳过引导',
    holdToConfirm: '长按确认',

    // 屏幕阅读器的Aria标签
    ariaStepProgress: ({ current, total }) => `${current}步,共${total}`,
    ariaTimeRemaining: ({ ms }) => `剩余${Math.ceil(ms / 1000)}`,
    ariaDelayProgress: '自动推进进度',

    // 可见的格式化文本
    formatTimeRemaining: ({ ms }) => `剩余${Math.ceil(ms / 1000)}`,

    // 目标元素异常提示信息(当目标元素出现问题时显示)
    targetIssue: {
      missingTitle: '目标不可见',
      missingBody: '目标元素当前不可见...',
      missingHint: '显示最后已知位置,直到元素重新出现。',
      hiddenTitle: '目标不可见',
      hiddenBody: '目标元素当前不可见...',
      hiddenHint: '显示最后已知位置,直到元素重新出现。',
      detachedTitle: '目标已离开页面',
      detachedBody: '请导航回包含此元素的页面...',
    },
  }}
>

German Example

德语示例

tsx
const germanLabels = {
  back: 'Zurück',
  next: 'Weiter',
  finish: 'Fertig',
  skip: 'Tour überspringen',
  holdToConfirm: 'Gedrückt halten zum Bestätigen',
  ariaStepProgress: ({ current, total }) => `Schritt ${current} von ${total}`,
  targetIssue: {
    missingTitle: 'Ziel nicht sichtbar',
    missingBody: 'Das Zielelement ist derzeit nicht sichtbar.',
    detachedTitle: 'Ziel hat die Seite verlassen',
    detachedBody: 'Navigieren Sie zurück zur Seite mit diesem Element.',
    // ... other labels
  },
}

<TourProvider flows={[...]} labels={germanLabels}>
tsx
const germanLabels = {
  back: 'Zurück',
  next: 'Weiter',
  finish: 'Fertig',
  skip: 'Tour überspringen',
  holdToConfirm: 'Gedrückt halten zum Bestätigen',
  ariaStepProgress: ({ current, total }) => `Schritt ${current} von ${total}`,
  targetIssue: {
    missingTitle: 'Ziel nicht sichtbar',
    missingBody: 'Das Zielelement ist derzeit nicht sichtbar.',
    detachedTitle: 'Ziel hat die Seite verlassen',
    detachedBody: 'Navigieren Sie zurück zur Seite mit diesem Element.',
    // ... 其他标签
  },
}

<TourProvider flows={[...]} labels={germanLabels}>

DevTools

开发者工具

The
@flowsterix/react/devtools
subpath provides development tools for building and debugging tours:
  • Steps tab - Visual element picker to capture tour steps and export JSON for AI
  • Flows tab - View and edit stored flow states for debugging
@flowsterix/react/devtools
子路径提供了用于构建和调试引导流程的开发工具:
  • 步骤标签页 - 可视化元素选取器,用于捕获引导步骤并导出JSON供AI使用
  • 引导流标签页 - 查看和编辑存储的引导流状态以进行调试

Setup

配置步骤

tsx
import { DevToolsProvider } from '@flowsterix/react/devtools'

function App() {
  return (
    <TourProvider flows={[...]}>
      <DevToolsProvider enabled={process.env.NODE_ENV === 'development'}>
        <YourApp />
      </DevToolsProvider>
    </TourProvider>
  )
}
tsx
import { DevToolsProvider } from '@flowsterix/react/devtools'

function App() {
  return (
    <TourProvider flows={[...]}>
      <DevToolsProvider enabled={process.env.NODE_ENV === 'development'}>
        <YourApp />
      </DevToolsProvider>
    </TourProvider>
  )
}

Steps Tab (Element Grabber)

步骤标签页(元素选取器)

  1. Press
    Ctrl+Shift+G
    to toggle grab mode
  2. Click elements to capture as tour steps
  3. Drag to reorder steps in the panel
  4. Click "Copy" to export JSON for AI
  1. Ctrl+Shift+G
    切换选取模式
  2. 点击元素以捕获为引导步骤
  3. 在面板中拖动以重新排序步骤
  4. 点击“复制”导出JSON供AI使用

Export Format

导出格式

json
{
  "version": "1.0",
  "steps": [
    {
      "order": 0,
      "element": "<button class=\"btn-primary\">Get Started</button>",
      "componentTree": ["button", "Button", "Header", "App"]
    }
  ]
}
json
{
  "version": "1.0",
  "steps": [
    {
      "order": 0,
      "element": "<button class=\"btn-primary\">Get Started</button>",
      "componentTree": ["button", "Button", "Header", "App"]
    }
  ]
}

AI Workflow

AI工作流

  1. Capture elements with devtools
  2. Copy the JSON export
  3. Paste into AI with prompt: "Create a Flowsterix tour flow for these elements"
  4. AI generates flow definition with proper
    data-tour-target
    selectors
  1. 使用开发者工具捕获元素
  2. 复制JSON导出内容
  3. 将其粘贴到AI中,并提示:“为这些元素创建一个Flowsterix引导流”
  4. AI生成带有正确
    data-tour-target
    选择器的引导流定义

Keyboard Shortcuts

键盘快捷键

ShortcutAction
Ctrl+Shift+G
Toggle grab mode
Esc
Cancel grab mode
Ctrl+Shift+M
Collapse/expand panel
快捷键操作
Ctrl+Shift+G
切换选取模式
Esc
取消选取模式
Ctrl+Shift+M
折叠/展开面板

Flows Tab

引导流标签页

The Flows tab shows all registered flows and their stored state. Use it to:
  • View flow status - See which flows are idle, running, paused, completed, or cancelled
  • Inspect state - Check current step index, version, and step ID
  • Edit flow state - Modify stored JSON directly (useful for debugging)
  • Delete flow state - Clear stored state to reset a flow (cancels if running)
Features:
  • Live updates when active flow state changes
  • Shows "Active" badge for currently running flow
  • Confirmation required before deleting
Use cases:
  • Reset a flow to test from beginning
  • Debug unexpected flow behavior by inspecting stored state
  • Manually advance a stuck flow by editing
    stepIndex
  • Clear completed flows to re-trigger
    autoStart
引导流标签页显示所有已注册的引导流及其存储的状态。可用于:
  • 查看引导流状态 - 查看哪些引导流处于空闲、运行、暂停、完成或取消状态
  • 检查状态详情 - 查看当前步骤索引、版本和步骤ID
  • 编辑引导流状态 - 直接修改存储的JSON(调试时非常有用)
  • 删除引导流状态 - 清除存储的状态以重置引导流(如果正在运行则取消)
功能特性:
  • 引导流状态变更时实时更新
  • 为当前运行的引导流显示“Active”徽章
  • 删除前需要确认
使用场景:
  • 重置引导流以从头开始测试
  • 通过检查存储的状态调试意外的引导流行为
  • 通过编辑
    stepIndex
    手动推进卡住的引导流
  • 清除已完成的引导流以重新触发
    autoStart

Additional Resources

额外资源

  • CSS Setup - Required shadcn CSS variables
  • Flow Patterns - Targeting, advance rules, waitFor
  • React Integration - Hooks, events, step content
  • Router Adapters - TanStack, React Router, Next.js
  • Advanced Patterns - Versions, storage, migrations
  • Mobile Support - Mobile drawer, snap points, gestures
  • CSS设置 - 所需的shadcn CSS变量
  • 引导流模式 - 目标定位、推进规则、waitFor
  • React集成 - 钩子、事件、步骤内容
  • 路由适配器 - TanStack、React Router、Next.js
  • 高级模式 - 版本、存储、迁移
  • 移动端支持 - 移动端抽屉、停靠点、手势

Examples

示例

  • Basic Flow - Simple 3-step onboarding
  • Async Content - waitFor patterns
  • Lifecycle Hooks - UI synchronization
  • Router Sync - All 4 router adapters
  • 基础引导流 - 简单的3步入门引导
  • 异步内容 - waitFor模式
  • 生命周期钩子 - UI同步
  • 路由同步 - 所有4种路由适配器