TourKit
@tour-kit/adoption

Feature Adoption Tracking

Track feature usage, measure adoption rates, and nudge users toward underused features with @tour-kit/adoption for React

LLM Context File

Working with an AI assistant? Download the context file for @tour-kit/adoption and paste it into your conversation for accurate code generation.

What is @tour-kit/adoption?

The adoption package helps you understand which features users actually use, identify adoption patterns, and intelligently nudge users toward valuable features they haven't discovered.

Why Track Adoption?

Building features is expensive. Understanding which features drive value and which go unused helps you:

  • Prioritize development based on actual usage patterns
  • Guide users to features they'll find valuable
  • Reduce churn by identifying features users abandon
  • Measure product-market fit through adoption metrics

Core Concepts

Feature

A product capability you want to track. Each feature has:

  • ID: Unique identifier
  • Trigger: How usage is detected (click, event, or callback)
  • Criteria: When it's considered "adopted" (default: 3 uses in 30 days)
const feature = {
  id: 'dark-mode',
  name: 'Dark Mode',
  trigger: '#dark-mode-toggle', // CSS selector
  adoptionCriteria: { minUses: 3, recencyDays: 30 },
}

Adoption Status

Features progress through four states:

  • not_started: Never used
  • exploring: Used, but below adoption threshold
  • adopted: Meets adoption criteria
  • churned: Was adopted but hasn't been used recently

Nudge

A contextual prompt shown to encourage feature discovery. The package handles:

  • Scheduling: When to show nudges based on cooldowns and session limits
  • Prioritization: Which features to nudge first
  • Dismissal: Permanent dismissal or temporary snoozing

Installation

pnpm add @tour-kit/adoption @tour-kit/core
npm install @tour-kit/adoption @tour-kit/core
yarn add @tour-kit/adoption @tour-kit/core

Quick Start

app/layout.tsx
import { AdoptionProvider } from '@tour-kit/adoption'

const features = [
  {
    id: 'dark-mode',
    name: 'Dark Mode',
    trigger: '#dark-mode-toggle',
    category: 'customization',
  },
  {
    id: 'keyboard-shortcuts',
    name: 'Keyboard Shortcuts',
    trigger: { event: 'shortcuts:opened' },
    category: 'productivity',
  },
]

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <AdoptionProvider features={features}>
          {children}
        </AdoptionProvider>
      </body>
    </html>
  )
}
components/feature-badge.tsx
import { IfNotAdopted, NewFeatureBadge } from '@tour-kit/adoption'

export function DarkModeToggle() {
  return (
    <button id="dark-mode-toggle" className="relative">
      Toggle Dark Mode
      <IfNotAdopted featureId="dark-mode">
        <NewFeatureBadge>New!</NewFeatureBadge>
      </IfNotAdopted>
    </button>
  )
}
components/export-button.tsx
import { useFeature } from '@tour-kit/adoption'

export function ExportButton() {
  const { trackUsage, isAdopted } = useFeature('export')

  const handleExport = async () => {
    // Your export logic
    await exportData()
    // Track the usage
    trackUsage()
  }

  return (
    <button onClick={handleExport}>
      Export Data {!isAdopted && <span className="badge">New</span>}
    </button>
  )
}

Usage Tracking Methods

Automatic Click Tracking

The simplest approach: provide a CSS selector as the trigger.

const feature = {
  id: 'search',
  name: 'Search',
  trigger: '[data-feature="search"]', // Tracks clicks on this element
}

Click tracking uses event delegation for performance. Elements can be added/removed dynamically.

Custom Event Tracking

For complex interactions, dispatch custom events:

const feature = {
  id: 'export',
  name: 'Export',
  trigger: { event: 'export:complete' },
}

// In your code
async function handleExport() {
  await exportData()
  window.dispatchEvent(new CustomEvent('export:complete'))
}

Programmatic Tracking

For maximum control, use the useFeature hook:

function AIAssistant() {
  const { trackUsage } = useFeature('ai-assist')

  const handleGenerate = async () => {
    const result = await generateWithAI()
    if (result.success) {
      trackUsage() // Track only on success
    }
  }

  return <button onClick={handleGenerate}>Generate with AI</button>
}

Adoption Criteria

Define what "adopted" means for each feature:

const feature = {
  id: 'advanced-search',
  name: 'Advanced Search',
  trigger: '#advanced-search',
  adoptionCriteria: {
    minUses: 5, // Must use 5+ times
    recencyDays: 30, // Within last 30 days
  },
}

Custom Adoption Logic

For complex cases, provide a custom function:

const feature = {
  id: 'premium-feature',
  name: 'Premium Feature',
  trigger: '#premium-btn',
  adoptionCriteria: {
    custom: (usage) => {
      // Adopted if used 10+ times OR used in last 7 days
      return usage.useCount >= 10 ||
        (usage.lastUsed &&
         Date.now() - new Date(usage.lastUsed).getTime() < 7 * 24 * 60 * 60 * 1000)
    },
  },
}

Configuration

Full provider configuration:

<AdoptionProvider
  features={features}
  storage={{
    type: 'localStorage',
    key: 'my-app-adoption',
  }}
  nudge={{
    enabled: true,
    initialDelay: 5000, // Wait 5s before first nudge
    cooldown: 86400000, // 24 hours between nudges
    maxPerSession: 3, // Max 3 nudges per session
    maxFeatures: 1, // Show 1 feature at a time
  }}
  userId="user-123" // For multi-user tracking
  onAdoption={(feature) => {
    console.log(`Feature adopted: ${feature.name}`)
  }}
  onChurn={(feature) => {
    console.log(`Feature churned: ${feature.name}`)
  }}
/>

The userId prop changes the storage key to be user-specific. Omit for single-user applications.

Next Steps

On this page