Bar chart

Grouped, stacked or 100%, vertical or horizontal, with a measured callout and a computed takeaway. Same props on both engines.

Install

Pick one engine. Both builds take the same props, so switching later changes only the import path.

Recharts

npx shadcn@latest add https://beautifulcharts.dev/r/instrument-bar-chart-recharts.json

import { BarChart } from "@/components/beautiful-charts/themes/instrument/recharts/bar-chart";
npm dependencies: recharts@^3.10.1

Apache ECharts

npx shadcn@latest add https://beautifulcharts.dev/r/instrument-bar-chart-echarts.json

import { BarChart } from "@/components/beautiful-charts/themes/instrument/echarts/bar-chart";
npm dependencies: echarts@^6.1.0

Example

A complete client module. Paste it into your app after installing the Recharts build; for ECharts, change recharts to echarts in the import. To pick options visually and copy the matching code, use the Customizer.

"use client";

import { BarChart } from "@/components/beautiful-charts/themes/instrument/recharts/bar-chart";
import type { ChartConfig } from "@/components/beautiful-charts/core/contracts";

const data = [
  { month: "Jan", signups: 186, trials: 80 },
  { month: "Feb", signups: 305, trials: 200 },
  { month: "Mar", signups: 237, trials: 120 },
  { month: "Apr", signups: 73, trials: 190 },
  { month: "May", signups: 209, trials: 130 },
  { month: "Jun", signups: 214, trials: 140 },
];

const config = {
  signups: { label: "Sign-ups", color: "var(--chart-1)" },
  trials: { label: "Trials", color: "var(--chart-2)" },
} satisfies ChartConfig;

export function SignupsByMonth() {
  return (
    <BarChart
      data={data}
      xKey="month"
      config={config}
      title="Sign-ups and trials"
      description="January to June"
      stack="stacked"
      valueFormatter={(value) => value.toLocaleString("en-US")}
      onSelectedChange={(selection) => console.log(selection?.datum.month, selection?.seriesKey)}
    />
  );
}

Bar chart props

Generated from BarChartProps in the installed source, so this table and your editor's hints always agree. * marks a required prop.

Bar chart props
PropTypeDescription
xKey*keyof TDatum & stringThe category or time key on each row, e.g. "month".
gridbooleanDraw grid lines behind the plot. On by default.
xAxisAxisOptionsCategory axis: hide it, format ticks, or add a label.
yAxisAxisOptionsValue axis: hide it, format ticks, or add a label.
referencesReferenceLine[]Target or threshold lines, fixed or computed from a series (average, median, max, min).
series(keyof TDatum & string)[]Series keys to draw, in order. Defaults to the keys in config, then numeric keys in the data.
stackStackMode"none" groups bars side by side and overlays areas. "stacked" stacks them. "percent" splits each row by its absolute total: positive parts stack up from zero, negative parts down, and the parts' sizes add to 100%.
orientation"vertical" | "horizontal""vertical" (default) draws columns; "horizontal" draws bars along the value axis.
curveCurveLine and area interpolation. "monotone" (default) never overshoots the data.
radiusnumberBar corner radius in px. A series' own radius in config wins.
barGapnumberSpace between bars in one category, in px.
barCategoryGapnumberShare of each category left empty around its bars, 0–0.9.
maxBarSizenumberWidest a bar can get, in px.
showValuesbooleanDraw each value on its mark. Labels that don't fit are left out; the table and tooltip keep every value.
highlight"max" | "none"Emphasise the largest category and dim the rest. Ties, and rows with no positive total, emphasise nothing.
forecastFromnumberRows from this index on (inclusive) are a forecast: hatched bars, dashed lines and hatched areas.
polarity"up" | "down"Whether a rise is good ("up", the default) or bad ("down", e.g. costs or churn). Tints the delta by meaning.
brushboolean | BrushOptionsAn overview strip under the plot with a draggable window. Indexes in callbacks stay indexes into data.
readoutbooleanThe big number in the header: the total for bars, the latest value for trends. Pass false to hide it (e.g. when a stat row says it better, or when a total means nothing, like a sum of percentages).
background"none" | "rule" | "dots" | "grid" | "crosshair"A quiet pattern behind the plot, drawn by the theme.
backgroundSlot(plot: { x: number; y: number; width: number; height: number }) => ReactNodeDraw your own SVG behind the plot (any pattern or artwork). Receives the plot rectangle in px.

Recharts build only

Native Recharts children render inside the chart with your original rows, so ReferenceArea, Label or a custom Customized layer work as they do in any Recharts chart.

Recharts build props
PropTypeDescription
childrenReactNodeRecharts children render inside the chart: reference areas, custom labels, anything Recharts accepts.

Apache ECharts build only

Escape hatches into the engine. Your option is deep-merged over the generated one, so you can change one nested setting without replacing a whole section.

Apache ECharts build props
PropTypeDescription
rendererEChartsRenderer"canvas" (default, best for large data) or "svg".
optionOptionOverrideDeep-merged over the generated ECharts option (plain objects merge; arrays and class instances replace), or a function that receives the generated option and returns the final one. See the chart docs.

Series config

One entry per series key in config. shadcn's ChartConfig entries work as they are. Give a series at most one of color, theme or colors; TypeScript rejects more than one.

Bar chart series config
PropTypeDescription
labelReactNodeName shown in the legend, tooltip and table. Defaults to the key, humanised.
ariaLabelstringPlain text for tables, summaries and announcements when label is not a string.
iconComponentType<{ className?: string }>An icon component (e.g. from lucide-react) shown in place of the swatch in the legend and tooltip.
format(value: number) => stringFormats this series' values everywhere: tooltip, legend, table, readout.
fillFillVariantBody fill: gradient (default for bars and areas), solid, a pattern, unit blocks or thin bars, or "none".
strokeStrokeVariantLine style for lines and area edges.
strokeWidthnumberLine width in px.
markerMarkerVariantMarker on each point of a line or area at rest. The active point always gets a ring.
glowbooleanA soft glow on this series at rest, not only when it's active.
radiusnumberBar corner radius for this series, in px. Overrides the chart's radius.
connectNullsbooleanJoin the line across missing values instead of leaving a gap. Off by default: a gap is honest.
type"bar" | "line" | "area"For composed charts: how this series is drawn, and which value axis it uses.
axis"left" | "right"Which value axis the series uses (composed charts). A right axis gets its own scale.
stackIdstringBars with the same stack id stack together (composed charts). Defaults to one stack per axis.
colorColorValueOne colour: any CSS colour or var(--chart-1), or { light, dark }.
themenevershadcn's per-theme colours, { light, dark }.
colorsneverGradient stops from base to value end, per theme. The last stop is the series' single colour.

Events

Every callback, and the payload types it receives. Indexes always point into the data you passed, even when a brush or grouping shows a subset.

Bar chart events
PropTypeDescription
onSelectedChange(selection: Selection<TDatum> | null) => voidCalled when a row is selected or cleared, by pointer, keyboard or the data table.
onSelectedSeriesChange(key: string | null) => voidCalled when the selected series changes (legend "select" action, or a click on a series).
onHiddenChange(hidden: string[]) => voidCalled when the legend hides or shows a series.
onActiveChange(index: number | null) => voidCalled as the hovered or focused row changes, or null when it leaves.
onReady(chart: EChartsType) => voidCalled once with the ECharts instance, e.g. to add event listeners.
export type Selection<TDatum> = {
  /** Index into the data you passed in, even when a brush shows a subset. */
  dataIndex: number;
  datum: TDatum;
  seriesKey: string | null;
  source: "pointer" | "keyboard" | "table";
  /** For a grouped segment (a donut's "Other"): the indexes of every row it stands for. `dataIndex` is the largest. */
  members?: number[] | undefined;
};

/** A range of rows shown in the plot, as indexes into your data (inclusive). */
export type RangeValue = { startIndex: number; endIndex: number };

export type TooltipContext<TDatum> = {
  datum: TDatum;
  /** Index into the data you passed in. */
  dataIndex: number;
  /** Visible series at this index. `value` is your raw value; `share` is its part of the row in 100% mode. */
  series: { key: string; label: ReactNode; value: number | null; share?: number | null | undefined; color: string; selected: boolean }[];
};

Parts and styling hooks

Each part carries a data-slot attribute. Theme rules inside the chart are one attribute selector deep, so a selector such as .my-chart [data-slot="chart-title"] overrides them. These are the slots in the files this chart installs; parts a chart doesn't use are simply absent from its markup.

brush-handlebrush-labelbrush-windowchartchart-backgroundchart-background-slotchart-bodychart-brushchart-descriptionchart-eyebrowchart-footerchart-headerchart-headingchart-legendchart-legend-itemchart-legend-labelchart-legend-swatchchart-legend-valuechart-livechart-mainchart-plotchart-readoutchart-statchart-stat-notechart-stat-valuechart-statechart-state-boxchart-state-messagechart-statschart-tablechart-table-scrollchart-takeawaychart-titlechart-tooltipodometerodometer-digitodometer-glyphsodometer-reelodometer-signpartial-notereadout-deltareadout-unitreadout-valuesr-onlytooltip-indicatortooltip-rowtooltip-sharetooltip-titletooltip-value

Props every chart shares

Header, states, legend, tooltip, formatting, motion, sound and controlled interaction state. They work the same on every Instrument chart and both engines. The guide explains colours, controlled state and accessibility.

Show all 34 shared props
Props every chart shares
PropTypeDescription
data*TDatum[]Your rows, as they are. Missing values (null, undefined, NaN) stay missing and are never drawn as zero.
localestringBCP 47 locale. Defaults to "en-US" so server and client render the same text.
timeZonestringIANA time zone for dates. Defaults to "UTC" so server and client agree.
configChartConfigPer-series label, colour, icon, format and style, keyed by series key. shadcn ChartConfig works as it is.
eyebrowReactNodeSmall label above the title.
titleReactNodeCard title. A string also names the chart for screen readers.
descriptionReactNodeA line under the title.
takeawayReactNode | ((summary: string) => ReactNode) | falseThe sentence under the title. Computed from the data by default; pass text, a function of the computed sentence, or false to hide it.
ariaDescriptionstringAccessible summary for screen readers. Computed from the data by default.
motionMotionPreference"full" (default), "subtle" or "off". Reduced-motion settings always win.
motionOptionsMotionOptionsWhen the entrance plays.
soundbooleanSound cues on hover, scrub and select. Plays only if the visitor has also turned sound on for the page.
densityDensity"comfortable" (default) or "compact" for dashboards.
legendLegendOptions | falseLegend position, alignment, shape and click action, or false to hide it.
tooltipTooltipOptions<TDatum> | falseTooltip position, indicator, cursor and custom content, or false to turn it off.
valueFormatter(value: number) => stringValues formatted for display. Series formatters in config win.
loadingbooleanShow the loading state. Over existing data, the old marks stay dimmed instead of vanishing.
errorstring | nullAn error message. Shows the error state in place of the plot.
emptyStateReactNodeShown instead of the plot when there is no data.
loadingStateReactNodeShown while loading is true and there is no data yet.
errorStateReactNodeShown when error is set.
selectednumber | nullControlled and uncontrolled interaction state. Point selection (selected) pins one row by its index in data. Series selection (selectedSeries) picks one series and dims the others. They are separate: picking a row doesn't pick a series.
defaultSelectednumber | nullInitial selected row when uncontrolled.
selectedSeriesstring | nullControlled series selection: this series stays lit and the others dim.
defaultSelectedSeriesstring | nullInitial series selection when uncontrolled.
hiddenstring[]Controlled hidden series keys.
defaultHiddenstring[]Series hidden at first when uncontrolled.
activeIndexnumber | nullControlled hover or focus index, for syncing several charts on one cursor.
palette"theme" | "shadcn"Series colours: "theme" (default) uses the theme's designed palette; "shadcn" uses your --chart-1..5. --bc-series-N always wins. You can also opt a whole section in with the class bc-use-shadcn-colors.
tablebooleanShow the "Show data table" disclosure. On by default.
statsChartStat[]A row of figures under the title, for dashboard cards and blocks.
classNamestringClasses on the card, for spacing and layout.
heightnumberHeight of the plot area in px. The frame sizes around it.