Fair Rank

Ranks ties fairly at the precision shown and sets aside entries with too few samples, so leaderboards don't mislead.

StopsTied players shown with different ranks

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/fair-rank.json

The files land in your project. They're yours to edit.

Usage

fair-rank-usage.tsx
import { FairRank, type RankEntry } from "@/components/ui/fair-rank"

<FairRank caption={`Support CSAT, ${week}`} entries={WEEKS[week]} minSamples={20} samplesLabel="ratings" format={(s) => `${s}%`} />

What it catches

  • Equal-looking scores given different ranks
  • Movement since the last period
  • Entries with too few samples, set aside with the reason

Props

FairRank props
PropTypeDefault
higherIsBetter

Set false for times, golf scores and anything where lower wins.

booleantrue
minSamples

Entries with fewer samples are listed but not ranked, and don't push anyone down.

number0
decimals

Decimals shown. Scores are compared as displayed, so two scores that look equal always share a rank.

number1
entries* (required)

Rows to rank, with scores and optional previous rank.

RankEntry[]—
caption

Table caption, e.g. "CSAT, October".

string—
format

Formats a score for display; receives the score already rounded to `decimals`.

(score: string) => string(s) => s
samplesLabel

Noun for samples, e.g. "ratings".

string"samples"
unrankedText

Templates: `{min}`, `{has}`, `{samples}`, `{rank}`.

string"Not ranked: needs {min} {samples}, has {has}. Would be {…
tieNote

Note explaining how ties are ranked.

string"T = tied. Tied entries share a rank, so the next rank is…
newLabel

Label for entries new this period.

string"New"
formatOrdinal

Turns a rank into words for screen readers and the unranked note: 3 → "3rd".

(rank: number) => stringenglishOrdinal
tiedPrefix

Shown before a tied rank: "T-" gives T-2.

string"T-"
tiedText

Screen reader text for a tied rank. `{rank}` is the ordinal.

string"Tied {rank}"
noChangeLabel

Screen reader text for movement.

string"No change"
upLabel

Text for moving up.

string"Up"
downLabel

Text for moving down.

string"Down"
columnLabels

Hidden column headers, read by screen readers.

{ rank?: string; name?: string; score?: string; change?: string }—
unrankedLabel

Name of the list of entries that aren't ranked yet.

string"Not ranked yet"

* required. Also accepts the native props of <div> (className, aria-*, data-*…), passed through to it.

Accessibility

  • A real table with a header row
  • Ties and movement are stated in text ("tied", up/down labels), not only colour or arrows
  • Row animations are skipped with prefers-reduced-motion

Only what the source does. Test with your own screen reader and content before shipping.

Limitations

  • Ties are judged at the displayed precision; finer differences are ignored on purpose
  • Uses standard competition ranking only

What it stops

Tied players shown with different ranks

Also known as

leaderboard · ranking · ties · scores · hackathon results · gamification · standings · top players

Field type

Dependencies

Pairs well with