Tiered Rewards
Bronze, silver, gold, and platinum achievements
Point System
Each achievement awards configurable points
Secret Achievements
Hidden until unlocked for surprise delight
Auto-Unlock
Trigger achievements from analytics events
How Achievements Work
Achievements are one-time awards that recognize user accomplishments. Once unlocked, they remain in the user's profile permanently. Each achievement can award points that contribute to a total score.
Configuration
Define achievements in your engagement config. Group related achievements for better organization:
import { defineEngagement, achievement } from '@sylphx/sdk/engagement'
export const engagement = defineEngagement({
achievements: {
// Onboarding achievements
first_steps: achievement({
name: 'First Steps',
description: 'Complete the onboarding tutorial',
icon: 'footprints',
tier: 'bronze',
points: 10,
category: 'onboarding',
}),
profile_complete: achievement({
name: 'Looking Good',
description: 'Complete your profile with photo and bio',
icon: 'user-check',
tier: 'bronze',
points: 15,
category: 'onboarding',
}),
// Activity achievements
power_user: achievement({
name: 'Power User',
description: 'Use the app for 30 consecutive days',
icon: 'zap',
tier: 'gold',
points: 100,
category: 'activity',
}),
night_owl: achievement({
name: 'Night Owl',
description: 'Complete a task between midnight and 4am',
icon: 'moon',
tier: 'silver',
points: 25,
category: 'activity',
secret: true, // Hidden until unlocked!
}),
// Social achievements
influencer: achievement({
name: 'Influencer',
description: 'Refer 10 friends who sign up',
icon: 'users',
tier: 'platinum',
points: 250,
category: 'social',
}),
// Milestone achievements
centurion: achievement({
name: 'Centurion',
description: 'Complete 100 tasks',
icon: 'target',
tier: 'gold',
points: 75,
category: 'milestones',
// Progress tracking
progressTarget: 100,
}),
},
})| Property | Type | Description |
|---|---|---|
namerequired | string | Display name for the achievement |
descriptionrequired | string | How to earn this achievement |
iconrequired | string | Icon identifier (Lucide icon name or custom) |
tierrequired | 'bronze' | 'silver' | 'gold' | 'platinum' | Rarity/difficulty tier |
points | number | Points awarded when unlocked (default: 0) |
category | string | Category for grouping in UI (e.g., "onboarding", "social") |
secret | boolean | Hide name/description until unlocked (default: false) |
progressTarget | number | Target count for progress-based achievements |
Unlocking Achievements
Unlock achievements when users complete specific actions:
'use client'
import { useAchievements } from '@sylphx/sdk/react'
export function TaskCompleteHandler({ taskId }) {
const { unlockAchievement } = useAchievements()
const handleComplete = async () => {
// Complete the task...
await completeTask(taskId)
// Check if this unlocks an achievement
const taskCount = await getCompletedTaskCount()
if (taskCount === 1) {
const result = await unlockAchievement('first_steps')
if (result.unlocked) {
// Show celebration modal!
showAchievementModal(result.achievement)
}
}
if (taskCount === 100) {
const result = await unlockAchievement('centurion')
if (result.unlocked) {
showAchievementModal(result.achievement)
}
}
}
return (
<button onClick={handleComplete}>
Complete Task
</button>
)
}Idempotent Unlocking
unlockAchievement multiple times. The achievement will only be awarded once, and subsequent calls return alreadyUnlocked: true.Automatic Triggers
Link achievements to analytics events for automatic unlocking:
export const engagement = defineEngagement({
achievements: {
// Unlock when specific event fires
first_purchase: achievement({
name: 'First Purchase',
description: 'Make your first purchase',
icon: 'shopping-bag',
tier: 'bronze',
points: 20,
// Auto-unlock when this event fires
trigger: 'purchase_completed',
}),
// Unlock after event fires N times
share_master: achievement({
name: 'Share Master',
description: 'Share content 10 times',
icon: 'share-2',
tier: 'silver',
points: 50,
progressTarget: 10,
// Count-based trigger
trigger: {
event: 'content_shared',
count: 10,
},
}),
// Unlock based on event property
big_spender: achievement({
name: 'Big Spender',
description: 'Make a purchase over $100',
icon: 'credit-card',
tier: 'gold',
points: 75,
// Property-based trigger
trigger: {
event: 'purchase_completed',
condition: { 'amount': { $gte: 100 } },
},
}),
// Time-based achievement
night_owl: achievement({
name: 'Night Owl',
description: 'Complete a task between midnight and 4am',
icon: 'moon',
tier: 'silver',
points: 25,
secret: true,
trigger: {
event: 'task_completed',
condition: {
'$hour': { $gte: 0, $lt: 4 } // 00:00-03:59
},
},
}),
},
})
// When you track events via analytics...
analytics.track('content_shared', { contentId: '...' })
// ...achievements with matching triggers are automatically checked!Progress Tracking
Progress Achievements
Track incremental progress toward achievements:
'use client'
import { useAchievementProgress } from '@sylphx/sdk/react'
export function AchievementProgress({ achievementId }) {
const {
progress, // { current: 7, target: 10, percentage: 70 }
increment, // Function to increment progress
isLoading,
} = useAchievementProgress(achievementId)
return (
<div className="p-4 border rounded-xl">
<div className="flex justify-between mb-2">
<span className="font-medium">Share Master</span>
<span className="text-sm text-muted-foreground">
{progress?.current ?? 0} / {progress?.target ?? 0}
</span>
</div>
<div className="h-2 bg-muted rounded-full overflow-hidden">
<div
className="h-full bg-primary transition-all"
style={{ width: `${progress?.percentage ?? 0}%` }}
/>
</div>
</div>
)
}Secret Achievements
Secret achievements add an element of discovery and delight:
// In config
night_owl: achievement({
name: 'Night Owl',
description: 'Complete a task between midnight and 4am',
icon: 'moon',
tier: 'silver',
points: 25,
secret: true, // 👈 Hidden until unlocked
}),
// In UI, secret achievements show as:
// - Name: "???" or "Secret Achievement"
// - Description: "This achievement is hidden"
// - Icon: 🔒
// After unlocking, the real name/description is revealed!
// Query with secret flag
const achievements = await platform.engagement.listAchievements()
// Returns all achievements with:
// - secret: true achievements have name/description hidden
// - unlocked secret achievements are fully visible
// Or explicitly check
const definition = achievements.find(a => a.id === 'night_owl')
if (definition.secret && !userUnlocked.includes('night_owl')) {
// Show placeholder
} else {
// Show real achievement
}Keep exploring to discover this hidden badge!
Complete a task between midnight and 4am
Achievement Categories
Group achievements by category for organized display:
'use client'
import { useAchievements } from '@sylphx/sdk/react'
export function AchievementsByCategory() {
const { achievements, unlockedIds, isLoading } = useAchievements()
// Group by category
const byCategory = achievements?.reduce((acc, a) => {
const cat = a.category || 'general'
if (!acc[cat]) acc[cat] = []
acc[cat].push(a)
return acc
}, {} as Record<string, typeof achievements>)
return (
<div className="space-y-8">
{Object.entries(byCategory ?? {}).map(([category, items]) => (
<div key={category}>
<h3 className="text-lg font-bold capitalize mb-4">
{category}
</h3>
<div className="grid grid-cols-4 gap-4">
{items.map((achievement) => {
const isUnlocked = unlockedIds?.includes(achievement.id)
return (
<AchievementCard
key={achievement.id}
achievement={achievement}
isUnlocked={isUnlocked}
/>
)
})}
</div>
</div>
))}
</div>
)
}Notifications
Notify users when they unlock achievements:
// Server-side: After unlocking
const result = await platform.engagement.unlockAchievement({
userId,
achievementId: 'power_user',
})
if (result.unlocked) {
// Send push notification
await platform.push.send({
userId,
title: '🏆 Achievement Unlocked!',
body: `You earned "${result.achievement.name}"!`,
data: {
type: 'achievement',
achievementId: result.achievement.id,
},
})
// Or send email for special achievements
if (result.achievement.tier === 'platinum') {
await sendEmail(userId, {
template: 'achievement-unlocked',
data: {
achievementName: result.achievement.name,
points: result.achievement.points,
totalPoints: result.totalPoints,
},
})
}
}
// Client-side: Listen for achievement events
import { useEngagementEvents } from '@sylphx/sdk/react'
function AchievementListener() {
useEngagementEvents({
onAchievementUnlocked: (achievement) => {
// Show toast or modal
showCelebration(achievement)
},
})
return null
}Display Components
Pre-built components for achievement UIs:
import {
AchievementGrid, // Grid of all achievements
AchievementCard, // Single achievement card
AchievementBadge, // Compact badge icon
AchievementProgress, // Progress bar for progress-based
AchievementModal, // Celebration modal on unlock
TotalPoints, // User's total achievement points
} from '@sylphx/sdk/react'
// Full grid with categories
<AchievementGrid
groupByCategory
showProgress // Show progress bars
showLocked // Show locked achievements (default: true)
hideSecrets={false} // Show "???" placeholders for secrets
/>
// Single achievement card
<AchievementCard
achievementId="power_user"
size="lg" // sm, md, lg
showDescription
/>
// Compact badge for profiles
<AchievementBadge achievementId="power_user" />
// Total points display
<TotalPoints />
// Renders: "🏅 450 points"
// Celebration modal (shown automatically on unlock)
<AchievementModal
autoShow // Show when achievement unlocks
celebrationDuration={3000}
onClose={() => {}}
/>