Home/ Cookbook/ Charts/ Vertical bars

Compare buckets with a vertical bar chart

By the end of this recipe you'll have a clean column chart with categorical x-axis, value labels on demand, and one bar quietly highlighted for the day's takeaway.

Charts Intermediate ~8 min React · @corelithzw/react

Overview

One bar per bucket. Heights compare directly because they share a baseline. The reader's eye does the maths for you.

Vertical bars answer "how big is each?" — pours per shift, orders per weekday, signups per product. They beat lines whenever the x-axis is discrete and unordered, or when you want the reader to compare individual values rather than read a trend.

Skip vertical bars if your category labels are long ("Premium plan with priority support" wraps and looks terrible vertical) — go horizontal instead. Skip if your bars are showing a continuous time series — that's a line chart's job.

Default opinion: sort by value, not by label. The reader's eye reads ranking before it reads names.

The chart

Daily pours at the gold mine clerk's window. Thursday gets the highlight because that's the day worth talking about.

Pours per shift, last seven days Bar chart showing pour counts by weekday. Thursday is the highest at 47; Monday is the lowest at 12. 6040200 12 22 33 47 38 28 18 Mon Tue Wed Thu Fri Sat Sun
Pours Highest day
Thursday at 47 pours, more than triple Monday's 12. Friday a moderate 38; weekend declines to 18 on Sunday.

Required pieces

From @corelithzw/react.

Intended exports used here: Chart.Bar.

Roadmap. Chart.Bar ships in @corelithzw/react v0.2.

The React snippet

Same shape as Chart.Line. Highlight one bar by returning a colour from a per-datum function.

PoursPerShift.tsx@corelithzw/react

Customising

Three forks.

Recolour per tone

Use a tone function when each bucket has a qualitative sense — success/danger/neutral — and the colour repeats that sense.

<Chart.Bar
  data={data}
  color={(d) =>
    d.pours > 30 ? 'success' :
    d.pours < 15 ? 'danger' : 'brand'}
  height={260}
/>

Sort + flip

Sort by value descending so the eye reads ranking. Flip the y-domain (or the bar direction) for a "lower is better" metric.

<Chart.Bar
  data={data.sort((a, b) => b.pours - a.pours)}
  xAccessor="day"
  yAccessor="pours"
  height={260}
/>

Animate from baseline

Grow each bar from the x-axis on first mount. Keep the stagger short (40–80ms) — long stagger reads as a loading state.

<Chart.Bar
  data={data}
  animate="grow"
  animateStaggerMs={60}
  height={260}
/>

In context

Inside a report layout — pours-per-shift with the totals on the left.

Pours per shift
196 total

Accessibility

Bars are forgiving — labels do most of the work, you just have to attach them.

  • Role and label. Root <svg> uses role="img" + aria-labelledby on inline <title> + <desc>.
  • Per-bar labels. Wrap each <rect> in a <g aria-label="Thursday: 47 pours"> so the screen reader names the value.
  • Screen-reader summary. A visually-hidden paragraph (.ck-sr-only) names the highest, the lowest, the total and any obvious pattern.
  • Highlight repeats the message. The highlighted bar is full opacity, but the value above it is also bold and brand-coloured — colour is never the only signal.
  • Keyboard. Each bar is tabindex="0"; Arrow keys walk the buckets, Home/End jump to the extremes.