Rank Shuffle Board
A data-driven rank shuffle leaderboard — rows swap positions with a FLIP animation when scores update, arrows flash for rank gains and losses, and the top three carry gold, silver, and bronze podium vocabulary.
npx shadcn@latest add https://webberui.com/r/rank-shuffle-board.jsonPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<RankShuffleBoard />
Installation
npx shadcn@latest add https://webberui.com/r/rank-shuffle-board.jsonOr, once registries are configured in components.json, install it as @webberui/rank-shuffle-board.
Usage
import { RankShuffleBoard } from "@/components/ui/rank-shuffle-board";
const entries = [
{ id: "amy", name: "Amy", score: 2480 },
{ id: "ben", name: "Ben", score: 2210 },
{ id: "cleo", name: "Cleo", score: 1975 },
];
<RankShuffleBoard entries={entries} unit="pt" />Sorting happens inside the component — just update the scores in entries and the rows swap positions automatically with a FLIP animation. id must be stable (do not use array indices) for FLIP to match positions correctly before and after.
With avatars:
<RankShuffleBoard
unit="pts"
entries={members.map((m) => ({
id: m.id,
name: m.name,
score: m.score,
avatar: <img src={m.avatarUrl} alt="" className="size-8 rounded-full" />,
}))}
/>Props
RankShuffleBoard
| Prop | Type | Default | Description |
|---|---|---|---|
entries | RankShuffleEntry[] | — | Leaderboard entries, sorted internally by score from high to low with a stable sort (ties keep their original order) |
unit | string | — | Unit text after the score (pt, pts, and so on) |
className | string | — | Appended to the outermost <ol> class |
RankShuffleEntry
| Field | Type | Description |
|---|---|---|
id | string | Stable unique identifier; the FLIP animation matches each row's before and after positions by it |
name | string | Display name |
score | number | Score, by which the component sorts internally from high to low |
avatar | React.ReactNode | Avatar to the left of the name (optional) |
How it works
- FLIP swapping: rows slide continuously to their new ranks with a Motion
layoutanimation (spring), matching before and after positions by the stableidkey instead of fading out and repainting - Gain / loss arrows: the component records the previous rank on a ref, and any row whose rank changed briefly flashes an arrow (up ↑ green / down ↓ red) that
AnimatePresencefades out after 1.5 seconds; another change within those 1.5 seconds resets the direction and the countdown. The arrow sits in a fixed-width slot, so its appearance and disappearance never push the score around - Podium vocabulary: the top three carry a gold (amber) / silver (slate) / bronze (orange) leading edge color band, with the rank badge enlarged and matching the same palette; from fourth place onward the palette is neutral
- Score spring tween: the score is written straight into the DOM through a MotionValue (with no React re-render), springing to the new value, displayed with thousands separators and rounded to whole numbers
- Stable sort: the original index acts as the tiebreak, so entries with equal scores keep the order they were passed in and never swap for no reason
Accessibility
- When the user has "reduce motion" enabled at the system level, FLIP is disabled (rows jump straight to their new positions), the score shows its final value directly, and the arrows play no fade in / fade out (keeping the DOM structure consistent with SSR)
- The score container carries an
aria-label(the final formatted value, including the unit), and the animating span mid-tween is hidden from assistive technology (aria-hidden), so the screen reader only ever announces the final value - The board is a semantic ordered
<ol>list; the gain/loss arrows and the color bands are decorative and are all markedaria-hidden
Year Pulse Grid
A GitHub-style wall of day cells for a whole year — a wave sweeps across on load lighting cells up by intensity, hovering a day floats an event card, and clicking a cell selects it and filters the linked sidebar list to that day's events.
Isotype Proportion Grid
An "X in every Y" isotype proportion grid — hundreds of small icons light up in a color wave as you scroll, stepping through different proportion statements one at a time.