v4 Feature Setup
This checklist covers the full v4 setup surface: provider, tracking, UI placement, storage, customization, events, portability, and headless usage.
1. Provider
Wrap your app once. The root react-achievements entry point is the web setup and includes built-in notifications and confetti by default.
import { AchievementProvider } from 'react-achievements';
import { achievements } from './achievements';
export function App() {
return (
<AchievementProvider achievements={achievements}>
<YourApp />
</AchievementProvider>
);
}
Disable built-in effects when you only want state and UI components:
<AchievementProvider
achievements={achievements}
ui={{ enableNotifications: false, enableConfetti: false }}
>
<YourApp />
</AchievementProvider>
Built-in unlock notifications auto-dismiss after 5 seconds by default. Set ui.notificationDuration to customize the timing in milliseconds. When one update unlocks multiple achievements, the notifications stack instead of replacing earlier unlocks.
<AchievementProvider
achievements={achievements}
ui={{ notificationDuration: 8000 }}
>
<YourApp />
</AchievementProvider>
Tune the built-in canvas-confetti celebration through ui.confetti:
<AchievementProvider
achievements={achievements}
ui={{
confetti: {
colors: ['#22d3ee', '#f97316', '#facc15'],
particleCount: 120,
spread: 80,
},
}}
>
<YourApp />
</AchievementProvider>
Add optional per-reward confetti when one achievement should feel bigger or quieter than the global default. Rewards without confetti keep using ui.confetti and the selected theme.
const achievements = {
score: {
100: {
title: 'Century!',
description: 'Score 100 points',
},
1000: {
title: 'Boss Finale!',
description: 'Score 1,000 points',
confetti: {
colors: ['#facc15', '#f97316', '#ef4444'],
particleCount: 240,
spread: 100,
startVelocity: 60,
scalar: 1.4,
},
},
},
tutorial: {
true: {
title: 'Tutorial Complete',
confetti: false,
},
},
};
confetti: false disables confetti only for that reward. ui.enableConfetti: false still disables confetti for every reward.
2. Direct Tracking
Use useSimpleAchievements for most React apps.
import { useSimpleAchievements } from 'react-achievements';
function QuestButton() {
const { track, increment, trackMultiple } = useSimpleAchievements();
return (
<>
<button onClick={() => track('score', 100)}>Score 100</button>
<button onClick={() => increment('tasksCompleted')}>Complete task</button>
<button onClick={() => trackMultiple({ score: 500, profileComplete: true })}>
Finish onboarding
</button>
</>
);
}
3. UI Placement
Use the widget when you want a trigger plus modal.
<AchievementsWidget />
<AchievementsWidget placement="inline" label="Badges" />
Use renderTrigger to make the trigger match your app exactly:
<AchievementsWidget
placement="inline"
renderTrigger={({ buttonProps, unlockedCount, totalCount }) => (
<button {...buttonProps} className="drawer-row">
Achievements
<span>{unlockedCount}/{totalCount}</span>
</button>
)}
/>
Use existing UI with the modal:
const [open, setOpen] = useState(false);
<button onClick={() => setOpen(true)}>Achievements</button>
<AchievementsModal isOpen={open} onClose={() => setOpen(false)} />
Use inline content for profile, settings, drawer, or dashboard surfaces:
<AchievementsList showLocked />
Use compact square badges for denser achievement catalogs:
<AchievementsWidget density="compact" />
<AchievementsModal isOpen={open} onClose={() => setOpen(false)} density="compact" />
<AchievementsList density="compact" />
Tune the modal backdrop and scrollbar chrome from either the widget or the modal:
<AchievementsWidget
modalBackdropBlur={2}
hideModalScrollbar
/>
<AchievementsModal
isOpen={open}
onClose={() => setOpen(false)}
backdropBlur="2px"
hideScrollbar
/>
For backdropBlur and modalBackdropBlur, pass a number for pixels or a CSS length string. Omit the prop or pass 0 to disable backdrop blur. Scrollbar hiding keeps the modal scrollable.
4. Icons And Themes
Provider-level icons are inherited by notifications, widgets, modals, and lists.
<AchievementProvider
achievements={achievements}
icons={{ login: '🔑', trophy: '🏆' }}
ui={{ theme: 'minimal' }}
>
<YourApp />
<AchievementsWidget />
</AchievementProvider>
Override per component when needed:
<AchievementsWidget
theme="gamified"
icons={{ streak: '🔥' }}
modalTitle="Team achievements"
/>
5. Custom UI Components
Replace built-in notification, modal, or confetti components through ui.
<AchievementProvider
achievements={achievements}
ui={{
NotificationComponent: MyNotification,
ModalComponent: MyAchievementsModal,
ConfettiComponent: MyConfetti,
}}
>
<YourApp />
</AchievementProvider>
Custom NotificationComponent implementations receive duration and stackIndex so they can preserve the same auto-dismiss and stacking behavior as the built-in notifications.
Custom ModalComponent implementations receive hideScrollbar, density, and backdropBlur in addition to the base modal props. Honor those props when you want AchievementsWidget and AchievementsModal controls to work with a provider-level custom modal.
Custom ConfettiComponent implementations receive show plus the resolved confetti settings for the unlocked reward.
For custom inline rows, use AchievementsList.renderAchievement.
<AchievementsList
renderAchievement={({ achievement, icon, isLocked }) => (
<div className={isLocked ? 'muted-row' : 'earned-row'}>
<span>{icon}</span>
<span>{achievement.achievementTitle}</span>
</div>
)}
/>
6. Storage
Use built-in storage options:
import { StorageType } from 'react-achievements';
<AchievementProvider achievements={achievements} storage={StorageType.Local}>
<YourApp />
</AchievementProvider>
Supported storage options include:
StorageType.Localfor browserlocalStorageStorageType.Memoryfor non-persistent stateStorageType.IndexedDBfor larger browser datasetsStorageType.RestAPIwithrestApiConfigfor backend persistenceLocalStorage,MemoryStorage,IndexedDBStorage,RestApiStorage,OfflineQueueStorage, and custom sync or async storage adapters
<AchievementProvider
achievements={achievements}
storage={StorageType.RestAPI}
restApiConfig={{ baseUrl: '/api/achievements', userId }}
>
<YourApp />
</AchievementProvider>
7. Event-Based Tracking
Use an injected engine when your app already has semantic events.
import { AchievementEngine, AchievementProvider } from 'react-achievements';
const engine = new AchievementEngine({
achievements,
eventMapping: {
userScored: (data) => ({ score: data.points }),
},
storage: 'local',
});
<AchievementProvider engine={engine}>
<YourApp />
</AchievementProvider>
const engine = useAchievementEngine();
engine.emit('userScored', { points: 100 });
8. Import And Export
const { exportData, importData } = useAchievements();
const backup = exportData();
const result = importData(backup, { merge: true });
9. Headless And React Native
The web entry point includes DOM UI. For React Native or fully custom UI, use the DOM-free headless entry point:
import {
AchievementProvider,
useAchievementState,
useSimpleAchievements,
} from 'react-achievements/headless';
function CustomAchievementSurface() {
const { track, reset } = useSimpleAchievements();
const { allAchievements, unlockedIds, unlockedCount, totalCount } = useAchievementState();
return (
<section>
<button onClick={() => track('score', 100)}>Score 100</button>
<button onClick={reset}>Reset</button>
<p>{unlockedCount} / {totalCount} unlocked</p>
{allAchievements.map((achievement) => (
<article key={achievement.achievementId}>
<strong>{achievement.achievementTitle}</strong>
<span>{achievement.isUnlocked ? 'Unlocked' : 'Locked'}</span>
</article>
))}
<pre>{JSON.stringify(unlockedIds, null, 2)}</pre>
</section>
);
}
<AchievementProvider achievements={achievements}>
<CustomAchievementSurface />
</AchievementProvider>
React Native UI components are not included in v4. Use headless state/hooks with your own native components, or use achievements-engine directly outside React.