Get in Touch

Every article with comparative data runs into the same decision: how to show it. The automatic answer is usually to install a charting library. But for half a dozen bars, that choice is expensive — and the irony is that the result is often less accessible than the three-line CSS alternative.

This article documents the chart pattern used across this blog: horizontal bars in plain HTML and CSS, with no dependencies. Every bit of the code below is running in production right here.

The problem: 500 KB to draw four bars

Charting libraries solve real problems — computed axes, scales, interaction, time series. The catch is that you pay for the whole bundle even when you use one percent of it.

To get the real numbers, we downloaded the minified files straight from the CDN and measured the bytes, without gzip:

JavaScript weight to render one simple bar chart

ApexCharts 3
527 KB
Chart.js 4
204 KB
Chartist 1
37 KB
Plain HTML and CSS
1.3 KB

Measured by Due Web Studio on 12 August 2026: minified files downloaded from jsDelivr, size in bytes without gzip compression. The "Plain HTML and CSS" row is the CSS block described in this article. Its bar is drawn at 1% for visibility — proportionally it would be 0.25%.

These are different orders of magnitude. And the cost does not stop at download: JavaScript has to be fetched, parsed and executed before it draws a single pixel, while CSS lands in the first paint. On a content page, where the chart usually sits in the middle of the text, that feeds straight into Largest Contentful Paint.

The markup: the value goes in the text, not in the bar

The most important decision in this pattern is not visual — it is where the information lives. Each chart row is a three-part grid: label, track and value.

The rule that drives everything: bar width is reinforcement of the information, never the information itself. The value appears as real text on every row.

That solves accessibility with no ARIA attributes at all. A screen reader reads "Chart.js, 204 KB" because both are text. If the CSS fails to load, the content still reads as a list. And you can select the numbers with a mouse, which a canvas chart never allows.

The structure of one row looks like this:

  • A container with the chart class, wrapping everything.
  • A short title in a paragraph with strong, stating what is measured and in which unit.
  • One row per bar, holding label, track and value.
  • Inside the track, the bar with its width as an inline percentage.
  • A source paragraph at the end, citing where the data came from.

The percentage is worked out as you write: the largest value in the series becomes 100% and the rest scale against it. No arithmetic happens in the visitor's browser.

The CSS: a three-column grid and a track that clips

Each row's layout is a simple grid. The first column uses minmax() for the label — a pixel floor so it never gets squeezed, and a percentage ceiling so it never dominates the row. The middle column takes the remaining space. The third shrinks to fit the value.

Why the track needs overflow hidden

The track has rounded corners and the bar lives inside it. Without overflow: hidden on the track, a bar at 100% spills past the corners and the rounding disappears on precisely the most important bar. With it, the track clips the bar and the curve holds.

A small detail that changes the finish: the bar rounds only its right-hand corners. On the left it sits at the origin, and a rounded corner there would suggest the bar starts somewhere after zero.

The detail almost everyone forgets: tabular-nums

Numeric values stacked in a column need to align. In proportional fonts a "1" is narrower than an "8", so "527 KB" and "204 KB" end up different widths and the column wobbles. The fix is one line: font-variant-numeric: tabular-nums, which gives every digit the same advance width. It is the difference between a chart that looks designed and one that looks thrown together.

Responsive without duplicating the chart

On a narrow screen, three columns do not fit: the label gets crushed and the bar becomes a dash. The answer is not to hide anything or ship a second markup — it is to rearrange the same grid.

In a media query below 480px, the grid drops to two columns and the label spans the full width above them. The result: label on top, bar and value side by side underneath. Same HTML, same information, no conditional logic in the content — four lines of CSS.

That is the structural advantage of building it with CSS Grid rather than canvas: the chart reflows like content, because it is content.

Track contrast: the quiet mistake

The background track is usually drawn in a very light grey, almost invisible. That is a genuine accessibility problem: the guidance on non-text contrast asks for a minimum contrast on components that carry information.

Because the value lives in the text here, the track is not the only source of the information, which lowers the risk. Even so, an invisible track removes the sense of scale and makes comparing bars harder. It is worth checking contrast between track and background, and between bar and track.

When the library is genuinely worth it

It would be dishonest to end without this. The pattern above is good for one specific case: few bars, direct comparison, static data inside a piece of text. Outside that, it breaks quickly.

  • Computed axes and scales: if the axis has to adapt to the data on its own, you will reimplement the library, worse.
  • Real interaction: tooltips, zoom, filtering a series by clicking the legend — none of that comes out of CSS.
  • Many data points: hundreds of rows of HTML weigh more than the library you avoided.
  • Geometry: pie, radar, scatter and line all depend on calculation. The horizontal bar is the rare case where the geometry is just a width.
  • Data that changes: if it comes from an API and updates itself, you already have JavaScript on the page anyway.

The practical test: if the chart is static, small and comparative, CSS wins comfortably. If it is interactive or dynamic, use the library and feel no guilt about it.

Key takeaways

  • Put the value in the text. That covers accessibility, copy-paste and the no-CSS case in one move.
  • Work out the proportion as you write, not in the visitor's browser.
  • Use a three-column grid and rearrange to two on mobile — same markup.
  • Do not forget overflow: hidden on the track and tabular-nums on the value.
  • Drive the colours from CSS variables so the chart follows the theme.
  • Know when to stop and install the library.

Conclusion

Choosing between plain CSS and a charting library is not about purism — it is about proportion between the tool and the problem. Half a dozen bars in an article does not justify half a megabyte of JavaScript, and the CSS version ends up more accessible for free, because the data starts life as text.

The same attention to weight and accessibility goes into the business websites and landing pages we build, and into the SEO and maintenance work that follows delivery. If you want to talk through your project, get in touch.

FAQ

Do CSS-only charts work without JavaScript?+
Yes. The bar width is set by an inline style attribute in the HTML, so the chart renders on the first paint without waiting for any script. If JavaScript fails or is blocked, the chart is still there.
Is this kind of chart accessible to screen readers?+
It is, as long as the value lives in the text and not only in the bar width. In the pattern described here every row carries its label and value as real text, so a screen reader announces "Chart.js, 204 KB" without seeing the bar at all. Width is visual reinforcement, not the information.
When is a charting library actually worth it?+
When you need computed axes, interactive tooltips, zoom, time series, many data points, or chart types that depend on geometry such as pie, radar and scatter. For half a dozen comparison bars inside an article, the library is dead weight.
How do I make the bars proportional?+
Work out each value as a percentage of the largest one in the series and write it into the inline style: the largest becomes 100% and the rest scale against it. The maths happens while you write the content, not in the visitor browser.
Does this chart work in dark mode?+
It does if the colours come from CSS variables instead of fixed values. Using tokens for the bar, the track and the text means a theme switch recalculates everything on its own, with no duplicated rules.
Due Web Studio
Written by

Due Web Studio

Due Web Studio specializes in professional website development, SEO and digital performance. We build fast, optimized, results-driven online presences for companies that want to grow online.

Recommended

Tools we recommend

The platforms we rely on with clients. Some are affiliate links.

Disclosure: some links above are affiliate links — if you sign up through them, Due Web Studio may earn a commission at no extra cost to you.