Bar Chart Animated
Vertical HTML bar chart: each bar grows up from the bottom with a slight bounce, one after the other, when the chart scrolls 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
The data lives in the HTML: a ul.brc-chart list where each li.brc-item holds its value in data-value and its label in span.brc-label. On start-up the script adds a decorative .brc-track (aria-hidden) to every item, holding the .brc-bar, plus a .brc-val text with the formatted value. A bar's height is value ÷ maximum: the maximum is the root's data-max attribute if present, otherwise the largest value in the list.
The chart does not play on page load but when it appears: an IntersectionObserver waits until at least 35% of the list is visible, reads the values again, then adds the brc-on class to the root. Each bar then goes from scaleY(0) to scaleY(1) in 1 s on a cubic-bezier(.34, 1.56, .64, 1) curve that overshoots slightly before settling, with a 0.1 s offset per bar. It only happens once.
With data-show-values on the root, a .brc-tip label sits above each bar and fades in right after it. Numbers are written with Intl.NumberFormat in the page language (nearest lang attribute): “1,240” on an English page, “1 240” on a French one. data-unit="%" gives a localised percentage; any other unit is appended after a non-breaking space.
The size follows the container: the list takes the available width up to --brc-max-width and the bars share the space without exceeding --brc-bar-width. The five original gradients (indigo, violet, magenta, pink, red) repeat every five items.
Accessibility
- Screen readers: the list is announced as a real list (“Activity this week, list, 5 items”) and each item gives its label followed by its value, e.g. “Mon 60”. The value comes from the
.brc-valtext, present even when nothing is shown on screen. Remember to adapt the list'saria-label. - Reduced motion: under
prefers-reduced-motion: reduce, bars and their values are drawn straight away at full height, with no growth and no fade. - Nothing to operate: the chart has no button or link, so it takes no keyboard focus and does not react to hover or touch.
- Colour: the gradients carry no information, the value is always written; if you replace them, keep enough contrast with the background.
Browser compatibility
HTML, CSS (custom properties, transform, @keyframes) and a small script (IntersectionObserver, Intl.NumberFormat). Measured on 8 Nov 2026 in Chromium 151, Firefox 153 and WebKit 26.5 (Playwright): two independent copies on one page, the second waits until it is shown before playing, reduced motion, 390 px width, no errors.
Without IntersectionObserver, the bars play straight away on load. Without JavaScript only the labels show (heights are computed by the script).
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0225-root">
<ul class="brc-chart" aria-label="Activity this week">
<li class="brc-item" data-value="60"><span class="brc-label">Mon</span></li>
<li class="brc-item" data-value="90"><span class="brc-label">Tue</span></li>
<li class="brc-item" data-value="45"><span class="brc-label">Wed</span></li>
<li class="brc-item" data-value="75"><span class="brc-label">Thu</span></li>
<li class="brc-item" data-value="55"><span class="brc-label">Fri</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.brc-item) |
60, 90, 45, 75, 55 | One positive number per item; add or remove li elements to change the number of bars. A negative or missing value counts as 0. |
Scale maximum (HTML — data-max on the root) |
The largest value | Set it (e.g. data-max="100") to compare several charts on the same scale. |
Visible values (HTML — data-show-values on the root) |
Absent: values read by screen readers only | Present: each bar carries its value above it. |
Unit (HTML — data-unit on the root) |
None | % gives a percentage in the page language's format; any other unit (km, h…) is appended after the number. |
Bar colours (CSS — --brc-c1 to --brc-c5) |
Indigo, violet, magenta, pink, red gradients | One background value per variable (colour or gradient); they repeat every five items. |
Dimensions (CSS — --brc-height, --brc-max-width, --brc-bar-width, --brc-min-height) |
160 px, 480 px, 48 px, 300 px | Height of the bar area, maximum list width, maximum bar width, minimum root height (set 0 inside a small component). |
Background and text (CSS — --brc-bg, --brc-text, --brc-value) |
#0a0a0f, 50% white, white | On a light site: --brc-bg: transparent and dark text colours. |
FAQ
hidden or display: none) the list is not visible to the IntersectionObserver; the growth starts when the panel is shown, then never replays.data-value attributes before that. Afterwards, changing an attribute redraws nothing: replace the chart with a fresh copy of the HTML and run the script again.data-value="6.2", data-value="1240"): the script formats it for the page, up to one decimal, with a comma for thousands in English and a space in French.