Skip to content
kerfjsGitHub

BarChart

Compares amounts across categories, grouped, stacked or as shares of a whole.

SourcePackage @kerf-js/veneerTry it

Preview

tsx
import { BarChart } from '@kerf-js/veneer'export function Deploys() {  return (  <BarChart    title="Deploys by team"    description="Last five quarters"    data={DEPLOYS_BY_TEAM}    x="quarter"    xName="Quarter"    series={TEAMS}  />  )}const DEPLOYS_BY_TEAM = [  {    quarter: 'Q4 2025',    platform: 96,    web: 142,    data: 58,    mobile: 41,  },  {    quarter: 'Q1 2026',    platform: 104,    web: 151,    data: 66,    mobile: 47,  },  {    quarter: 'Q2 2026',    platform: 118,    web: 164,    data: 71,    mobile: 52,  },  {    quarter: 'Q3 2026',    platform: 127,    web: 188,    data: 84,    mobile: 49,  },  {    quarter: 'Q4 2026',    platform: 139,    web: 203,    data: 92,    mobile: 61,  },]const TEAMS = [  { key: 'platform', name: 'Platform' },  { key: 'web', name: 'Web' },  { key: 'data', name: 'Data' },  { key: 'mobile', name: 'Mobile' },]

Installation

npm install @kerf-js/veneer

Try it

Change the controls: the preview and its code update together. The address keeps what you set, so you can link to it.

Live preview
Controls
Layout
Codeupdates as you change the controls
import { BarChart } from '@kerf-js/veneer'<BarChart title="Deploys by team" data={DEPLOYS_BY_TEAM} x="quarter" series={TEAMS} />

Usage

tsx
import { BarChart } from '@kerf-js/veneer'<BarChart  title="Deploys by team"  description="Last five quarters"  data={DEPLOYS_BY_TEAM}  x="quarter"  xName="Quarter"  series={TEAMS}/>

Stacked

layout="stacked" shows the total as well as its parts.

tsx
import { BarChart } from '@kerf-js/veneer'export function Stacked() {  return (  <BarChart    title="Monthly bill by service"    description="November to October"    data={COST_BY_SERVICE}    x="month"    xName="Month"    series={SERVICES}    layout="stacked"    format={dollars}  />  )}const TRAFFIC = [  {    month: 'Nov',    requests: 41.2,    errors: 0.42,    deploys: 88,    cost: 6120,  },  {    month: 'Dec',    requests: 52.8,    errors: 0.51,    deploys: 74,    cost: 6480,  },  {    month: 'Jan',    requests: 47.1,    errors: 0.38,    deploys: 96,    cost: 6390,  },  {    month: 'Feb',    requests: 49.6,    errors: 0.33,    deploys: 104,    cost: 6710,  },  {    month: 'Mar',    requests: 58.3,    errors: 0.47,    deploys: 118,    cost: 7240,  },  {    month: 'Apr',    requests: 61.9,    errors: 0.29,    deploys: 126,    cost: 7380,  },  {    month: 'May',    requests: 66.4,    errors: 0.31,    deploys: 131,    cost: 7690,  },  {    month: 'Jun',    requests: 64.2,    errors: 0.44,    deploys: 122,    cost: 7620,  },  {    month: 'Jul',    requests: 71.5,    errors: 0.27,    deploys: 139,    cost: 8010,  },  {    month: 'Aug',    requests: 74.8,    errors: 0.25,    deploys: 144,    cost: 8230,  },  {    month: 'Sep',    requests: 79.3,    errors: 0.36,    deploys: 152,    cost: 8560,  },  {    month: 'Oct',    requests: 86.1,    errors: 0.22,    deploys: 167,    cost: 8930,  },]const COST_BY_SERVICE = TRAFFIC.map((t) => {  const compute = Math.round(t.cost * 0.56),    storage = Math.round(t.cost * 0.17),    database = Math.round(t.cost * 0.15)  return {    month: t.month,    compute,    storage,    database,    network: t.cost - compute - storage - database,  }})const dollars = (v: number) =>  `$${v.toLocaleString('en-US')}`const SERVICES = [  { key: 'compute', name: 'Compute' },  { key: 'storage', name: 'Storage' },  { key: 'database', name: 'Database' },  { key: 'network', name: 'Network' },]

Percent

layout="percent" makes every bar the same height, so the mix is what you read.

tsx
import { BarChart } from '@kerf-js/veneer'export function Percent() {  return (  <BarChart    title="Deploys by team"    description="Each quarter as a share of its deploys"    data={DEPLOYS_BY_TEAM}    x="quarter"    xName="Quarter"    series={TEAMS}    layout="percent"  />  )}const DEPLOYS_BY_TEAM = [  {    quarter: 'Q4 2025',    platform: 96,    web: 142,    data: 58,    mobile: 41,  },  {    quarter: 'Q1 2026',    platform: 104,    web: 151,    data: 66,    mobile: 47,  },  {    quarter: 'Q2 2026',    platform: 118,    web: 164,    data: 71,    mobile: 52,  },  {    quarter: 'Q3 2026',    platform: 127,    web: 188,    data: 84,    mobile: 49,  },  {    quarter: 'Q4 2026',    platform: 139,    web: 203,    data: 92,    mobile: 61,  },]const TEAMS = [  { key: 'platform', name: 'Platform' },  { key: 'web', name: 'Web' },  { key: 'data', name: 'Data' },  { key: 'mobile', name: 'Mobile' },]

With a range switch

New data moves the bars. Anything in toolbar sits beside the view toggle.

tsx
import { state } from '@kerf-js/core'import { BarChart, Segmented } from '@kerf-js/veneer'export function Range() {  const range = state('6')  return (    <BarChart      title="Monthly bill by service"      data={COST_BY_SERVICE.slice(-Number(range.value))}      x="month"      xName="Month"      series={SERVICES}      layout="stacked"      format={dollars}      toolbar={        <Segmented          label="Range"          size="sm"          value={range.value}          onChange={(v) => (range.value = v)}          options={RANGES}        />      }    />  )}const TRAFFIC = [  {    month: 'Nov',    requests: 41.2,    errors: 0.42,    deploys: 88,    cost: 6120,  },  {    month: 'Dec',    requests: 52.8,    errors: 0.51,    deploys: 74,    cost: 6480,  },  {    month: 'Jan',    requests: 47.1,    errors: 0.38,    deploys: 96,    cost: 6390,  },  {    month: 'Feb',    requests: 49.6,    errors: 0.33,    deploys: 104,    cost: 6710,  },  {    month: 'Mar',    requests: 58.3,    errors: 0.47,    deploys: 118,    cost: 7240,  },  {    month: 'Apr',    requests: 61.9,    errors: 0.29,    deploys: 126,    cost: 7380,  },  {    month: 'May',    requests: 66.4,    errors: 0.31,    deploys: 131,    cost: 7690,  },  {    month: 'Jun',    requests: 64.2,    errors: 0.44,    deploys: 122,    cost: 7620,  },  {    month: 'Jul',    requests: 71.5,    errors: 0.27,    deploys: 139,    cost: 8010,  },  {    month: 'Aug',    requests: 74.8,    errors: 0.25,    deploys: 144,    cost: 8230,  },  {    month: 'Sep',    requests: 79.3,    errors: 0.36,    deploys: 152,    cost: 8560,  },  {    month: 'Oct',    requests: 86.1,    errors: 0.22,    deploys: 167,    cost: 8930,  },]const COST_BY_SERVICE = TRAFFIC.map((t) => {  const compute = Math.round(t.cost * 0.56),    storage = Math.round(t.cost * 0.17),    database = Math.round(t.cost * 0.15)  return {    month: t.month,    compute,    storage,    database,    network: t.cost - compute - storage - database,  }})const dollars = (v: number) =>  `$${v.toLocaleString('en-US')}`const SERVICES = [  { key: 'compute', name: 'Compute' },  { key: 'storage', name: 'Storage' },  { key: 'database', name: 'Database' },  { key: 'network', name: 'Network' },]const RANGES = [  { value: '6', label: '6 months' },  { value: '12', label: '12 months' },]

When to use

Use it for

  • Comparing values across a few categories or periods
  • Showing how a total is made up over time

Reach for something else

  • LineChart, when the trend matters more than each value
  • DonutChart, when there is one total, split a few ways

Accessibility

  • The toggle in the corner swaps the plot for a real table of the same numbers.
  • Each bar has a label, and a sentence describing the data is written for screen readers. Set summary to replace it.

API Reference

PropTypeDefaultDescription
titlestring—The heading.
level2 | 3 | 4 | 6 | 5—The title's heading level: one below the heading the chart sits under. Default 3, a chart in a page section.
descriptionunknown—One supporting line under the title.
toolbarunknown—Beside the view toggle: a layout switch, a range picker.
state"error" | "loading" | "empty"—Replaces the plot, in the same frame. With no data the frame shows empty on its own.
loadingstring—Shows that work is under way.
empty{ title: string; detail?: unknown; }—The title and detail shown in place of the plot when there is no data.
error{ title: string; detail?: unknown; }—A message that marks the field invalid and says why.
actionunknown—The next step under an empty or error state: a Retry button.
dataobject[]—The series to draw.
summarystring | ((c: { data: object[]; title: string; }) => string)—One sentence for screen readers and the page; generated from the data when absent.
xstring—The key in each row that names the category.
seriesSeriesDef[]—The keys to draw, each a string or { key, name, color }.
formatFmt—Turns a value into text in tooltips, labels and the table.
axisFormatFmt—A shorter format for the axis alone.
xNamestring—What the x values are, for the table and tooltips.
layoutBarLayout—grouped side by side, stacked on top of each other, or percent stacked to 100%.

RelatedLine chartCombo chartChart