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:
- Renders when feature is not adopted
- Returns
nullwhen feature is adopted - Re-renders when adoption status changes
// Initially shows badge
<NewFeatureBadge featureId="feature" />
// After user adopts the feature
// → Badge disappears automaticallyAccessibility
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
- 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" />- 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>- 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
- IfNotAdopted Component - More flexible conditional rendering
- FeatureButton - Button with built-in tracking
- useFeature Hook - Access adoption state programmatically