shadimage
trophy-ui
uiachievement-cardsource: ok · refreshed

Achievement Card

React achievement card with totals, featured badges, and scrollable lists for profile and progress surfaces. Fits achievement UI examples in SaaS, consumer and community products. Open source, built on shadcn/ui + Tailwind.

npx shadcn add "https://ui.trophy.so/r/achievement-card.json"
free

Where it goes

profilefeature-grid

Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.

Dependencies

Bundle weight · light

Registry dependencies

achievement-badgeachievement-listbutton

Files1 · source cached 10/8/2026, 7:41:22 AM

registry/trophy/ui/achievement-card.tsx
"use client"

import * as React from "react"

import { cn } from "@/lib/utils"
import { AchievementBadge } from "@/components/ui/achievement-badge"
import { AchievementList } from "@/components/ui/achievement-list"
import { Button } from "@/components/ui/button"

interface Achievement {
  id: string
  name: string
  description?: string | null
  trigger: "metric" | "api" | "streak"
  badgeUrl?: string | null
  progress?: number
  rarity?: number
}

interface UserAchievement extends Achievement {
  achievedAt: string | null
}

interface AchievementCardProps extends React.HTMLAttributes<HTMLDivElement> {
  achievements: UserAchievement[]
  highlightedAchievements: UserAchievement[]
  badgeSize?: "sm" | "default" | "lg"
  lockedStyle?: "grayscale" | "silhouette" | "hidden"
  onAchievementClick?: (achievement: UserAchievement) => void
}

const AchievementCard = React.forwardRef<HTMLDivElement, AchievementCardProps>(
  (
    {
      className,
      achievements,
      highlightedAchievements,
      badgeSize = "default",
      lockedStyle = "grayscale",
      onAchievementClick,
      ...props
    },
    ref
  ) => {
    const unlockedCount = achievements.filter(
      (a) => a.achievedAt !== null
    ).length

    return (
      <div
        ref={ref}
        className={cn("bg-card rounded-2xl border p-6 shadow-sm", className)}
        {...props}
      >
        <div className="text-center">
          <p className="text-7xl font-bold tracking-tight sm:text-8xl">
            {unlockedCount}
          </p>
          <p className="text-muted-foreground mt-1 text-sm font-medium">
            Badges Unlocked
          </p>
        </div>

        <div className="mt-10 flex items-end justify-center gap-4">
          {highlightedAchievements.slice(0, 3).map((achievement, index) => (
            <AchievementBadge
              key={achievement.id}
              achievement={achievement}
              badgeSize="sm"
              onAchievementClick={onAchievementClick}
              className={cn(
                "w-28 border-0 bg-transparent p-0 shadow-none hover:shadow-none",
                index === 1 ? "-translate-y-2" : "translate-y-1"
              )}
            />
          ))}
        </div>

        <div className="mt-10">
          <div className="mb-3 flex items-center justify-between">
            <h3 className="text-primary text-sm font-medium">
              All Achievements
            </h3>
            <Button variant="link" size="sm" onClick={() => {}}>
              See all
            </Button>
          </div>
          <AchievementList
            achievements={achievements}
            badgeSize={badgeSize}
            lockedStyle={lockedStyle}
            onAchievementClick={onAchievementClick}
          />
        </div>
      </div>
    )
  }
)
AchievementCard.displayName = "AchievementCard"

export { AchievementCard }
export type { AchievementCardProps, Achievement, UserAchievement }

More from trophy-ui

Browse all components in this library →