Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

A CSS bar chart works when the categories and values are plain, readable HTML and CSS only sets the bar lengths, colours and layout. If the stylesheet fails to load or is overridden, the reader still gets every label and number. The CSS technique is simple. The part that determines whether the chart is sound is keeping the data in the markup, not in the styling.

How the work is divided between HTML and CSS

Progressive enhancement starts from content that works on its own and adds presentation where the browser supports it. For a bar chart, that means each category and its value exist as text before any CSS runs. CSS then reads a number from the markup, converts it into a bar length, and draws the bar. The words and numbers never depend on the bars.

Current CSS covers everything this approach needs: custom properties, calc() and min(). Older tutorials use the label “CSS3” for this, but CSS3 is a set of modules rather than a single version, and the techniques below rely on standard features that browsers have supported for several years.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Start with the data in HTML

The W3C and MDN guidance this article relies on favours meaningful HTML and the correct native element over added ARIA. It does not prescribe one markup pattern for every bar chart. The structure below is a practical choice for a short, single-series chart. The figures are illustrative, not measured.

#1 Best Overall
Sale
HTML and CSS: Design and Build Websites
  • HTML CSS Design and Build Web Sites
  • Comes with secure packaging
  • It can be a gift option
<h3>Books sold by genre (illustrative figures)</h3>
<ul class="bar-chart" style="--max: 80">
  <li style="--value: 72">
    <span class="bar-label">Fiction</span>
    <span class="bar"></span>
    <span class="bar-value">72 units</span>
  </li>
  <li style="--value: 48">
    <span class="bar-label">Non-fiction</span>
    <span class="bar"></span>
    <span class="bar-value">48 units</span>
  </li>
  <li style="--value: 21">
    <span class="bar-label">Poetry</span>
    <span class="bar"></span>
    <span class="bar-value">21 units</span>
  </li>
</ul>

Three details matter here. The heading names the chart in ordinary text. The label and the value sit in their own elements, so they can be read without any styling. The --value property carries the number that CSS uses, while the visible text carries the unit. The number therefore appears twice. If you generate the list from a data source, produce both from the same variable so they cannot drift apart.

Style the items as bars

  1. Remove the list bullets and lay out each item as a row. Use a grid with three columns: label, bar and value.
  2. Set a shared scale on the list. The --max property defines what 100% of the track represents. Every bar must use the same scale, or the lengths will mislead.
  3. Convert each value to a width. Divide the item’s value by the scale and clamp the result at 100%.
.bar-chart {
  list-style: none;
  margin: 0;
  padding: 0;
}
.bar-chart li {
  display: grid;
  grid-template-columns: 7rem 1fr 5rem;
  align-items: center;
  gap: 0.75rem;
  margin-block: 0.5rem;
}
.bar {
  display: block;
  height: 1rem;
  background: #2b6cb0;
  width: min(calc(var(--value, 0) / var(--max, 1) * 100%), 100%);
}

With a scale of 80, the Fiction bar fills 90% of the track, Non-fiction fills 60%, and Poetry fills 26.25%. The min() wrapper stops a value above the scale from spilling past the track, but it also hides the overflow. Choose a scale that covers your largest value, rather than relying on the clamp.

What the reader sees when styling fails

  • Stylesheet not loaded. The chart appears as a bulleted list of labels and values. Every category and number is still readable.
  • A value is missing or misspelled. The fallbacks in the CSS above (var(--value, 0) and var(--max, 1)) keep the width at zero. Without a fallback, the width is invalid and the span can stretch across the whole column, showing a full bar for missing data.
  • Colours removed. A high-contrast or forced-colours setting can strip the background from a bar. The value text still carries the information, which is why bar colour should never be the only signal.
  • Very long labels. A fixed label column can wrap or truncate text. Test the chart with your longest real category name before shipping.

Make the chart understandable to assistive technology

Give complex charts a text description or a data table

The W3C Web Accessibility Initiative explains that assistive technologies may not have access to structural information conveyed visually, and it shows how to link to or provide a fuller text description of a complex image. (W3C WAI, Complex Images) That guidance was written for images, but the same logic applies to a CSS chart. A list of one label and one value per item is already fully described by its text. If a chart has several series, time comparisons or annotations, supply a text summary or convert it into a real <table> with header cells. Presenting it this way is our recommendation for CSS charts, not a rule quoted from the W3C page.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use native elements and avoid unnecessary ARIA

MDN advises using the correct semantic HTML element before reaching for ARIA, because native elements already supply their semantics and behaviour. (MDN Web Docs, ARIA – Accessibility) A list already announces how many items it contains. The empty bar spans hold no text, so they add nothing to the accessible name, and they need no ARIA attributes. Adding roles to decorative styling is the most common mistake here, and it is covered in the next section.

Rank #3
Sale
Web Design with HTML, CSS, JavaScript and jQuery Set
  • Brand: Wiley
  • Set of 2 Volumes
  • A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers

Data chart or progress indicator?

A bar drawn to a length is not automatically a progress indicator. A chart that compares categories is a data display. A progress indicator reports a position on a completion range. The markup differs for each, and so does the role.

Aspect Static data chart Progress indicator
What the value means Independent category values compared with each other A single position on a completion range
Example Books sold per genre 3 of 5 checkout steps complete
Markup Native list or table with labels and values Native <progress value="3" max="5"> element with a text alternative inside
ARIA role None. Do not add role="progressbar" to category bars Native element supplies the semantics. MDN recommends native <progress> or <input type="range"> where appropriate. (MDN Web Docs, ARIA progressbar role)
Role of the bar width Decorative encoding of the value Shows how far the task has progressed

The Fiction chart above is a data chart, so it needs no progress markup. Use <progress> only when the example represents completion. If a region of the page is loading, WAI-ARIA 1.2 says to associate the status with the region using aria-describedby and to set aria-busy="true" on the region while it loads. That guidance applies to real loading progress, not to an ordinary data chart. (W3C, Accessible Rich Internet Applications (WAI-ARIA) 1.2)

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Choosing between CSS bars, SVG, canvas and charting libraries

The sources behind this article do not name a winner among CSS, SVG, canvas and charting libraries. The four criteria below help you decide, and the answer depends on whether your chart is static data or progress, and on how much of its meaning must reach assistive technology.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Criterion CSS bars on HTML SVG Canvas Charting library
Semantic content in HTML Labels and values are ordinary elements Shapes and text are in the DOM; meaning depends on the text and descriptions you add Pixels only; values are not in the DOM unless you add a fallback Depends on what the library renders; inspect its output
Readable without styling Yes, if labels and values are in the markup Only if the text is in the markup No, unless a text alternative is supplied Depends on the library
Values exposed to assistive technology Through the list or table text Through the text and descriptions you provide Only through a text alternative you provide Depends on the library’s output
Best fit Small static category data Custom graphics that need precise shapes Very large or animated datasets Complex, interactive charts when the library’s accessible output has been checked

For a handful of categories, CSS bars on a list or table keep the data in the document with the least machinery. Choose a heavier option when the chart needs behaviour the markup cannot give you.

The Bottom Line

“”

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.