Skip to main content

How to track product tour drop-off points

Track where users abandon your product tours step by step. Build a drop-off funnel with Tour Kit's analytics events, PostHog, and Mixpanel.

DomiDex
DomiDexCreator of Tour Kit
April 11, 202614 min read
Share
How to track product tour drop-off points

How to track product tour drop-off points

You built a 7-step product tour. Users start it. But something between step 3 and step 4 kills momentum, and you have no idea what. Without step-level tracking, your tour analytics are a black box: you know the completion rate, but not where users bail.

Most product tour libraries fire a single tour_completed event and call it a day. That tells you nothing about the 39% of users who started but never finished. The average product tour completion rate is just 61%, according to Chameleon's analysis of 15 million tour interactions. The fix isn't more events. It's the right events at the right granularity, piped into a funnel your team can actually read.

This tutorial walks through building step-level drop-off tracking with Tour Kit's @tour-kit/analytics package. You'll wire up step_viewed, step_completed, and tour_abandoned events, then visualize the funnel in PostHog or Mixpanel. By the end, you'll know exactly which step hemorrhages users and have the data to fix it.

npm install @tourkit/core @tourkit/react @tourkit/analytics

Prerequisites

  • React 18+ or React 19 project
  • A Tour Kit tour with 3+ steps already working
  • An analytics tool account (PostHog, Mixpanel, Amplitude, or GA4)
  • Basic familiarity with React hooks and TypeScript

What is product tour drop-off tracking?

Product tour drop-off tracking measures exactly where users stop progressing through a guided tour. Instead of treating the tour as a pass/fail binary, you instrument each step transition as a discrete event and compare how many users reach step N versus step N+1. The result is a conversion funnel specific to your tour, showing the precise step where engagement collapses and giving your team a clear target for improvement.

The formula is straightforward:

Step drop-off rate = ((Users at step N - Users at step N+1) / Users at step N) × 100

A 5-step tour with 1,000 starters and 340 completions has a 66% cumulative drop-off rate. But that number is useless without knowing whether the loss happens at step 2 (bad targeting) or step 4 (confusing content). Step-level tracking gives you that answer.

Why drop-off tracking matters more than completion rate

Product tour drop-off tracking at the step level reveals problems that completion rate alone hides entirely, because a single aggregate number can't distinguish between a tour with one broken step and a tour that gradually loses users across every transition.

Completion rate is a vanity metric when used alone. A 74% completion rate on a 4-step tour sounds healthy until you discover that 38% of starters bail after the first screen. You're losing users before they see the value your tour is supposed to deliver.

Chameleon's benchmark data across 550 million tour interactions shows a striking pattern: 4-step tours average 74% completion, but 5-step tours crater to 34%. That single extra step cuts your completion rate in half. Without step-level drop-off data, you'd never know whether to shorten your tour or fix a specific step's content.

Here's what step-level data actually reveals:

MetricWhat it tells youAction it enables
Completion rate alone"Users finish or don't"Nothing specific
Step-level drop-off"Step 3 loses 40% of remaining users"Rewrite step 3 content, add interactive element
Time-on-step"Users spend 12s on step 2 vs 2s average"Step 2 is confusing, so simplify or split it
Abandonment context"Users close the tab during step 4"Step 4's target element may be broken or off-screen

As the Amplitude team puts it: "Data is the linchpin of any successful product tour. Without it, you're just guessing."

How to calculate drop-off rate (formula and example)

The per-step drop-off rate tells you what percentage of users who reached a given step failed to advance, and Userpilot's onboarding research shows that SaaS products typically see 30-50% cumulative drop-off across their onboarding flows, which means most tours have at least one step losing a significant chunk of users. Here's the formula applied to a realistic 5-step tour:

Step drop-off rate = ((Users at step N - Users at step N+1) / Users at step N) × 100

Applied to a hypothetical onboarding tour with 1,000 starters:

StepUsers reachedDrop-off rateDiagnosis
1. Welcome1,00015%Normal, some users dismiss immediately
2. Create project8508%Healthy transition
3. Invite team78242%Problem: users resist social actions early
4. Run first test45412%Recoverable after step 3 fix
5. View dashboard399Final step (39.9% overall completion)

Step 3 is the problem. A 42% step-level drop-off where users are asked to invite teammates before they've seen the product's value. Moving that step to a secondary onboarding flow (or making it skippable) could recover hundreds of users per cohort.

Benchmark ranges based on Chameleon's 15M interaction dataset:

  • Healthy step drop-off: under 15%
  • Needs attention: 15-30%
  • Critical: above 30%

Step 1: Set up the analytics provider

Tour Kit's @tour-kit/analytics package provides a plugin-based tracker that fires structured events for every tour lifecycle moment, including step_viewed, step_completed, tour_skipped, and tour_abandoned with step index and duration metadata attached automatically. Start by wrapping your app with the analytics provider and registering at least one plugin.

// src/providers/TourAnalytics.tsx
import { AnalyticsProvider } from '@tourkit/analytics'
import { PostHogPlugin } from '@tourkit/analytics/plugins/posthog'

const plugins = [
  PostHogPlugin({
    // PostHog SDK must be initialized before this runs
    client: typeof window !== 'undefined' ? window.posthog : undefined,
  }),
]

export function TourAnalyticsWrapper({ children }: { children: React.ReactNode }) {
  return (
    <AnalyticsProvider plugins={plugins}>
      {children}
    </AnalyticsProvider>
  )
}

Swap PostHogPlugin for MixpanelPlugin, AmplitudePlugin, or GoogleAnalyticsPlugin depending on your stack. Every plugin implements the same AnalyticsPlugin interface (track(), identify(), page()), and events dispatch to all registered plugins in order. Run a ConsolePlugin alongside PostHog during development to see events in your browser console.

Step 2: Instrument your tour with step-level events

Tour Kit's analytics tracker fires step_viewed and step_completed events automatically when you use the useAnalytics hook, but product tour drop-off tracking also requires capturing when users leave without finishing, which is where the tour_abandoned event comes in. Wire up your tour's onStepChange, onComplete, and onSkip callbacks to the tracker.

// src/tours/OnboardingTour.tsx
import { useTour } from '@tourkit/react'
import { useAnalytics } from '@tourkit/analytics'

export function OnboardingTour() {
  const analytics = useAnalytics()

  const tour = useTour({
    tourId: 'onboarding-v2',
    steps: [
      { id: 'welcome', target: '#welcome-banner', content: 'Welcome to Acme!' },
      { id: 'create-project', target: '#new-project-btn', content: 'Start by creating a project.' },
      { id: 'invite-team', target: '#invite-btn', content: 'Invite your teammates.' },
      { id: 'run-test', target: '#run-test-btn', content: 'Run your first test.' },
      { id: 'dashboard', target: '#dashboard-link', content: 'Check your results here.' },
    ],
    onStepChange: (step, index, context) => {
      // Fires on every step transition — the backbone of drop-off tracking
      analytics.stepViewed(
        'onboarding-v2',
        step.id,
        index,
        context.tour.steps.length
      )
    },
    onComplete: () => {
      analytics.tourCompleted('onboarding-v2')
    },
    onSkip: (context) => {
      // User clicked "Skip tour" — different from abandonment
      analytics.tourSkipped(
        'onboarding-v2',
        context.currentStepIndex,
        context.tour.steps[context.currentStepIndex]?.id
      )
    },
  })

  return <>{tour.component}</>
}

The distinction between tour_skipped and tour_abandoned matters for your funnel. A skip is intentional (user clicked a button). Abandonment happens when the user navigates away, closes the tab, or the tour element disappears. Tour Kit detects abandonment via beforeunload and MutationObserver events automatically.

Step 3: Add time-on-step tracking for deeper analysis

Knowing where users drop off is step one, but knowing how long they hesitate before dropping gives you the "why," because a user who spends 2 seconds on a step and leaves had a fundamentally different problem than one who stares at it for 45 seconds. Tour Kit's tracker records duration on every event automatically (it starts a timer on stepViewed and stops it on stepCompleted or abandonment). You can enrich this with custom metadata:

// src/hooks/useDropOffTracking.ts
import { useCallback, useRef } from 'react'
import { useAnalytics } from '@tourkit/analytics'

export function useDropOffTracking(tourId: string) {
  const analytics = useAnalytics()
  const stepTimers = useRef<Map<string, number>>(new Map())

  const onStepEnter = useCallback(
    (stepId: string, stepIndex: number, totalSteps: number) => {
      stepTimers.current.set(stepId, Date.now())
      analytics.stepViewed(tourId, stepId, stepIndex, totalSteps, {
        enteredAt: new Date().toISOString(),
        percentComplete: Math.round((stepIndex / totalSteps) * 100),
      })
    },
    [analytics, tourId]
  )

  const onStepExit = useCallback(
    (stepId: string, stepIndex: number, totalSteps: number) => {
      const enterTime = stepTimers.current.get(stepId)
      const timeOnStep = enterTime ? Date.now() - enterTime : 0

      analytics.stepCompleted(tourId, stepId, stepIndex, totalSteps, {
        timeOnStepMs: timeOnStep,
        timeOnStepSeconds: Math.round(timeOnStep / 1000),
      })

      stepTimers.current.delete(stepId)
    },
    [analytics, tourId]
  )

  return { onStepEnter, onStepExit }
}

Now every step event carries timeOnStepMs in its metadata. When you build the funnel in PostHog or Mixpanel, you can break down drop-off by time spent, separating "confused users" (long dwell time, 15+ seconds) from "uninterested users" (instant dismissal, under 2 seconds).

Step 4: Build the funnel in your analytics tool

With step-level events flowing into your analytics backend, the next task is building a funnel chart that turns raw step_viewed and step_completed counts into the step-by-step drop-off visualization your product team can act on without needing to write SQL. Both PostHog and Mixpanel support ordered funnel analysis out of the box.

PostHog funnel setup

In PostHog, create a new Insight with type "Funnel":

  1. Add step_viewed as the first event, filtered by tourId = onboarding-v2 and stepIndex = 0
  2. Add another step_viewed event for each step index (1, 2, 3, 4)
  3. Alternatively, use step_completed events if you want to track step exits rather than entries
  4. Set the funnel window to 30 minutes (most tours complete in under 5 minutes, so this catches stragglers)
  5. Break down by stepId to see named steps instead of index numbers

PostHog's HogQL also lets you query step timing with raw SQL if the visual funnel isn't enough.

Mixpanel funnel setup

Mixpanel's funnel builder handles ordered step sequences natively:

  1. Create a new Funnel report
  2. Add step_viewed events in order, each filtered by stepIndex (0 through N)
  3. Enable "time to convert" to see median time between steps
  4. Use "Flows" to discover where users actually go after dropping off (did they close the tab? Navigate to a different page? Start a different tour?)

Mixpanel's multi-path funnel feature is particularly useful here. It shows you what users did instead of completing the next step, not just that they left.

Step 5: Set up drop-off alerts

Tracking data that nobody looks at is worse than no tracking at all, because it gives you false confidence that you're measuring things while problems go unnoticed for weeks. Set a threshold alert so your team gets notified the moment a specific step's drop-off rate spikes above your baseline.

// src/analytics/drop-off-monitor.ts
import type { TourEvent, AnalyticsPlugin } from '@tourkit/analytics'

export function createDropOffMonitorPlugin(options: {
  threshold: number // e.g., 0.3 for 30%
  minimumSample: number // don't alert on tiny samples
  onAlert: (alert: { stepId: string; dropOffRate: number }) => void
}): AnalyticsPlugin {
  const stepCounts = new Map<string, { viewed: number; completed: number }>()

  return {
    name: 'drop-off-monitor',
    track(event: TourEvent) {
      if (!event.stepId) return
      const counts = stepCounts.get(event.stepId) ?? { viewed: 0, completed: 0 }

      if (event.eventName === 'step_viewed') counts.viewed++
      if (event.eventName === 'step_completed') {
        counts.completed++
        const dropOffRate = 1 - counts.completed / counts.viewed
        if (counts.viewed >= options.minimumSample && dropOffRate > options.threshold) {
          options.onAlert({ stepId: event.stepId, dropOffRate })
        }
      }
      stepCounts.set(event.stepId, counts)
    },
  }
}

Register it alongside your main analytics plugin. The onAlert callback can post to Slack, PagerDuty, or your internal dashboard.

This runs client-side for real-time monitoring. For production alerting with larger sample sizes, run the aggregation server-side against your PostHog or Mixpanel data using their APIs.

Benchmarks: what good looks like

Industry benchmarks from Chameleon's published analysis of 550 million tour data points and Userpilot's onboarding research give you concrete targets for each metric, so you can tell whether your tour's drop-off pattern is normal or a sign of deeper UX problems. Here's what to aim for:

MetricNeeds workAcceptableStrong
Overall completion rateBelow 40%40-65%Above 65%
Per-step drop-offAbove 30%15-30%Below 15%
First-step abandonmentAbove 25%10-25%Below 10%
Average time-on-stepOver 20s or under 1s3-12s5-8s
Tour length7+ steps (16% completion)5 steps (34%)3-4 steps (72-74%)

The data point that should shape every tour you build: tours with 4 steps average 74% completion, but adding just one more step drops that to 34%. If your tour has more than 5 steps, split it.

Progress indicators boost completion by 12% and reduce dismissal by 20%. Click-triggered (self-serve) tours complete at 67% versus 31% for delay-triggered ones. Trigger method matters as much as content.

5 ways to improve your drop-off rate

After instrumenting product tour drop-off tracking and identifying your worst-performing steps, these five fixes consistently produce the largest improvements based on the benchmark data above and patterns we've observed testing Tour Kit's own onboarding flows.

1. Shorten your tour. If you have 5+ steps, split into a primary tour (3-4 steps covering the "aha moment") and a secondary tour triggered by a checklist or feature adoption nudge. Tour Kit's @tour-kit/checklists package handles this pattern natively.

2. Move social actions later. "Invite your team" steps consistently show the highest drop-off in onboarding tours. Users want to experience value before committing socially. Move these to a separate flow triggered after the user completes their first core action.

3. Add progress indicators. A visible step counter (3/5) gives users a sense of completion momentum. The 12% completion boost from progress indicators, as measured in Chameleon's dataset, is one of the easiest wins in onboarding.

4. Switch from delay triggers to click triggers. Self-serve tours (user clicks "Show me around") complete at more than double the rate of auto-triggered tours. If your tour fires on page load after a 3-second delay, test a button trigger instead.

5. Fix the content, not the format. When a specific step shows high time-on-step and high drop-off, the content is confusing, not the tour mechanism. Rewrite the copy, add a screenshot or GIF, or split the step into two simpler ones.

Tools for drop-off tracking

Tour Kit's @tour-kit/analytics package works with any analytics tool that accepts custom events, and the choice of analytics backend matters because funnel visualization quality varies significantly between tools. PostHog, Mixpanel, Amplitude, and GA4 all support ordered funnels, but their step-timing and path analysis features differ in ways that affect how actionable your drop-off data ends up being.

ToolFunnel supportBest forTour Kit plugin
PostHogOrdered funnels, HogQL for custom queriesSelf-hosted, privacy-first teamsPostHogPlugin
MixpanelMulti-path funnels, time constraintsTeams who need "what happened instead" analysisMixpanelPlugin
AmplitudeOrdered/unordered funnels, cohort breakdownEnterprise teams with complex segmentation needsAmplitudePlugin
GA4Basic funnel explorationTeams already on Google AnalyticsGoogleAnalyticsPlugin

For serious funnel work, PostHog or Mixpanel are the strongest choices. GA4's funnel exploration works but lacks the step-timing and path analysis features that make drop-off diagnosis actionable.

One honest limitation: Tour Kit doesn't include a built-in dashboard for tour analytics. You're responsible for the visualization layer. The upside is your tour data lives in your existing analytics stack alongside every other product metric, with no separate login, no data silo, and no per-MAU pricing surprises from a dedicated onboarding SaaS tool.

Common issues and troubleshooting

When setting up product tour drop-off tracking, these three issues come up most often during initial integration, and each one can make your funnel data look broken even when the events are firing correctly.

"Events fire but the funnel shows 0 conversions"

Check your funnel's time window. If your funnel requires all steps within 5 minutes but some users take 15 minutes (they got distracted mid-tour), widen the window to 30 minutes or 1 hour. Also verify that the tourId property matches exactly across all events. A typo in one step breaks the entire funnel.

"Step 1 shows fewer users than tour_started"

This happens when the tour starts but the first step's target element hasn't rendered yet. Tour Kit waits for the element by default, but if your component uses React.lazy, the step_viewed event fires only after the element appears. The gap between tour_started and the first step_viewed is your loading latency.

"Abandonment events fire on every page navigation"

If you're using a SPA router (Next.js App Router, React Router), Tour Kit's beforeunload handler may fire on route changes within the app. Configure the analytics provider to distinguish between hard navigation (actual abandonment) and soft navigation (SPA route change during the tour). Check that your tour's step targets exist on the destination route.

Next steps

With step-level product tour drop-off tracking in place and funnel visualization showing exactly where users bail, the next layer is acting on the data automatically rather than manually reviewing dashboards every week.

  • A/B test tour variants: use Tour Kit's @tour-kit/scheduling package with feature flags to test different step orders and measure which variant has lower drop-off. We covered this in detail in our A/B testing product tours guide.
  • Trigger re-engagement flows: when a user drops off at a specific step, fire a tour_abandoned event that triggers a follow-up checklist or hint using @tour-kit/checklists or @tour-kit/hints.
  • Track post-tour activation: tour completion doesn't equal goal completion, as Product Fruits notes. Connect your tour funnel to your product's activation metric (first project created, first test run, first invite sent) to measure whether the tour actually works, not just whether users finish it.

For a working example with all the tracking code wired up, check the Tour Kit documentation and the analytics package API reference.

FAQ

What is a good product tour completion rate?

Tour Kit users and industry benchmarks from Chameleon's 15-million-interaction dataset show that 3-4 step tours average 72-74% completion. Tours with 5 steps drop to 34%, and 7+ step tours complete at just 16%. A reasonable target is above 65% for short tours and above 40% for longer onboarding sequences. Product tour drop-off tracking at the step level is the only way to know which specific steps need improvement.

How do I track which tour step users drop off at?

Fire a step_viewed event on every step transition and a step_completed event when the user advances. Compare the counts in a funnel chart — the step with the largest gap between step_viewed and the next step's step_viewed is your drop-off point. Tour Kit's @tour-kit/analytics package handles this automatically with stepViewed() and stepCompleted() tracker methods.

Does adding analytics to a product tour affect performance?

Tour Kit's analytics events are lightweight JSON payloads batched in a queue. The @tour-kit/analytics package uses a flush interval (default: 5 seconds) and sends events asynchronously. In our testing, enabling analytics adds less than 1ms to each step transition. The analytics package itself is under 3KB gzipped, so the bundle size impact is minimal.

What's the difference between tour skip and tour abandonment?

A skip happens when the user intentionally clicks a "Skip tour" or "Close" button — Tour Kit fires tour_skipped with the step index where they bailed. Abandonment occurs when the user navigates away, closes the browser tab, or the tour's target element disappears from the DOM. Tour Kit fires tour_abandoned for these cases. Separating the two in your funnel reveals whether users are actively rejecting the tour (skip) or passively disengaging (abandonment).

Can I track product tour drop-off without a third-party analytics tool?

Yes. Tour Kit's plugin interface accepts any object with a track() method. You can write a custom plugin that sends events to your own API endpoint, stores them in localStorage for local analysis, or logs them to the console during development. The ConsolePlugin included with @tour-kit/analytics is useful for debugging your event schema before connecting a production analytics tool.

Ready to try userTourKit?

$ pnpm add @tour-kit/react