Leaderboard

Entries ranked by a value, with the top three on a podium.

Dark previewReplay previewOpen in new tab

Step challenge

Sep 29 – Oct 5 · ends in 3 days

Use it for any list where the order is the point: a fitness challenge, a sales team's month, a game's weekly XP, the fastest times on a course. You pass entries with a value, and the component sorts them, numbers the places and draws the top three on a podium above the rest.

The component works out the ranks, so the data does not carry them. Entries on the same value share a place and the next place is skipped (1, 2, 2, 4), which is how a scoreboard is normally read.

It renders every row it is given. It is not virtualised, so for a long board pass limit and let highlight-id keep the viewer's own row in view.

For one number and its trend, use KPI.

Installation

Copy the folder into components/ui, where you can edit it. It uses the Avatar from this library and the cn helper.

text
components/ui/leaderboard/
├── Leaderboard.vue
├── rank.ts
└── index.ts

Usage

vue
<script setup lang="ts">
import { Leaderboard } from "@/components/ui/leaderboard";
</script>

<template>
  <Leaderboard
    :data="[
      { id: 'maya', name: 'Maya Chen', value: 84210, change: 2 },
      { id: 'omar', name: 'Omar Haddad', value: 79655 },
      { id: 'lena', name: 'Lena Brandt', value: 71038, change: -1 },
      { id: 'sam', name: 'Sam Okafor', value: 66402, change: 1 },
      { id: 'ines', name: 'Inès Moreau', value: 61987, change: -2 },
    ]"
    unit="steps"
  />
</template>

Podium bars to scale

The default podium. Each bar is drawn against the leader's value, so a close race looks close and a runaway looks like one. A floor keeps third place visible as a bar when the leader is far ahead. Set podium-scale="rank" to step the heights by place instead.

Dark previewReplay previewOpen in new tab

Cards instead of bars

podium="cards" gives each of the top three a card, with first raised between the other two. Use it when the values are labels people read (revenue, a level) rather than amounts to compare by eye. format-value controls how the value is written, and the screen reader hears the same text.

Dark previewReplay previewOpen in new tab

Keeping the viewer in sight

highlight-id tints that entry's row. When limit cuts it off, the row is pinned under the list with its real place, after a break that shows rows were skipped.

Dark previewReplay previewOpen in new tab

Lowest first, for times

order="asc" ranks the smallest value first, for a race time or a golf score. The podium then always steps by place, because a bar scaled to a winning time would be the shortest. podium="none" puts everyone in the list.

Dark previewReplay previewOpen in new tab

Movement and presses

change is the number of places an entry moved since the last period: positive is up the board, negative is down, 0 shows a dash. Leave it out and no movement column is drawn. A @select listener makes every row and podium place a button, and receives the entry and its rank.

Dark previewReplay previewOpen in new tab

Press a row or a podium place.

A short list

With three entries and podium="none", everyone sits in the list. subtitle adds a second line under the name.

Dark previewReplay previewOpen in new tab

Nothing to rank

An empty data array shows empty-text in place of the board. It defaults to "No entries yet".

Dark previewReplay previewOpen in new tab
Nobody has logged a run this week

Step challenge

The bar podium, scaled to the values. Switching weeks swaps the data and the bars regrow to the new totals.

Dark previewReplay previewOpen in new tab

Step challenge

Sep 29 – Oct 5 · ends in 3 days

Sales team

Cards for the top three, with revenue written out in currency.

Dark previewReplay previewOpen in new tab

Closed revenue, Q3

$2,622,130

Team West

Weekly league

Forty players cut to ten. The viewer is 27th, so their row is pinned under the list with its real place.

Dark previewReplay previewOpen in new tab

Gold league

Top 10 move up to Sapphire. Resets Sunday.

Fastest laps

Lowest time first, no podium, and a tie for second.

Dark previewReplay previewOpen in new tab

Qualifying · Q3

Fastest laps

Two drivers tied on 1:21.047 share second.

API Reference

Leaderboard

PropTypeDefaultDescription
datareadonly LeaderboardEntry[]—The entries, in any order: { id, name, value, avatar?, subtitle?, change? }. Places are worked out from value.
podium"bars" | "cards" | "none""bars"How the top three are drawn. bars stands them on a podium whose bars are scaled to their values; cards gives each a card, with first raised between the other two; none puts everyone in the list.
order"desc" | "asc""desc"desc ranks the highest value first. asc ranks the lowest first, for a time or a golf score.
podiumScale"value" | "rank""value"What the podium bar heights follow. value draws each against the leader's value; rank steps them by place. Always rank when order is asc, where the winning value is the smallest.
podiumHeightnumber132Height of the tallest podium bar, in pixels.
limitnumber—Show at most this many entries, podium included.
highlightIdstring—The entry to mark, usually the person looking at the board. Their row is tinted, and if limit cuts them off it is pinned at the bottom with their real place.
formatValue(value: number) => string(value) => value.toLocaleString("en-US")Turns a value into its label. Defaults to grouped digits: 12,480.
unitstring—Word read after the value by a screen reader, e.g. "points".
emptyTextstring"No entries yet"Shown when data has nothing to rank.
EventDescription
select(entry, rank). Listening makes every row and podium place a button.

It also takes the attributes of the element it renders, and a class.

Notes

Accessibility

Each podium place and each row is a single accessible element, read as one sentence: "Rank 2, Omar Haddad, 79,655 steps, up 1 place". A tie adds "tied" after the rank. Pass unit so the value is read with what it counts; without it, the reader hears a bare number.

Reduced motion

The podium bars grow when the board first appears, third place first and the leader last, in CSS, so they are there even before the page hydrates. With reduced motion on, they are drawn at full height straight away.

Colour

The podium uses the theme's primary colour at three strengths rather than gold, silver and bronze. Fixed medal colours would clash with most of the themes, and the place is already shown by the number on the bar.

Public exports

  • Values: Leaderboard, rankEntries, podiumHeights
  • Types: LeaderboardEntry, LeaderboardPodium, LeaderboardRanked