Data✨ Premium

Bar Chart Race

Horizontal bar ranking: when a value changes, the bars stretch and the rows slide to their new rank.

CSSTransitionRace

Updated

This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Data has 28 effects, including 1 free. Explore the category →

3 usage examples

Pétanque tournament ranking on the refreshment stand screen — Bar Chart Race example 1

① Pétanque tournament ranking on the refreshment stand screen

WhenThe big screen of a village doubles tournament: title, round number, and the five teams ranked with thick bars. After each round (every 1.3 s in the scene), two teams score and rows overtake each other in front of the crowd.
WhyFrom a distance an overtake is noticed because the row moves; a static score table would force people to reread every number after each round.
Settingsdata-unit="pts", --rce-bar-height:34px, --rce-gap:10px, --rce-label-width:8.6em, --rce-max-width:none, pale yellow values, --rce-speed:.9s, default colours.
Voting for a school mascot's name — Bar Chart Race example 2

② Voting for a school mascot's name

WhenOn a school website, a card asks what to call the playground hedgehog. A click on "Vote Frisemouche" (or anywhere in the card, for the capture) adds one class's four votes; the bar stretches and the name climbs if it passes another.
WhyChildren see the effect of their vote at once; a name moving up makes the poll lively without showing percentages.
SettingsColour set row by row with style="--rce-color:…", --rce-bar-height:20px, --rce-label-width:7.4em, brown text on a cream card, unitless values.
Top 5 loans in a library website sidebar — Bar Chart Race example 3

③ Top 5 loans in a library website sidebar

WhenNext to an article on a library website, a "Most borrowed" box with two tabs. Bars grow when the box comes into view; the scene then switches between "Week" and "Month" on its own and the titles re-rank.
WhyIn a 214 px column, thin ranked bars show the gap between titles better than a plain numbered list, without stealing attention from the article.
SettingsOne colour for every row (--rce-color set by a .column .rce-item rule in the page CSS), --rce-bar-height:14px, --rce-label-width:6.6em, left-aligned labels.

How it works

The ranking is a real ordered list <ol class="rce-list">: each <li class="rce-item" data-value="…"> holds its label. The script adds a .rce-track with its .rce-bar and the .rce-val number to every row. The largest value fills the whole track and the others are proportional (or relative to data-max if you set it).

When a data-value changes, the script records each row's position, puts the rows back in descending order in the DOM, then starts them from their old place with a translateY that it brings back to zero: the FLIP technique. Rows therefore glide to their new rank while the bar widths change (0.8 s transition, cubic-bezier(.23, 1, .32, 1) curve). Several values changed at once give a single reorder.

At start-up every row gets one of five gradients (amber, blue, red, cyan, violet) based on its original position and keeps it while it climbs or drops. The first time the chart scrolls into view, the bars grow from zero.

Labels are truncated with an ellipsis when wider than their column (--rce-label-width); values are formatted in the page language with data-unit.

Accessibility

  • Reading order matches display order: rows are really moved in the DOM (no order property), so a screen reader goes through the list top to bottom as it is shown. Each row reads "label, value".
  • List name: the aria-label on .rce-list ("Ranking") says what is ranked; adapt it to your data.
  • Reduced motion: under prefers-reduced-motion: reduce, bars and rows take their final size and place without sliding, on first display and on updates.
  • No automatic announcement: a change of rank is not spoken. If an overtake matters (new leader), announce it in a separate sentence.

Browser compatibility

CSS Grid, width and transform transitions, MutationObserver, IntersectionObserver and Intl.NumberFormat. Measured in Chromium 151, Firefox 153 and WebKit 26.5: 150 ms after a change, rows sit between their old and new place in all three engines; two independent rankings on one page, 390 px, white background, no JS error.

Chrome 57+✓ Full (measured)
Firefox 55+✓ Full (measured)
Safari 12.1+✓ WebKit engine measured, not checked in real Safari
Edge 79+✓ Full (Chromium engine)
Safari iOS 12.2+✓ Not checked on a real device
Chrome Android 57+✓ Full (Chromium engine)

Without JavaScript the ordered list and its labels stay readable, with no bars or values: put the value in the label text if it must remain visible without script.

The code

HTML structure to paste into your page (the CSS + JS come with your purchase):

index.html — structure
<div class="fx-0245-root">
  <ol class="rce-list" aria-label="Ranking">
    <li class="rce-item" data-value="70"><span class="rce-label">Amber</span></li>
    <li class="rce-item" data-value="60"><span class="rce-label">Cobalt</span></li>
    <li class="rce-item" data-value="45"><span class="rce-label">Coral</span></li>
    <li class="rce-item" data-value="40"><span class="rce-label">Lagoon</span></li>
    <li class="rce-item" data-value="35"><span class="rce-label">Lilac</span></li>
  </ol>
</div>

One-time payment, no subscription — the code is yours forever

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
data-value on each .rce-item (HTML) 70, 60, 45, 40, 35 Row value. Changing it re-runs the bar stretch and the re-ranking.
data-max on the root (HTML) the largest value Value matching a full bar; handy for a fixed scale (e.g. 100).
data-unit, data-decimals (HTML) none, 0 Unit appended to each value ("42 pts") and decimals.
--rce-color (CSS, on one row or for the whole ranking) 5 rotating gradients Bar colour. Set on a row (style="--rce-color:…"), it follows that row wherever it moves. For the whole ranking, use a rule placed after the effect's CSS and more specific than .rce-item alone, for example .my-ranking .rce-item: otherwise the five default tints win.
--rce-bar-height, --rce-gap (CSS) 24px, 8px Bar thickness and space between rows.
--rce-label-width, --rce-max-width (CSS) 4.5em, 420px Width of the label column and maximum width of the ranking.
--rce-text, --rce-value (CSS) 70% white, 60% white Label and value colours; darken them on a light background.
--rce-speed (CSS) .8s Duration of the bar and row slides.
--rce-bg, --rce-min-height (CSS) #0a0a0f, 300px Root background and minimum height.

FAQ

Change the attributes one after another in the same block of code: rows.forEach((li, i) => li.setAttribute('data-value', scores[i])). The script waits for the block to finish before re-ranking, so rows only move once, all together.
As many as the list contains; past five, the colours start over. For a long ranking, reduce --rce-bar-height and --rce-gap, and keep in mind that ten rows moving at once becomes hard to follow.
The one already ahead: on a tie the script keeps the current order, so tied rows do not swap places for no reason on every update.