GitHub

Achievement Unlocked

Celebratory React dialog for newly unlocked achievements with headline, badge, and share actions. Use for unlock moments, level up animations and more. Built on shadcn/ui + Tailwind. Open source.

Achievement Unlocked is a focused gamification moment for celebrating unlocks, level up animations, congratulating users, reinforcing rewards, and encouraging sharing in achievement-driven gamification experiences.

Installation

npx shadcn@latest add https://ui.trophy.so/achievement-unlocked

Usage

import { AchievementUnlocked } from "@/components/ui/achievement-unlocked"
const [open, setOpen] = useState(false)

<button
  type="button"
  onClick={() => setOpen(true)}
  className="rounded-lg border bg-background px-4 py-2 text-sm font-medium text-foreground transition-colors hover:bg-muted"
>
  Unlock achievement
</button>

<AchievementUnlocked
  achievement={{
    id: "task-champion",
    name: "Task Champion",
    description: "Complete a task 30 days in a row. Congratulations!!",
    unlockedAt: new Date().toISOString(),
  }}
  open={open}
  onOpenChange={setOpen}
  secondaryActionLabel="Share"
  onSecondaryActionClick={() => {
    navigator.share?.({
      title: "I unlocked Task Champion!",
      text: "Complete a task 30 days in a row. Congratulations!!",
      url: window.location.href,
    })
  }}
/>

Examples

Basic Usage

const [open, setOpen] = useState(false)

<button
  type="button"
  onClick={() => setOpen(true)}
  className="rounded-lg border bg-background px-4 py-2 text-sm font-medium text-foreground transition-colors hover:bg-muted"
>
  Unlock achievement
</button>

<AchievementUnlocked
  achievement={{
    id: "task-champion",
    name: "Task Champion",
    description: "Complete a task 30 days in a row. Congratulations!!",
    unlockedAt: new Date().toISOString(),
  }}
  open={open}
  onOpenChange={setOpen}
/>

With Trophy

Use the Trophy SDK to send a metric event server-side:

import { TrophyApiClient } from '@trophyso/node';

const trophy = new TrophyApiClient({
  apiKey: 'YOUR_API_KEY'
});

const response = await trophy.metrics.event(
  "words-written",
  {
    user: {
      id: 'user-id',
      email: '[email protected]',
      tz: 'Europe/London',
      subscribedToEmails: true,
      attributes: {
        department: 'engineering',
        role: 'developer'
      }
    },
    value: 750,
    attributes: {
      category: 'writing',
      source: 'mobile-app'
    }
  }
);

Then pass and transform the response into AchievementUnlocked props in your UI layer:

const unlockedAchievement = response.achievements?.[0]
const shouldOpen = Boolean(unlockedAchievement)

{
  unlockedAchievement ? (
    <AchievementUnlocked achievement={unlockedAchievement} open={shouldOpen} />
  ) : null
}

API Reference

AchievementUnlocked

PropTypeDefaultDescription
achievementAchievementRequiredThe achievement to display
openbooleanRequiredControls dialog visibility
onOpenChange(open: boolean) => voidRequiredCalled when open state changes
secondaryActionLabelstring"Share"Label for the secondary action button
onSecondaryActionClick() => void-Secondary button click handler for social share or custom actions
onShare() => void-Deprecated alias for onSecondaryActionClick
classNamestring-Additional classes for the dialog

Achievement

interface Achievement {
  id: string
  name: string
  description?: string | null
  unlockedAt?: string
}