Achievement List
trophy/achievement-listFreeComponent
Vertical React achievement list with icons, descriptions, and optional progress for dense achievement UI. Complements grids and cards in gamification dashboards. Open source, built on shadcn/ui + Tailwind.
Live preview
live · rendered by the registry
Rendered by trophy on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://ui.trophy.so/r/achievement-list.jsonSource
1"use client"23import * as React from "react"4import { cva, type VariantProps } from "class-variance-authority"5import { Trophy } from "lucide-react"67import { cn } from "@/lib/utils"89interface Achievement {10 id: string11 name: string12 description?: string | null13 trigger: "metric" | "api" | "streak"14 badgeUrl?: string | null15 progress?: number16 rarity?: number17}1819interface UserAchievement extends Achievement {20 achievedAt: string | null21}2223const achievementListVariants = cva("flex flex-col", {24 variants: {25 columns: {26 2: "",27 3: "",28 4: "",29 auto: "",30 },31 gap: {32 sm: "gap-2",33 default: "gap-3",34 lg: "gap-4",35 },36 },37 defaultVariants: {38 columns: "auto",39 gap: "default",40 },41})4243interface AchievementListProps44 extends45 React.HTMLAttributes<HTMLDivElement>,46 VariantProps<typeof achievementListVariants> {47 achievements: UserAchievement[]48 badgeSize?: "sm" | "default" | "lg"49 lockedStyle?: "grayscale" | "silhouette" | "hidden"50 onAchievementClick?: (achievement: UserAchievement) => void51}5253const badgeSizeMap = {54 sm: "h-10 w-10",55 default: "h-12 w-12",56 lg: "h-14 w-14",57} as const5859const iconSizeMap = {60 sm: "h-5 w-5",61 default: "h-6 w-6",62 lg: "h-7 w-7",63} as const6465const progressSizeMap = {66 sm: 42,67 default: 48,68 lg: 56,69} as const7071const AchievementList = React.forwardRef<HTMLDivElement, AchievementListProps>(72 (73 {74 className,75 columns,76 gap,77 achievements,78 badgeSize = "default",79 lockedStyle = "grayscale",80 onAchievementClick,81 ...props82 },83 ref84 ) => {85 return (86 <div ref={ref} className={cn(className)} {...props}>87 <div88 role="list"89 aria-label="Achievements"90 className={achievementListVariants({ columns, gap })}91 >92 {achievements.map((achievement) => {93 const isUnlocked = achievement.achievedAt !== null94 const hasProgress =95 isUnlocked && typeof achievement.progress === "number"9697 if (!isUnlocked && lockedStyle === "hidden") {98 return null99 }100101 const progress = hasProgress102 ? Math.min(100, Math.max(0, achievement.progress ?? 0))103 : 0104 const progressSize = progressSizeMap[badgeSize]105 const progressStroke = 3106 const progressRadius = (progressSize - progressStroke) / 2107// … truncated
What it pulls in
npm packages
Files it writes
More from trophy
All 18 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Achievement Badgeachievement-badge | trophy | Components | Free | 1 dep | |
| Achievement Cardachievement-card | trophy | Components | Free | no deps | |
| Achievement Gridachievement-grid | trophy | Components | Free | 2 deps | |
| Achievement Unlockedachievement-unlocked | trophy | Components | Free | 1 dep | |
| Leaderboard Cardleaderboard-card | trophy | Components | Free | no deps | |
| Leaderboard Podiumleaderboard-podium | trophy | Components | Free | 2 deps | |
| Leaderboard Rankingsleaderboard-rankings | trophy | Components | Free | 2 deps | |
| Points Awardspoints-awards | trophy | Components | Free | 2 deps |
