TourKit
@tour-kit/adoptionComponents

NewFeatureBadge

NewFeatureBadge component: badge overlay that highlights unadopted features and auto-hides after the user engages with them

Overview

NewFeatureBadge displays a badge (default: "New") for features that haven't been adopted yet. It automatically hides once the feature is adopted.

Basic Usage

import { NewFeatureBadge } from '@tour-kit/adoption'

function FeatureButton() {
  return (
    <button id="dark-mode-toggle">
      Toggle Dark Mode
      <NewFeatureBadge featureId="dark-mode" />
    </button>
  )
}

Props

Prop

Type

Extends all standard <span> props.

Examples

Default Badge

<NewFeatureBadge featureId="export" />
// Renders: <span>New</span>

Custom Text

<NewFeatureBadge featureId="ai-assist" text="AI" />
<NewFeatureBadge featureId="premium" text="Premium" />
<NewFeatureBadge featureId="beta" text="Beta" />

Different Variants

<NewFeatureBadge featureId="feature-1" variant="default" />
<NewFeatureBadge featureId="feature-2" variant="secondary" />
<NewFeatureBadge featureId="feature-3" variant="outline" />
<NewFeatureBadge featureId="feature-4" variant="destructive" />

Different Sizes

<NewFeatureBadge featureId="small-feature" size="sm" text="New" />
<NewFeatureBadge featureId="medium-feature" size="md" text="New" />
<NewFeatureBadge featureId="large-feature" size="lg" text="New" />

In Navigation

function NavBar() {
  return (
    <nav>
      <a href="/dashboard">Dashboard</a>

      <a href="/analytics">
        Analytics
        <NewFeatureBadge featureId="analytics" />
      </a>

      <a href="/ai-tools">
        AI Tools
        <NewFeatureBadge featureId="ai-tools" text="Beta" variant="secondary" />
      </a>
    </nav>
  )
}

In Dropdown Menu

import {
  DropdownMenu,
  DropdownMenuItem,
} from '@/components/ui/dropdown-menu'

<DropdownMenu>
  <DropdownMenuItem>
    Export to PDF
    <NewFeatureBadge featureId="pdf-export" size="sm" />
  </DropdownMenuItem>

  <DropdownMenuItem>
    Advanced Filters
    <NewFeatureBadge featureId="filters" size="sm" />
  </DropdownMenuItem>
</DropdownMenu>

In Feature Cards

function FeatureCard({ featureId, title, description }) {
  return (
    <div className="card">
      <div className="card-header">
        <h3>{title}</h3>
        <NewFeatureBadge featureId={featureId} size="sm" />
      </div>
      <p>{description}</p>
    </div>
  )
}

With Icons

import { Sparkles } from 'lucide-react'

<button>
  AI Assistant
  <NewFeatureBadge featureId="ai-assist">
    <Sparkles className="h-3 w-3" />
    AI
  </NewFeatureBadge>
</button>

Animated Badge

<NewFeatureBadge
  featureId="new-feature"
  className="animate-pulse"
  text="New!"
/>

Positioned Badge

<div className="relative">
  <button className="feature-btn">Advanced Search</button>

  <NewFeatureBadge
    featureId="advanced-search"
    className="absolute -top-1 -right-1"
    size="sm"
  />
</div>

Styling

Custom Classes

<NewFeatureBadge
  featureId="feature"
  className="ml-2 uppercase font-bold"
/>

CSS Variables

Customize badge appearance:

.new-feature-badge {
  --badge-bg: hsl(142 76% 36%);
  --badge-text: hsl(0 0% 100%);
  --badge-radius: 0.25rem;
  --badge-padding: 0.125rem 0.5rem;
}

Variant Styling

The badge uses class-variance-authority (cva) for variants:

// Default: Green background
<NewFeatureBadge variant="default" />

// Secondary: Gray background
<NewFeatureBadge variant="secondary" />

// Outline: Transparent with border
<NewFeatureBadge variant="outline" />

// Destructive: Red background
<NewFeatureBadge variant="destructive" />

Behavior

The badge automatically:

  1. Renders when feature is not adopted
  2. Returns null when feature is adopted
  3. Re-renders when adoption status changes
// Initially shows badge
<NewFeatureBadge featureId="feature" />

// After user adopts the feature
// → Badge disappears automatically

Accessibility

The badge renders a semantic <span>:

<NewFeatureBadge
  featureId="feature"
  aria-label="New feature available"
/>

For screen readers, consider adding context:

<button aria-label="Export data (new feature)">
  Export
  <NewFeatureBadge featureId="export" aria-hidden="true" />
</button>

Use aria-hidden="true" on decorative badges to avoid announcing "New" to screen readers when the button label already indicates it's new.

TypeScript

Fully typed:

import { NewFeatureBadge, type NewFeatureBadgeProps } from '@tour-kit/adoption'

const badgeProps: NewFeatureBadgeProps = {
  featureId: 'my-feature',
  text: 'New',
  variant: 'default',
  size: 'md',
  className: 'custom-class',
}

<NewFeatureBadge {...badgeProps} />

Comparison with Conditional Components

// Using NewFeatureBadge (simpler for badges)
<NewFeatureBadge featureId="feature" text="New" />

// Using IfNotAdopted (more flexible)
<IfNotAdopted featureId="feature">
  <span className="badge">New</span>
</IfNotAdopted>

Use NewFeatureBadge when:

  • You want a simple badge with minimal code
  • You want consistent badge styling
  • You don't need custom badge content

Use IfNotAdopted when:

  • You need fully custom content
  • You want to show complex UI
  • You need a fallback

Performance

The badge efficiently re-renders only when adoption status changes:

// ✓ Efficient: Only updates when "export" is adopted
<NewFeatureBadge featureId="export" />

// ✓ Also efficient: Multiple badges don't cause extra re-renders
<div>
  <NewFeatureBadge featureId="feature-1" />
  <NewFeatureBadge featureId="feature-2" />
  <NewFeatureBadge featureId="feature-3" />
</div>

Common Patterns

Category Badges

const CATEGORY_BADGES = {
  ai: { text: 'AI', variant: 'default' as const },
  premium: { text: 'Pro', variant: 'secondary' as const },
  beta: { text: 'Beta', variant: 'outline' as const },
}

function FeatureItem({ featureId, category }) {
  const badge = CATEGORY_BADGES[category]

  return (
    <div>
      Feature Name
      {badge && (
        <NewFeatureBadge
          featureId={featureId}
          text={badge.text}
          variant={badge.variant}
        />
      )}
    </div>
  )
}

Pulsing Badge

<NewFeatureBadge
  featureId="important-feature"
  className="animate-pulse ring-2 ring-primary"
  text="Try me!"
/>

Count-Based Badge

import { useFeature } from '@tour-kit/adoption'

function SmartBadge({ featureId }: { featureId: string }) {
  const { usage, feature } = useFeature(featureId)
  const remaining = (feature?.adoptionCriteria?.minUses || 3) - usage.useCount

  return (
    <NewFeatureBadge
      featureId={featureId}
      text={`${remaining} left`}
      size="sm"
    />
  )
}

Best Practices

  1. Use consistent text across your app:
// Good: Consistent
<NewFeatureBadge featureId="feature-1" text="New" />
<NewFeatureBadge featureId="feature-2" text="New" />

// Bad: Inconsistent
<NewFeatureBadge featureId="feature-1" text="New" />
<NewFeatureBadge featureId="feature-2" text="NEW!" />
<NewFeatureBadge featureId="feature-3" text="Try this" />
  1. Don't overuse badges - they lose impact:
// Good: Strategic use
<nav>
  <a href="/home">Home</a>
  <a href="/features">Features <NewFeatureBadge featureId="features" /></a>
  <a href="/about">About</a>
</nav>

// Bad: Too many badges
<nav>
  <a href="/home">Home <NewFeatureBadge featureId="home" /></a>
  <a href="/features">Features <NewFeatureBadge featureId="features" /></a>
  <a href="/about">About <NewFeatureBadge featureId="about" /></a>
</nav>
  1. Consider badge placement:
// Good: Badge after text
<button>
  Export Data
  <NewFeatureBadge featureId="export" className="ml-2" />
</button>

// Also good: Badge as superscript
<button className="relative">
  Export Data
  <NewFeatureBadge
    featureId="export"
    className="absolute -top-1 -right-1"
    size="sm"
  />
</button>

Next Steps

On this page