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.
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



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-labeland--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.
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):
<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 / property | Default | Effect |
|---|---|---|
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
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.--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.IntersectionObserver is disconnected after the first appearance. The chart then stays at its final state.