Standings

Animated Leaderboard for React

A ranking that races, like a timing tower. The place numbers stay put and the rows slide past them: when a score overtakes the one above, the two rows trade places, the climbing one passing over the other on a soft shadow, and a chip counts the places each has gained. Give it periods and it plays through them week by week, with a timeline to scrub back and forth; give it new scores and the rows race to the new order.

  • Data
  • Chart
  • Motion

Uses clsx, tailwind-merge

Inspired by ByDefault

Open in ChatGPTOpen in Claude
speed1.00×
stiffness260
changetrue
<Standings />

The demo follows as you drag.

Top free · Productivity

United States · daily downloads

Daybook rank

#6

  1. Tally Notes, 11,800
  2. Northdesk, 9,800
  3. Kiln Calendar, 8,600
  4. Pebble, 6,200
  5. Orbit Focus, 4,900
  6. Daybook (you), 3,100

Take this demo to your coding agent.
Add this moment from KeyframeUI to our project and adapt it to our content.

Standings: A ranking that races, like a timing tower. The place numbers stay put and the rows slide past them: when a score overtakes the one above, the two rows trade places, the climbing one passing over the other on a soft shadow, and a chip counts the places each has gained. Give it periods and it plays through them week by week, with a timeline to scrub back and forth; give it new scores and the rows race to the new order.
This demo, Category Chart: A journaling app's eight weeks after its relaunch, climbing a store's Productivity chart from sixth to first, with a rank readout that turns over at each pass.

1. Install it:
   npx shadcn@latest add @keyframeui/standings-demo
   (If the CLI can't find @keyframeui, use https://keyframeui.com/r/standings-demo.json instead.)
   It adds the demo, components/standings-demo.tsx, and the component, components/ui/standings.tsx.

2. Make it ours: our copy, data, images and colors. Keep the structure the motion depends on. If we only need the effect, use <Standings> on its own.

3. These props set its feel. Change them before you change the file:
   - speed (default 1): How fast the race runs along the timeline, as a multiple of the default pace: each period as it plays, the race to a clicked period, and the loop's hold, rewind and rest. `2` is twice as fast.
   - stiffness (default 260): Stiffness of the critically damped spring a row rides to its new place when it overtakes another. Higher lands sooner; at the default it lands in about 0.4s.
   - change (default true): Show a chip with the places each row has gained since the first period (or, without periods, since the standings first rendered).

4. Don't change, in components/ui/standings.tsx:
   - One frame loop (`createStandings`) owns every moving value and writes transforms and text straight to the DOM; React renders the rows once and only hears back when a period settles or the order changes.
   - The place numbers never move, the rows do: each slides by `transform` on a critically damped spring (damping `2 * sqrt(stiffness)`), so a swap never overshoots.
   - A row going up passes over the other: z-index 3 while rising, 1 while falling, 2 at rest, and its shadow's opacity follows its upward speed.
   - The loop sleeps once every row has landed; playing pauses off screen and on a hidden tab and picks up where it was.
   - Reduced motion: no autoplay, it rests on the last period, and a row that changes place jumps there and fades in; the timeline still steps and scrubs.
   Its timing is all in the TIMING block at the top of that file.

Docs: https://keyframeui.com/components/standings.md

Installation

Add the component on its own with the CLI, or copy the file in by hand.

The CLI writes components/ui/standings.tsx and installs clsx and tailwind-merge. It needs the @keyframeui registry in components.json; add it once per project.

npx shadcn@latest add @keyframeui/standings

Examples

Finished sections built with Standings. Each one installs whole, with the component included.

Benchmark Tabs

A model leaderboard on four benchmarks. Switch the tab and the same six models race to their new order.

Open leaderboard

Run of Nov 2

Accuracy on 1,400 multi-step problems

  1. Halcyon Pro, 84.1%
  2. Gannet XL, 81.7%
  3. Wren 3 (you), 79.4%
  4. Osprey 70B, 76.2%
  5. Marlin 2, 72.8%
  6. Petrel Mini, 61.5%

Take this demo to your coding agent.
Add this moment from KeyframeUI to our project and adapt it to our content.

Standings: A ranking that races, like a timing tower. The place numbers stay put and the rows slide past them: when a score overtakes the one above, the two rows trade places, the climbing one passing over the other on a soft shadow, and a chip counts the places each has gained. Give it periods and it plays through them week by week, with a timeline to scrub back and forth; give it new scores and the rows race to the new order.
This demo, Benchmark Tabs: A model leaderboard on four benchmarks. Switch the tab and the same six models race to their new order.

1. Install it:
   npx shadcn@latest add @keyframeui/standings-benchmark-demo
   (If the CLI can't find @keyframeui, use https://keyframeui.com/r/standings-benchmark-demo.json instead.)
   It adds the demo, components/standings-benchmark-demo.tsx, and the component, components/ui/standings.tsx.

2. Make it ours: our copy, data, images and colors. Keep the structure the motion depends on. If we only need the effect, use <Standings> on its own.

3. These props set its feel. Change them before you change the file:
   - speed (default 1): How fast the race runs along the timeline, as a multiple of the default pace: each period as it plays, the race to a clicked period, and the loop's hold, rewind and rest. `2` is twice as fast.
   - stiffness (default 260): Stiffness of the critically damped spring a row rides to its new place when it overtakes another. Higher lands sooner; at the default it lands in about 0.4s.
   - change (default true): Show a chip with the places each row has gained since the first period (or, without periods, since the standings first rendered).

4. Don't change, in components/ui/standings.tsx:
   - One frame loop (`createStandings`) owns every moving value and writes transforms and text straight to the DOM; React renders the rows once and only hears back when a period settles or the order changes.
   - The place numbers never move, the rows do: each slides by `transform` on a critically damped spring (damping `2 * sqrt(stiffness)`), so a swap never overshoots.
   - A row going up passes over the other: z-index 3 while rising, 1 while falling, 2 at rest, and its shadow's opacity follows its upward speed.
   - The loop sleeps once every row has landed; playing pauses off screen and on a hidden tab and picks up where it was.
   - Reduced motion: no autoplay, it rests on the last period, and a row that changes place jumps there and fades in; the timeline still steps and scrubs.
   Its timing is all in the TIMING block at the top of that file.

Docs: https://keyframeui.com/components/standings.md

Launch Day

A launch board on the day, ranked by live votes, where a fast riser works its way to the top.

Launching today on Dawnlist

Ranked by votes · resets at midnight

Live · 9:12

  1. Parcel Pigeon, 412
  2. Quietly, 388
  3. Moss, 351
  4. Inkwell (you), 296
  5. Tidy Tabs, 240
  6. Brightwave, 198

Take this demo to your coding agent.
Add this moment from KeyframeUI to our project and adapt it to our content.

Standings: A ranking that races, like a timing tower. The place numbers stay put and the rows slide past them: when a score overtakes the one above, the two rows trade places, the climbing one passing over the other on a soft shadow, and a chip counts the places each has gained. Give it periods and it plays through them week by week, with a timeline to scrub back and forth; give it new scores and the rows race to the new order.
This demo, Launch Day: A launch board on the day, ranked by live votes, where a fast riser works its way to the top.

1. Install it:
   npx shadcn@latest add @keyframeui/standings-launch-demo
   (If the CLI can't find @keyframeui, use https://keyframeui.com/r/standings-launch-demo.json instead.)
   It adds the demo, components/standings-launch-demo.tsx, and the component, components/ui/standings.tsx.

2. Make it ours: our copy, data, images and colors. Keep the structure the motion depends on. If we only need the effect, use <Standings> on its own.

3. These props set its feel. Change them before you change the file:
   - speed (default 1): How fast the race runs along the timeline, as a multiple of the default pace: each period as it plays, the race to a clicked period, and the loop's hold, rewind and rest. `2` is twice as fast.
   - stiffness (default 260): Stiffness of the critically damped spring a row rides to its new place when it overtakes another. Higher lands sooner; at the default it lands in about 0.4s.
   - change (default true): Show a chip with the places each row has gained since the first period (or, without periods, since the standings first rendered).

4. Don't change, in components/ui/standings.tsx:
   - One frame loop (`createStandings`) owns every moving value and writes transforms and text straight to the DOM; React renders the rows once and only hears back when a period settles or the order changes.
   - The place numbers never move, the rows do: each slides by `transform` on a critically damped spring (damping `2 * sqrt(stiffness)`), so a swap never overshoots.
   - A row going up passes over the other: z-index 3 while rising, 1 while falling, 2 at rest, and its shadow's opacity follows its upward speed.
   - The loop sleeps once every row has landed; playing pauses off screen and on a hidden tab and picks up where it was.
   - Reduced motion: no autoplay, it rests on the last period, and a row that changes place jumps there and fades in; the timeline still steps and scrubs.
   Its timing is all in the TIMING block at the top of that file.

Docs: https://keyframeui.com/components/standings.md

Props

entries Required

TypeStandingsEntry[]

The rows, in any order: they're ranked by score. Each is { id, label, detail?, icon?, value, you? }, where value is one number or one per period, and you tints the row in the accent and tags it "You".

periods

Typestring[]

Labels for the periods an entry's value array runs over ("W1", "Mar"). With two or more, a timeline under the rows plays and scrubs through them.

period

Typenumber

The period to show, controlled. The rows race to it when it changes.

defaultPeriod

Typenumber

The period to start on, uncontrolled. Defaults to the first when it autoplays, otherwise the last.

onPeriodChange

Type(period: number) => void

Called with the period a visitor picks, or that autoplay reaches.

onOrderChange

Type(ids: string[]) => void

Called with the ids in their new order, the moment a row overtakes another. Use it to keep a rank readout in step with the race.

autoplay

Typeboolean | numberDefaulttrue

Play through the periods once the standings scroll into view. A number sets the ms per period (1000 by default). Under reduced motion it shows the last period instead.

loop

TypebooleanDefaultfalse

After the last period, hold for 2.6s, rewind and play again.

speed

TypenumberDefault1

How fast the race runs along the timeline, as a multiple of the default pace: each period as it plays, the race to a clicked period, and the loop's hold, rewind and rest. 2 is twice as fast.

stiffness

TypenumberDefault260

Stiffness of the critically damped spring a row rides to its new place when it overtakes another. Higher lands sooner; at the default it lands in about 0.4s.

best

Type"highest" | "lowest"Default"highest"

Which end of the scores ranks first: "lowest" for times and prices.

max

Typenumber

The score that fills a bar. Defaults to the highest score in entries.

format

Type(value: number) => string

How a score is written. Defaults to a rounded number with thousands separators.

change

TypebooleanDefaulttrue

Show a chip with the places each row has gained since the first period (or, without periods, since the standings first rendered).

className

Typestring

Extra classes for the root. Rows are opaque so they can pass over each other: match them to your card with --standings-surface (default white, #09090b in dark), and set the accent with --standings-accent (default #ff5a1f) and the row height with --standings-row (default 3.25rem).