More Tables Snippets
Leaderboard Table — Free HTML CSS JS Snippet
Leaderboard Table · Tables · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Leaderboard Table — Medal Ranks, Progress Bars, Streak Counter & Period Tab Switcher

A leaderboard table ranks users, teams, or entities by a score metric — displaying position, identity, score, relative progress, and rank movement. Leaderboards are used in learning platforms, gamified apps, sales dashboards, fitness trackers, and developer community sites to drive engagement and competition. This snippet provides a complete leaderboard table with gradient medal badges, avatar initials, streak counters, score display, progress bars relative to the top score, rank change indicators, and a period tab switcher.
Medal rank badges
Ranks 1–3 use gradient medal badges: gold (amber → orange), silver (slate → grey), bronze (dark amber). Each uses background: linear-gradient with a matching box-shadow glow in the same colour. Ranks 4+ use a neutral grey background. The medal div is a fixed 28×28px square with border-radius: 8px for a rounded square shape — different from the circular avatar to maintain visual separation.
Progress bars relative to top score
Each row's progress bar width is set as a percentage of the top score (not 100%). Row 1 gets 98% (not 100% to avoid exact full-fill), row 2 gets 91%, and so on. Each bar uses a unique gradient matching the medal or avatar colour — creating colour-coded rows without repeating colours, the same fill technique as the standalone progress bar snippet. The bar transition: width 0.6s ease animates on the period tab switch.
Rank change indicators
The .change span shows +N (green), -N (red), or – (grey) for rank movement since the previous period. These use the same tinted background pattern as status badges elsewhere in the library.
Period tab switcher
Three tabs (Week, Month, All time) switch the data period. The active tab gets a white background on the grey tab container — the standard pill-style tab switcher pattern. In production, the tab click triggers an API fetch for the selected period and re-renders the table.
Score display
Scores use font-feature-settings: "tnum" (tabular numbers) so digits align vertically — a critical detail for numerical columns where values need to be visually compared across rows. Without tabular numbers, proportional digit widths cause misalignment in number columns.
Streak counter
A fire emoji + day count (🔥 14d) communicates daily engagement streaks. This is a lightweight gamification signal that is increasingly standard on learning and fitness platforms.
Build with AI
Build, Understand, Optimize, and Extend It With AI
You do not have to reverse-engineer the bar-fill math by reading the markup alone. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain precisely why setPeriod() resets each bar-fill width to 0% before reapplying the stored width, and why row 1 is hardcoded to 98% rather than 100%. The same assistant can help optimize it, for instance asking whether font-feature-settings "tnum" is enough for numeric alignment across locales, or how to avoid a layout thrash if hundreds of rows animate their bar widths at once. It is also useful for extending the table: ask it to wire setPeriod to a real fetch call with a loading skeleton, add a sortable column click handler, or highlight the current signed-in user's row with a sticky footer. Treat the code less like a finished artifact and more like a starting point for a conversation.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build a "leaderboard table" in plain HTML, CSS, and JavaScript with no libraries.
Requirements:
- A table with columns for rank, user (avatar initials plus name and role), a streak count, a numeric score, a progress bar relative to the top score, and a rank-change indicator (up, down, or unchanged).
- The top three rows must get distinct gold, silver, and bronze rounded-square medal badges (not circular avatars) built from CSS linear-gradient backgrounds with a matching colored box-shadow glow, plus a subtle tinted row background to set them apart from the rest of the table.
- Each row's progress bar width must be calculated as a percentage of the top scorer's score, capped at 98% even for the top row so no bar looks artificially maxed out, and each bar's fill color should be distinct per row rather than a single repeated color.
- The score column must use font-feature-settings: "tnum" so digits align vertically in a monospaced-number column regardless of proportional font metrics.
- Three period-switching tabs (e.g. Week, Month, All time) using a pill-style active state (white background on a light gray track). Clicking a tab must not fetch new data in this demo, but must re-trigger the bar-fill animation by resetting each bar's width to 0% and then back to its stored value on a short delay, so the CSS width transition visibly replays.
- A responsive rule that hides the streak column entirely below a defined breakpoint rather than shrinking it illegibly.
- Do not hardcode row count assumptions elsewhere in the JS — the period-switch bar animation logic must work against however many .bar-fill elements currently exist in the DOM.Step by step
How to Use
- 1Click the period tabs to see the bar animationClick Week, Month, or All time to trigger the bar width animation (bars drop to 0 then refill). In production, wire each tab to an API fetch that returns the ranked data for that period.
- 2Update user dataIn the HTML tbody, edit each row: change avatar initials, gradient colours, name, role, streak count, score, bar fill width percentage, and change indicator class (up/down/same) and value.
- 3Add or remove rowsDuplicate any table row pair in the tbody. Update the data-values and assign .top1/.top2/.top3 class only to the first three rows. Remove those classes from all others — the medal gradient and subtle row tint apply automatically.
- 4Calculate bar widthsSet each bar width as (score / topScore) * 100 + "%". Round to the nearest integer. The top scorer gets ~98% (not 100%) so the bar does not look artificially maxed out.
- 5Change the score label and period tabsUpdate the th "Score" header to your metric (Points, Revenue, Commits, Completions). Edit the three period button labels to match your time period options (Day/Week/Month, Sprint/Quarter/Year, etc.).
- 6Export in your formatClick "HTML" for a standalone file, "JSX" for a React component mapping a sorted data array to rows, or "Tailwind" for a React + Tailwind CSS version.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Divide each user's score by the top score and multiply by 100: const width = Math.round((score / topScore) * 100). Set this as the bar width percentage. Cap the top scorer at 98% (not 100%) so the bar does not appear artificially maxed: const width = Math.min(98, Math.round(score/topScore*100)). In a JavaScript-rendered table, compute this in the data mapping: rows.map((r,i) => ({...r, barWidth: Math.min(98, Math.round(r.score/rows[0].score*100))})).
Call your API on period tab click: fetch("/api/leaderboard?period=week").then(r=>r.json()).then(data => renderTable(data)). In renderTable, generate the table rows from the sorted data array: data.forEach((user, i) => { const rank = i+1; const tr = document.createElement("tr"); tr.innerHTML = ...; tbody.appendChild(tr); }). Clear the tbody before each render to prevent duplicate rows.
Compare each row's user ID to the current user ID: if (row.userId === currentUserId) { tr.classList.add("current-user"); }. In the CSS: .current-user td { background: rgba(99,102,241,0.05); } and .current-user .uname { color: #6366f1; font-weight: 800; }. Optionally add a "(You)" label after the name for unambiguous identification.
Click "JSX" to download. Accept a data prop: an array of {rank, name, role, score, streak, change, barWidth} objects sorted by score descending. Use useMemo to compute barWidth from scores. Manage activePeriod in useState. On period change, fetch new data and update the data state. Animate bars by setting a key on the table body that changes on data update, triggering a re-mount and re-animation.