Charts · Column Chart

Column Chart

Time-series vertical bars for activity, volume, and counts. Data-ink discipline (Tufte) — one accent, no gridlines, monospace numerics. Built-in source attribution and ASCII empty state.

Studio · Code, chart, settings

Three-panel workbench. Tweak any setting on the right — chart in the middle updates live and the code on the left regenerates ready to paste into your app.

Code
import { ColumnChart } from "@/components/charts/column-chart";

const data = [142, 168, 187, 159, 203, 178, 215];
const labels = ["MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"];

export function Example() {
  return (
    <ColumnChart
      data={data}
      labels={labels}
      height={240}
      gap={4}
      header={{ title: "Active users", subtitle: "Last 7 days" }}
      trend={0.184}
      goal={{ value: 190, label: "Weekly target" }}
      source="Brock Analytics, 2026"
      exportable
      exportFileName="active-users-7d"
    />
  );
}
Chart
Active users
Last 7 days
Source: Brock Analytics, 2026
Column chart with 7 data points. Source: Brock Analytics, 2026.
Column chart with 7 data points. Source: Brock Analytics, 2026.
LabelValue
MON142
TUE168
WED187
THU159
FRI203
SAT178
SUN215

Installation

npx shadcn@latest add brockui.com/r/column-chart

Usage

import { ColumnChart } from "@/components/charts/column-chart";

const data = [142, 168, 187, 159, 203, 178, 215];
const labels = ["MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"];

export function Example() {
  return (
    <ColumnChart
      data={data}
      labels={labels}
      height={220}
      trend={0.184}
      source="Brock Analytics, 2026"
    />
  );
}

Props

NameTypeDefaultDescription
datanumber[] | DataPoint[]Bar values. Two forms: number[] (with labels prop) or { label?, value, pattern?, color?, highlight?, note? }[] (object form — supports per-bar overrides for color, hatching, emphasis outline, and editorial annotation)
labelsstring[]undefinedX-axis labels (rendered in pixel font under bars + in hover tooltip). Only used when data is number[]
heightnumber200Chart height in pixels (Y-axis + bars area)
gapnumber4Gap between bars in pixels. Auto-shrinks for dense datasets (60+ bars)
accentstringvar(--brock-accent)Override the bar fill color (any CSS color or var). Defaults to Brock orange
barRadiusnumber0Top-corner radius in px. Common values: 0 (sharp), 2 (subtle), 6 (rounded)
header{ title?, subtitle? }undefinedTitle + subtitle block above the chart
xAxis{ title?, hideTicks? }undefinedX-axis configuration (title below ticks, hide tick labels)
yAxis{ title?, min?, max?, hideTicks? }undefinedY-axis configuration (vertical title, custom min/max, hide tick labels)
numberFormat{ prefix?, suffix?, decimals?, locale?, notation?, style?, currency? }undefinedNumber formatter applied to Y-axis, tooltip, and data labels. Supports BCP-47 locale, Intl.NumberFormat notation ('compact' → 1.2K), style ('currency' / 'percent'), and ISO 4217 currency. Explicit formatValue/yAxisFormat win
dataLabels{ show?, format? }undefinedShow value above each bar (Hack mono). Optional per-label formatter overrides numberFormat
pattern'solid' | 'hatched''solid'Default fill pattern for all bars. Per-point pattern on a data point wins over this. Hatched encodes historical/estimated/in-progress without spending a second color (Tufte)
hatchUntilIndexnumberundefinedConvenience: bars with index < N render hatched, the rest render solid. Classic historical-vs-projected encoding
hatchFromIndexnumberundefinedMirror of hatchUntilIndex: bars with index >= N render hatched. Useful for forecast bands and 'last N hatched' patterns. Combinable with hatchUntilIndex (union)
patternStyle'diagonal' | 'diagonal-reverse' | 'dots' | 'vertical' | 'horizontal''diagonal'Visual style of hatched bars (chart-level). Per-bar pattern still controls whether a bar is hatched; this controls how each hatched bar looks. Use 'dots' for grayscale/print
scroll'none' | 'auto''none'Overflow behavior when bars don't fit. 'auto' enables horizontal scroll; Y-axis pins to the left while bars + X-axis scroll together
minBarWidthnumber4Minimum px per bar. Used with scroll='auto' to decide chart min-width: N*minBarWidth + (N-1)*gap. Ignored when scroll='none'
bands{ from, to, label?, color? }[]undefinedPlot bands — highlighted vertical zones over a range of bar indices. Editorial pattern ('Q3', 'deployment window'). Render behind bars at low opacity. Indices are clamped to the data range
trendnumberundefinedDecimal trend indicator e.g. 0.184 → ↗ +18.4%. Orange if positive, muted if negative
goal{ value, label? }undefinedDashed reference line at value. Goal is included in max scale so it stays visible above bars. KPI dashboard pattern
sourcestringundefinedAttribution line rendered below the chart (FT pattern)
animation{ enabled?, duration? }{ enabled: true, duration: 400 }Staggered bar-rise on mount. Disabled automatically when prefers-reduced-motion is set
loadingbooleanfalseLoading state. With no data → full skeleton (dashed ghost bars + LOADING badge, ARIA role=status). With data → dim overlay on top of the chart for background refresh. Honors prefers-reduced-motion
errorError | string | nullnullTerminal error state. Replaces the chart even when data is present (stale data next to an error is misleading). Accepts an Error, a string message, or null. ARIA role=alert
onRetry() => voidundefinedCallback for the retry button in the default error state. The button is rendered only when this prop is provided
loadingLabelstring'Loading…'Label rendered next to the LOADING badge and used as the ARIA label for the skeleton state. Override for localization
errorLabelstring'Error'Label rendered above the error message and used as the ARIA label. Override for localization
retryLabelstring'Retry'Label of the retry button in the default error state. Override for localization
loadingFallbackReactNodeundefinedFull override of the default skeleton + overlay UI. Use for a custom-branded loading experience
errorFallbackReactNode | (error: Error) => ReactNodeundefinedFull override of the default error UI. May be a React node or a function that receives the normalized Error
exportableboolean | { png?, svg?, csv?, copy? }falseShow the export toolbar (top-right). true = all 4 actions; object form = enable specific ones. Imperative ref methods always work regardless
exportFileNamestring | (format) => string'chart'Base file name for downloads. String for fixed, function for per-format. Right extension (.png/.svg/.csv) auto-appended
onExport(format, artifact) => voidundefinedFires after an export completes. Receives format ('png'|'svg'|'csv'|'copy') and the artifact (Blob for png/copy, string for svg/csv). Useful for analytics or custom share flows
refRef<ColumnChartHandle>Imperative API: { exportSVG, exportPNG, exportCSV, copyImage }. Each method accepts { fileName, download, width, height } (scale extra on PNG). Works even while the chart is in loading/error/empty state
descriptionstringauto-generatedAccessible description for screen readers (figcaption + table caption). Defaults to 'Column chart with N data points. Source: ...'
formatValue(v: number) => stringtoLocaleStringFormat function for hover tooltip value. Wins over numberFormat
yAxisFormat(v: number) => stringtoLocaleStringFormat function for Y-axis tick labels. Wins over numberFormat

Accessibility

WCAG AA compliant. Keyboard navigable, screen-reader friendly, honors prefers-reduced-motion.

Keyboard
TabMove focus into the chart (single tab stop)
← → ↑ ↓Navigate between bars (roving tabindex)
HomeJump to first bar
EndJump to last bar
Screen reader markup
  • · <figure role="figure"> wraps the chart with anaria-labelledby pointing to the figcaption
  • · Each bar uses role="graphics-symbol" witharia-label="LABEL: value"
  • · A visually-hidden <table class="sr-only"> provides a tabular data summary (caption + rows for each data point)
  • · Trend indicator gets human-readable label (“Trend up 18.4 percent”) — arrows are aria-hidden

Design moves

  1. 1. Monospace Y-axis numbers + tabular-nums (Hack font) — values align by digit width.
  2. 2. Single--brock-accent(orange) for all bars. No gradient, no glow, no per-bar color coding.
  3. 3. No gridlines. Single 1px baseline at zero (Tufte data-ink).
  4. 4. Hover-tooltip with value in Hack mono + period in Departure Mono pixel-font badge.
  5. 5. Staggered entry animation (30ms cascade, scale-Y from baseline). Disabled onprefers-reduced-motion.
  6. 6. Built-insourceprop renders FT/Bloomberg-style attribution line below the chart.
  7. 7. ASCII empty state (▒▒▒ no data) when the dataset is empty — no separateisLoadingprop needed.

When to use

Column Chart fits time-series data where each bar is a discrete temporal bucket — hours, days, weeks, monthly buckets, agent calls per minute. Best for showing volume, count, or activity rhythm at a glance with sparse axes that don’t compete with the data.

When not to use

For continuous trends use Line Chart. For tiny embedded charts inside metric cards or prose use Sparkline. For categorical ranking with long labels (horizontal bars) use Bar Chart (coming soon). For composition over time use Stacked Column Chart (coming soon).

Inspired by

  • · Financial Times Visual Journalism — sparse axes, source-line attribution, single-color bars
  • · Stripe Annual Letters — inline numerics in editorial flow
  • · The Pudding — interactive storytelling with restraint
  • · Edward Tufte, The Visual Display of Quantitative Information — data-ink discipline