NimUI
ComponentsData Display

BarChart

Dependency-free categorical bar chart for dashboard cards and record panels

The BarChart component compares a handful of named categories — orders by channel, failures by reason, volume by warehouse — inside a dashboard card or a record detail panel. It is drawn with plain layout elements rather than SVG, so it reflows with its container and ships with zero charting dependencies.

BarChart is distinct from Meter: Meter reports one value inside a known range (role="meter"), while BarChart compares several categories against each other on a shared scale. Reach for it when the story is "which category is biggest", not "how full is this".

Import

import { BarChart } from '@nim-ui/components';

Playground

Change the values, tones, orientation, or max — bars rescale against the largest value unless you pin the scale.

Editable
<div className="w-full max-w-md">
<BarChart
  label="Orders by channel"
  showValues
  data={[
    { label: 'Web', value: 412 },
    { label: 'POS', value: 268 },
    { label: 'API', value: 97 },
    { label: 'Phone', value: 41 },
  ]}
/>
</div>

Vertical comparison

The default. Short category names sit under each column; showValues prints the formatted value at the top of each bar.

Vertical
Code
<BarChart
  label="Orders by channel"
  showValues
  data={[
    { label: 'Web', value: 412 },
    { label: 'POS', value: 268 },
    { label: 'API', value: 97 },
    { label: 'Phone', value: 41 },
  ]}
/>

Horizontal with per-datum tones

Use orientation="horizontal" when category names are long enough to truncate under a column. Per-datum tone flags the rows an operator should act on.

Horizontal
Code
<BarChart
  label="Failed payments by reason"
  orientation="horizontal"
  showValues
  data={[
    { label: 'Card declined', value: 34, tone: 'error' },
    { label: 'Address mismatch', value: 21, tone: 'warning' },
    { label: 'Manual review', value: 12 },
    { label: 'Expired card', value: 5 },
  ]}
/>

Fixed scale and formatted values

Pin max when the scale is meaningful on its own (a target, a quota, a percentage) so bars stay comparable between renders. valueFormatter controls both the printed value and the screen-reader enumeration.

Editable
<div className="w-full max-w-md">
<BarChart
  label="SLA attainment by tier"
  orientation="horizontal"
  tone="steel"
  max={100}
  showValues
  valueFormatter={(v) => `${v}%`}
  data={[
    { label: 'Tier 1', value: 98 },
    { label: 'Tier 2', value: 91 },
    { label: 'Tier 3', value: 76 },
  ]}
/>
</div>

Compact plot height

height sets the vertical plot height in px — drop it to around 72 for a chart tucked into a summary card.

Compact
Code
<BarChart
  label="Webhook retries per day"
  height={72}
  tone="warning"
  data={[
    { label: 'Mon', value: 12 },
    { label: 'Tue', value: 19 },
    { label: 'Wed', value: 7 },
    { label: 'Thu', value: 22 },
    { label: 'Fri', value: 15 },
  ]}
/>

Props

NameTypeDefaultDescription
data*Array<{ label: string; value: number; tone?: BarChartTone }>-Categories to plot, in display order
label*string-Accessible name for the chart
orientation'vertical' | 'horizontal''vertical'Columns with labels underneath, or rows with labels in a leading column
maxnumberlargest value in dataUpper bound of the scale; values above it are clamped to 100%
tone'ink' | 'steel' | 'success' | 'warning' | 'error''ink'Chart-level bar tone; a per-datum tone overrides it
showValuesbooleanfalsePrint the formatted value beside each bar — above the column when vertical, in a fixed-width trailing column when horizontal
valueFormatter(value: number) => stringStringFormats values for display and for the screen-reader enumeration
heightnumber120Plot height in px, vertical orientation only; a non-finite or non-positive value falls back to 120
aria-describedbystringgenerated description idReplaces the built-in screen-reader enumeration with your own description element
classNamestring-Additional CSS classes to apply

Accessibility

  • The root is role="img" with aria-label={label}, so the chart is announced as a single named graphic instead of a pile of empty divs.
  • A visually hidden enumeration of every label: value pair is referenced via aria-describedby, so screen readers get the actual numbers — not just a summary. Pass your own aria-describedby to substitute a richer description.
  • The drawn plot is aria-hidden, and values are formatted with valueFormatter in both the visual and the hidden text, so the two never disagree.
  • Tone is never the only signal: enable showValues, or pair the chart with a legend or table, wherever a color difference carries meaning.

Best Practices

Do

  • Keep it to roughly 3-8 categories — beyond that a DataTable reads faster
  • Pin max when the scale is meaningful (a quota, a target, a percentage) so charts stay comparable
  • Use valueFormatter for currency, percentages, and thousands separators
  • Reach for orientation="horizontal" as soon as category names start truncating
  • Use tone="steel" for a secondary data series — as data encoding, not decoration

Don't

  • Use BarChart for a single measurement against a limit — that is Meter
  • Give every bar a different tone; tone should mark exceptions, not label categories
  • Plot a time series with dozens of points — bar charts compare categories, not trends
  • Rely on tone alone to communicate a threshold breach without values or supporting text
  • Meter - Single value within a known range
  • MetricCard - KPI card that can host a chart
  • Stat - Single metric with trend
  • DataTable - Exact values across many rows

On this page