Data✨ Premium

Comparison Bars

Thin horizontal bars, label on the left and value on the right, that grow one after the other when the list comes into view.

CSSProgress

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

Range of three e-bikes in a cycle shop's comparison page — Comparison Bars example 1

① Range of three e-bikes in a cycle shop's comparison page

WhenThe “Compare” page of a bike shop: three bike cards (price, frame type) then, scrolling down, the “Stated range” block where three bars grow in kilometres. In the capture the page scrolls down to the block by itself.
WhyRange is often what decides between two models; bars on the same scale show the gap (85 versus 140 km) better than three numbers side by side.
Settingsdata-unit="km", data-max="150", values 120, 85 and 140, --cmpb-thickness: 12px, --cmpb-max-width: none, the same lime gradient for all three rows, dark text.
Share of assignments per field on the cards of a translator directory — Comparison Bars example 2

② Share of assignments per field on the cards of a translator directory

WhenA directory of freelance translators: two profile cards side by side (initials, speciality, languages, rate) with, in the middle of each, three 6 px bars showing the share of assignments per field. Both lists play together as soon as the cards show.
WhyClients first look for someone used to their kind of text. Two comparable cards, with the same small chart in the same place, are quick to scan.
SettingsTwo independent roots, data-unit="%", --cmpb-thickness: 6px, --cmpb-min-height: 0, violet gradients, labels translated with data-en in the demo page.
Score per theme shown after “See my result” at the end of a museum quiz — Comparison Bars example 3

③ Score per theme shown after “See my result” at the end of a museum quiz

WhenThe quiz of a family trail in a museum: after the tenth question, a “See my result” button replaces the closing message with a “Your score 7/10” card and three bars, painting, sculpture and archaeology, growing to the share of right answers.
WhyThe overall score says whether you did well, the bars say where to go back. They grow while the eye moves down the card, like a result being uncovered.
Settingsdata-unit="%", values 100, 50 and 67, labels “Painting (3 of 3)”…, --cmpb-thickness: 8px, gold, coral and sea-green gradients on a brown background; the card is a hidden removed on click.

How it works

Each row is a li.cmpb-item in the ul.cmpb-list list: its value in data-value, its label in span.cmpb-label. The script adds the .cmpb-value text to the right of the label and, underneath, a decorative .cmpb-track holding the .cmpb-fill. The displayed value and the length come from the same attribute: you cannot write “85%” above a bar filled to 62%.

The length is value ÷ data-max (100 by default), capped at the full track. The text is written with Intl.NumberFormat in the page language: with data-unit="%", “85%” in English and “85 %” in French; with another unit such as km, “120 km”. Labels that are too long are cut with an ellipsis instead of pushing the value off the row.

The animation waits for the visitor: an IntersectionObserver adds the cmpb-on class when 35% of the list is visible. Each fill then goes from scaleX(0) to scaleX(1) from the left, over 1.5 s (ease-out), starting at 0.1 s and 0.2 s apart from row to row. Three gradients (indigo-violet, magenta-pink, red-light pink) repeat every three rows.

Accessibility

  • Real text: each row reads “Workshop A 85%” in a list named by its aria-label. The track and its fill are hidden from screen readers.
  • Reduced motion: under prefers-reduced-motion: reduce, the bars show full from the start.
  • Nothing interactive: no row takes focus and nothing changes on hover or touch.
  • Contrast: on a light background, darken --cmpb-label and --cmpb-value; the default figure colour (50% white) is meant for the original dark background.

Browser compatibility

HTML and CSS (grid, custom properties, transform: scaleX) with a short script (IntersectionObserver, Intl.NumberFormat). Measured on 8 Nov 2026 in Chromium 151, Firefox 153 and WebKit 26.5 (Playwright): two independent copies, the second waits to be seen, reduced motion, 390 px wide, no errors.

Chrome 74+✓ Full (measured in Chromium 151)
Firefox 63+✓ Full (measured in Firefox 153)
Safari 12.1+✓ Measured in WebKit 26.5, not in real Safari
Edge 79+✓ Full (Chromium engine)
Safari iOS 12.2+✓ Not checked on a device
Chrome Android 74+✓ Full (Chromium engine)

Without IntersectionObserver, the bars grow on load. Without JavaScript only the labels appear: no value, no bar.

The code

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

index.html — structure
<div class="fx-0230-root" data-unit="%">
  <ul class="cmpb-list" aria-label="Workshop occupancy">
    <li class="cmpb-item" data-value="85"><span class="cmpb-label">Workshop A</span></li>
    <li class="cmpb-item" data-value="62"><span class="cmpb-label">Workshop B</span></li>
    <li class="cmpb-item" data-value="78"><span class="cmpb-label">Workshop C</span></li>
  </ul>
</div>

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

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Values (HTML — data-value on each li.cmpb-item) 85, 62, 78 One positive value per row; the displayed text is computed from it.
Maximum (HTML — data-max on the root) 100 The value that fills the whole track (e.g. data-max="150" for kilometres).
Unit (HTML — data-unit on the root) % % for a percentage in the page format, otherwise any short word (km, h, pts) appended after the number; empty for a bare number.
Colours (CSS — --cmpb-c1, --cmpb-c2, --cmpb-c3) Indigo → violet, magenta → pink, red → light pink gradients Background of each fill, repeated every three rows.
Track (CSS — --cmpb-track, --cmpb-thickness) rgba(99, 102, 241, .2), 8 px Track background colour and bar thickness.
Text (CSS — --cmpb-label, --cmpb-value) 80% white, 50% white Colour of the labels and of the values.
Size and background (CSS — --cmpb-max-width, --cmpb-min-height, --cmpb-bg) 420 px, 300 px, #0a0a0f Maximum list width (none to fill), minimum root height, background.

FAQ

Yes, if you change data-max: with data-max="150", 140 fills 93% of the track. A value above the maximum fills the whole track without overflowing, and its text stays exact.
The three variables --cmpb-c1 to --cmpb-c3 cover three rows and then repeat. For more colours, add a .cmpb-item:nth-child(4) .cmpb-fill { background: … } rule in your CSS.
No: the IntersectionObserver is disconnected after the first appearance. The chart then stays at its final state.