Skip to main content

Common Patterns

Ready-to-use v4 examples for common achievement workflows.

Floating Widget

Use the default widget when you want the fastest badge button plus modal setup:

import { AchievementsWidget } from 'react-achievements';

<AchievementsWidget position="bottom-right" />

Drawer Or Navigation Item

Inline placement lets the trigger flow with your layout:

<AchievementsWidget placement="inline" label="Badges" />

Use renderTrigger when the trigger should be one of your existing controls:

<AchievementsWidget
placement="inline"
renderTrigger={({ buttonProps, unlockedCount, totalCount }) => (
<button {...buttonProps} className="drawer-row">
Achievements
<span>{unlockedCount}/{totalCount}</span>
</button>
)}
/>

Existing Button Opens The Modal

Use AchievementsModal when your app already has a button, drawer row, command menu item, or profile-menu action.

import { useState } from 'react';
import { AchievementsModal } from 'react-achievements';

function AchievementAction() {
const [open, setOpen] = useState(false);

return (
<>
<button onClick={() => setOpen(true)}>View achievements</button>
<AchievementsModal isOpen={open} onClose={() => setOpen(false)} />
</>
);
}

AchievementsModal reads achievement state from the nearest AchievementProvider, so you do not need to map unlocked IDs into achievement objects.

Compact Badge Modal

Use compact density when the achievement modal should show square badges instead of roomier rows:

<AchievementsWidget density="compact" />

The same density works when you control the modal yourself:

<AchievementsModal
isOpen={open}
onClose={() => setOpen(false)}
density="compact"
/>

Use a lighter backdrop blur when the app underneath should stay more readable:

<AchievementsWidget modalBackdropBlur={2} />

<AchievementsModal
isOpen={open}
onClose={() => setOpen(false)}
backdropBlur="2px"
/>

Pass a number for pixels or a CSS length string. Omit the prop or pass 0 to disable backdrop blur.

Hide scrollbar chrome while preserving modal scrolling:

<AchievementsWidget hideModalScrollbar />

<AchievementsModal
isOpen={open}
onClose={() => setOpen(false)}
hideScrollbar
/>

Inline Achievement List

Use AchievementsList for profile pages, settings pages, drawers, or dashboard panels.

import { AchievementsList } from 'react-achievements';

<AchievementsList />

Use compact square badges for denser panels:

<AchievementsList density="compact" />

Only show earned achievements:

<AchievementsList showLocked={false} emptyState="No achievements unlocked yet." />

Render each row yourself while keeping provider state, filtering, and icon resolution:

<AchievementsList
renderAchievement={({ achievement, icon, isLocked }) => (
<div className={isLocked ? 'locked-row' : 'unlocked-row'}>
<span>{icon}</span>
<strong>{achievement.achievementTitle}</strong>
</div>
)}
/>

Provider-Level Icons

Define icon keys once on the provider. Notifications, widgets, modals, and lists can all use them.

<AchievementProvider
achievements={{
login: {
true: { title: 'First Login', icon: 'login' },
},
}}
icons={{ login: '🔑' }}
>
<App />
<AchievementsWidget />
</AchievementProvider>

Export Achievement Data

Allow users to download their achievement progress:

import { useAchievements } from 'react-achievements';

function ExportButton() {
const { exportData } = useAchievements();

const handleExport = () => {
const jsonString = exportData();
const blob = new Blob([jsonString], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');

link.href = url;
link.download = `achievements-${Date.now()}.json`;
link.click();
URL.revokeObjectURL(url);
};

return <button onClick={handleExport}>Download achievement data</button>;
}

Import Achievement Data

Restore achievements from a backup file:

import { useAchievements } from 'react-achievements';

function ImportButton() {
const { importData } = useAchievements();

const handleImport = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;

const reader = new FileReader();
reader.onload = (e) => {
const content = e.target?.result as string;
const result = importData(content, { merge: true });

if (result.success) {
alert('Achievement data imported.');
} else {
alert(`Import failed: ${result.errors?.join(', ') || 'Unknown error'}`);
}
};
reader.readAsText(file);
};

return <input type="file" accept=".json" onChange={handleImport} />;
}

Level Progress

Use the built-in LevelProgress component for a ready-made, theme-aware progress bar:

import { LevelProgress } from 'react-achievements';

<LevelProgress
level={3}
currentXP={120}
nextLevelXP={200}
theme="gamified"
/>

Reset All Achievements

Clear all achievement data during development or from a user-facing reset flow:

import { useAchievements } from 'react-achievements';

function ResetButton() {
const { reset } = useAchievements();

return (
<button onClick={reset}>
Reset achievements
</button>
);
}

Event-Based Tracking

Use semantic events instead of direct metric updates for larger apps:

import { useAchievementEngine } from 'react-achievements';

function GameComponent() {
const engine = useAchievementEngine();

return (
<button onClick={() => engine.emit('userScored', { points: 100 })}>
Score points
</button>
);
}

Listen to achievement events for analytics or custom workflows:

import { useEffect } from 'react';
import { useAchievementEngine } from 'react-achievements';

function AchievementAnalytics() {
const engine = useAchievementEngine();

useEffect(() => {
return engine.on('achievement:unlocked', (event) => {
analytics.track('Achievement Unlocked', {
achievementId: event.achievementId,
});
});
}, [engine]);

return null;
}