BarChart
Vertical, horizontal, stacked and grouped bars with tooltip, legend and labels.
Default
One series, rounded bars, a tooltip with no heading and no cursor.
January - June 2024
| Month | Desktop |
|---|---|
| January | 186 |
| February | 305 |
| March | 237 |
| April | 73 |
| May | 209 |
| June | 214 |
Code
<BarChart data={rows} config={{ desktop: { label: "Desktop", color: "var(--chart-1)" } }}
x="month" radius={8}
categoryAxis={{ tickFormatter: (v) => String(v).slice(0, 3) }}
tooltip={{ hideLabel: true, cursor: false }} />Usage
import { BarChart } from "@elements/ui/chart-bar";
import type { ChartConfig } from "@elements/ui/chart";
const config: ChartConfig = {
desktop: { label: "Desktop", color: "var(--chart-1)" },
mobile: { label: "Mobile", color: "var(--chart-2)" },
};
<BarChart data={rows} {config} x="month" />
<BarChart data={rows} {config} x="month" stack="normal" legend />
<BarChart data={rows} {config} x="month" orientation="horizontal" labels />Examples
Horizontal
orientation=horizontal puts the categories down the side.
January - June 2024
| Month | Desktop |
|---|---|
| January | 186 |
| February | 305 |
| March | 237 |
| April | 73 |
| May | 209 |
| June | 214 |
Code
<BarChart data={rows} {config} x="month" series={["desktop"]}
orientation="horizontal" radius={5} margin={{ left: -20 }}
categoryAxis={{ tickFormatter: (v) => String(v).slice(0, 3) }}
tooltip={{ hideLabel: true }} />Multiple
Every series in the config, side by side in each band, with a dashed tooltip indicator.
January - June 2024
| Month | Desktop | Mobile |
|---|---|---|
| January | 186 | 80 |
| February | 305 | 200 |
| March | 237 | 120 |
| April | 73 | 190 |
| May | 209 | 130 |
| June | 214 | 140 |
Code
<BarChart data={rows} {config} x="month" radius={4}
categoryAxis={{ tickFormatter: (v) => String(v).slice(0, 3) }}
tooltip={{ indicator: "dashed" }} />Stacked and legend
stack=normal stacks the series; only the outer ends of a stack are rounded.
January - June 2024
- Desktop
- Mobile
| Month | Desktop | Mobile |
|---|---|---|
| January | 186 | 80 |
| February | 305 | 200 |
| March | 237 | 120 |
| April | 73 | 190 |
| May | 209 | 130 |
| June | 214 | 140 |
Code
<BarChart data={rows} {config} x="month" stack="normal" legend
categoryAxis={{ tickFormatter: (v) => String(v).slice(0, 3) }} />Label
A value label above each bar; the top margin leaves room for it.
January - June 2024
| Month | Desktop |
|---|---|
| January | 186 |
| February | 305 |
| March | 237 |
| April | 73 |
| May | 209 |
| June | 214 |
Code
<BarChart data={rows} {config} x="month" series={["desktop"]} radius={8}
margin={{ top: 20 }} labels={{ position: "top" }}
tooltip={{ hideLabel: true }} />Custom label
Two label lists: the month inside each bar and the value past its end.
January - June 2024
| Month | Desktop |
|---|---|
| January | 186 |
| February | 305 |
| March | 237 |
| April | 73 |
| May | 209 |
| June | 214 |
Code
<BarChart data={rows} {config} x="month" orientation="horizontal" radius={4}
categoryAxis={false} margin={{ right: 16 }}
labels={[
{ key: "month", position: "insideLeft", series: "desktop", offset: 8, color: "var(--bg)" },
{ position: "right", series: "desktop", offset: 8, color: "var(--ink)" },
]}
series={["desktop"]} tooltip={{ indicator: "line" }} />Mixed
Each row carries its own fill, so every bar takes its browser's color.
January - June 2024
| Browser | Visitors |
|---|---|
| Chrome | 275 |
| Safari | 200 |
| Firefox | 187 |
| Edge | 173 |
| Other | 90 |
Code
<!-- each row's fill picks its color: fill: "var(--color-chrome)" -->
<BarChart data={browsers} config={browserConfig} x="browser" orientation="horizontal"
radius={5} margin={{ left: 0 }}
categoryAxis={{ tickFormatter: (v) => browserConfig[String(v)].label }}
tooltip={{ hideLabel: true }} />Active
active marks one bar (two-way: hover moves it, leaving restores it); activeBar outlines it.
January - June 2024
| Browser | Visitors |
|---|---|
| Chrome | 275 |
| Safari | 200 |
| Firefox | 187 |
| Edge | 173 |
| Other | 90 |
Code
<BarChart data={browsers} config={browserConfig} x="browser" radius={8}
active={2} activeBar
categoryAxis={{ tickFormatter: (v) => browserConfig[String(v)].label }}
tooltip={{ hideLabel: true, cursor: false }} />Negative
Negative values hang below the zero line; each row's fill picks its color by sign.
January - June 2024
| Month | Visitors |
|---|---|
| January | 186 |
| February | 205 |
| March | -207 |
| April | 173 |
| May | -209 |
| June | 214 |
Code
<!-- fill per row: rows.map((r) => ({ ...r, fill: r.visitors > 0 ? "var(--chart-1)" : "var(--chart-2)" })) -->
<BarChart data={rows} config={{ visitors: { label: "Visitors" } }} x="month"
categoryAxis={false}
labels={{ key: "month", position: "top" }}
tooltip={{ hideLabel: true, hideIndicator: true, cursor: false }} />Interactive
The header switches the series. 91 bars: ticks skip so their labels never overlap.
Showing total visitors for the last 3 months
| Date | Desktop |
|---|---|
| 2024-04-01 | 286 |
| 2024-04-02 | 136 |
| 2024-04-03 | 267 |
| 2024-04-04 | 121 |
| 2024-04-05 | 326 |
| 2024-04-06 | 204 |
| 2024-04-07 | 353 |
| 2024-04-08 | 289 |
| 2024-04-09 | 359 |
| 2024-04-10 | 314 |
| 2024-04-11 | 160 |
| 2024-04-12 | 466 |
| 2024-04-13 | 138 |
| 2024-04-14 | 472 |
| 2024-04-15 | 258 |
| 2024-04-16 | 359 |
| 2024-04-17 | 158 |
| 2024-04-18 | 268 |
| 2024-04-19 | 309 |
| 2024-04-20 | 151 |
| 2024-04-21 | 231 |
| 2024-04-22 | 122 |
| 2024-04-23 | 185 |
| 2024-04-24 | 278 |
| 2024-04-25 | 331 |
| 2024-04-26 | 394 |
| 2024-04-27 | 144 |
| 2024-04-28 | 270 |
| 2024-04-29 | 109 |
| 2024-04-30 | 362 |
| 2024-05-01 | 472 |
| 2024-05-02 | 265 |
| 2024-05-03 | 266 |
| 2024-05-04 | 270 |
| 2024-05-05 | 196 |
| 2024-05-06 | 387 |
| 2024-05-07 | 198 |
| 2024-05-08 | 433 |
| 2024-05-09 | 461 |
| 2024-05-10 | 389 |
| 2024-05-11 | 312 |
| 2024-05-12 | 219 |
| 2024-05-13 | 176 |
| 2024-05-14 | 364 |
| 2024-05-15 | 197 |
| 2024-05-16 | 93 |
| 2024-05-17 | 324 |
| 2024-05-18 | 126 |
| 2024-05-19 | 273 |
| 2024-05-20 | 325 |
| 2024-05-21 | 140 |
| 2024-05-22 | 449 |
| 2024-05-23 | 100 |
| 2024-05-24 | 373 |
| 2024-05-25 | 354 |
| 2024-05-26 | 182 |
| 2024-05-27 | 157 |
| 2024-05-28 | 123 |
| 2024-05-29 | 305 |
| 2024-05-30 | 427 |
| 2024-05-31 | 288 |
| 2024-06-01 | 202 |
| 2024-06-02 | 116 |
| 2024-06-03 | 182 |
| 2024-06-04 | 406 |
| 2024-06-05 | 156 |
| 2024-06-06 | 340 |
| 2024-06-07 | 251 |
| 2024-06-08 | 281 |
| 2024-06-09 | 93 |
| 2024-06-10 | 271 |
| 2024-06-11 | 225 |
| 2024-06-12 | 377 |
| 2024-06-13 | 197 |
| 2024-06-14 | 121 |
| 2024-06-15 | 123 |
| 2024-06-16 | 165 |
| 2024-06-17 | 102 |
| 2024-06-18 | 266 |
| 2024-06-19 | 306 |
| 2024-06-20 | 301 |
| 2024-06-21 | 138 |
| 2024-06-22 | 355 |
| 2024-06-23 | 453 |
| 2024-06-24 | 273 |
| 2024-06-25 | 444 |
| 2024-06-26 | 289 |
| 2024-06-27 | 405 |
| 2024-06-28 | 308 |
| 2024-06-29 | 344 |
| 2024-06-30 | 266 |
Code
<button e:for={key of ["desktop", "mobile"]} data-active={state.series === key}
onclick={() => state.series = key}>...</button>
<BarChart data={daily} config={dailyConfig} x="date" series={[state.series]}
class="chart-demo-wide" margin={{ left: 12, right: 12 }}
categoryAxis={{ tickFormatter: shortDate, minTickGap: 32 }}
tooltip={{ nameKey: "views", labelFormatter: (_, row) => longDate(row.date) }} />Expanded stack
stack=expand stacks each band to 100%; the value axis reads the share.
- Desktop
- Mobile
- Other
| Month | Desktop | Mobile | Other |
|---|---|---|---|
| January | 186 | 80 | 45 |
| February | 305 | 200 | 100 |
| March | 237 | 120 | 150 |
| April | 73 | 190 | 50 |
| May | 209 | 130 | 100 |
| June | 214 | 140 | 160 |
Code
<BarChart data={rows} config={{ desktop, mobile, other }} x="month"
stack="expand" legend valueAxis={{ tickFormatter: (v) => `${Number(v) * 100}%` }} />Legend toggles
legend toggle makes each legend item a button; hidden is two-way.
| Month | Desktop | Mobile | Other |
|---|---|---|---|
| January | 186 | 80 | 45 |
| February | 305 | 200 | 100 |
| March | 237 | 120 | 150 |
| April | 73 | 190 | 50 |
| May | 209 | 130 | 100 |
| June | 214 | 140 | 160 |
Code
<BarChart data={rows} {config} x="month" legend={{ toggle: true }}
hidden={state.hidden} />Axes and grid
A value axis with its own tick count, tick marks and axis lines, and a full grid.
| Month | Desktop | Mobile |
|---|---|---|
| January | 186 | 80 |
| February | 305 | 200 |
| March | 237 | 120 |
| April | 73 | 190 |
| May | 209 | 130 |
| June | 214 | 140 |
Code
<BarChart data={rows} {config} x="month"
valueAxis={{ tickCount: 4, tickFormatter: (v) => `${v}` , axisLine: true }}
categoryAxis={{ tickLine: true, axisLine: true, tickFormatter: (v) => String(v).slice(0, 3) }}
grid={{ horizontal: true, vertical: true }} />Background and tiny values
background draws a track behind each bar; barSize fixes the bar width; minPointSize keeps a near-zero bar visible; animate=false draws at once.
| Day | Goal |
|---|---|
| Mon | 80 |
| Tue | 0.2 |
| Wed | 64 |
| Thu | 92 |
| Fri | 40 |
Code
<BarChart data={rows} config={{ goal: { label: "Goal" } }} x="day" radius={8} barSize={28}
background minPointSize={4} animate={false} categoryAxis={false} grid={false} />Sign stack, thin bars and margins
stack=sign stacks positives up from zero and negatives down; maxBarSize caps the bar width; margin and tickMargin move the plot and its labels.
- Income
- Refunds
- Fees
| Month | Income | Refunds | Fees |
|---|---|---|---|
| January | 186 | -40 | -22 |
| February | 305 | -62 | -30 |
| March | 237 | -18 | -26 |
| April | 173 | -90 | -21 |
| May | 209 | -35 | -28 |
| June | 214 | -51 | -24 |
Code
<!-- stack="sign": positives stack up from zero, negatives down -->
<BarChart data={rows} {config} x="month" stack="sign" maxBarSize={16}
margin={{ top: 24, left: 24 }} categoryAxis={{ tickMargin: 20 }} />Controlled and keyboard
Tab to the chart; arrows move the active bar, Home and End jump, Escape hides the tooltip.
| Month | Desktop | Mobile |
|---|---|---|
| January | 186 | 80 |
| February | 305 | 200 |
| March | 237 | 120 |
| April | 73 | 190 |
| May | 209 | 130 |
| June | 214 | 140 |
Code
<BarChart data={rows} {config} x="month" active={state.active}
activeBar onActiveChange={() => state.changes++}
onItemClick={(i) => state.clicked = i} />Two axes each way
A right value axis scales mobile alone; a top category axis shows desktop's share of each month.
- Desktop
- Mobile
| Month | Desktop | Mobile |
|---|---|---|
| January | 186 | 80 |
| February | 305 | 200 |
| March | 237 | 120 |
| April | 73 | 190 |
| May | 209 | 130 |
| June | 214 | 140 |
Code
<!-- mobile on its own axis on the right; a second category axis on top shows the share of desktop -->
<BarChart data={rows} {config} x="month"
categoryAxis={[{ tickFormatter: shortMonth }, { orientation: "top", key: "share", tickFormatter: (v) => `${v}%` }]}
valueAxis={[{}, { orientation: "right", series: ["mobile"] }]} />Silhouette stack
stack silhouette centers each month's stack on zero.
| Month | Desktop | Mobile |
|---|---|---|
| January | 186 | 80 |
| February | 305 | 200 |
| March | 237 | 120 |
| April | 73 | 190 |
| May | 209 | 130 |
| June | 214 | 140 |
Code
<!-- each month's stack centered on zero -->
<BarChart data={rows} {config} x="month" stack="silhouette" valueAxis />Tooltip on click
tooltip trigger click: a click on a month shows its tooltip and it stays; a click elsewhere hides it.
| Month | Desktop | Mobile |
|---|---|---|
| January | 186 | 80 |
| February | 305 | 200 |
| March | 237 | 120 |
| April | 73 | 190 |
| May | 209 | 130 |
| June | 214 | 140 |
Code
<!-- a click shows the tooltip and keeps it; hover alone does nothing -->
<BarChart data={rows} {config} x="month" tooltip={{ trigger: "click" }} />A series left out of the tooltip
tooltipType none on a config entry draws the series and keeps it in the legend, but leaves it out of the tooltip and its announcement.
- Desktop
- Mobile
| Month | Desktop | Mobile |
|---|---|---|
| January | 186 | 80 |
| February | 305 | 200 |
| March | 237 | 120 |
| April | 73 | 190 |
| May | 209 | 130 |
| June | 214 | 140 |
Code
<!-- mobile is drawn and in the legend, but not in the tooltip -->
<BarChart data={rows} x="month" legend config={{
desktop: { label: "Desktop", color: "var(--chart-1)" },
mobile: { label: "Mobile", color: "var(--chart-2)", tooltipType: "none" },
}} />Reverse stack order
reverseStackOrder stacks the last series first, so the first sits on top; the legend and tooltip keep series order.
- Desktop
- Mobile
| Month | Desktop | Mobile |
|---|---|---|
| January | 186 | 80 |
| February | 305 | 200 |
| March | 237 | 120 |
| April | 73 | 190 |
| May | 209 | 130 |
| June | 214 | 140 |
Code
<!-- desktop stacks on top; legend and tooltip keep series order -->
<BarChart data={rows} {config} x="month" stack="normal" reverseStackOrder legend />Animation timing
animation sets when the marks start moving, how long they take and their easing: a CSS easing name, a cubic-bezier, spring, or a function of progress.
| Month | Desktop | Mobile |
|---|---|---|
| January | 186 | 80 |
| February | 305 | 200 |
| March | 237 | 120 |
| April | 73 | 190 |
| May | 209 | 130 |
| June | 214 | 140 |
Code
<!-- wait 150ms, then take 900ms to enter or move to new data -->
<BarChart data={rows} {config} x="month"
animation={{ begin: 150, duration: 900, easing: "linear" }} />Legend and tooltip types
legendType on a config entry sets that series' legend icon, and none leaves a drawn series out of the legend; tooltipType none leaves it out of the tooltip.
- Desktop
- Mobile
| Month | Desktop | Mobile | Other |
|---|---|---|---|
| January | 186 | 80 | 45 |
| February | 305 | 200 | 100 |
| March | 237 | 120 | 150 |
| April | 73 | 190 | 50 |
| May | 209 | 130 | 100 |
| June | 214 | 140 | 160 |
Code
<!-- desktop's legend icon is a line; mobile is left out of the tooltip, other out of the legend -->
<BarChart data={rows} x="month" legend config={{
desktop: { label: "Desktop", color: "var(--chart-1)", legendType: "line" },
mobile: { label: "Mobile", color: "var(--chart-2)", tooltipType: "none" },
other: { label: "Other", color: "var(--chart-3)", legendType: "none" },
}} />Data table
Every chart carries its data as a table for screen readers; showTable shows it.
| Month | Desktop | Mobile |
|---|---|---|
| January | 186 | 80 |
| February | 305 | 200 |
| March | 237 | 120 |
| April | 73 | 190 |
| May | 209 | 130 |
| June | 214 | 140 |
Code
<BarChart data={rows} {config} x="month" showTable={state.table} />Axis options
Each chart sets one option of the value or category axis: reversed, angle, label, unit, ticks, allowDecimals, interval, mirror, tickSize, tick, allowDataOverflow, niceTicks, size, hide, includeHidden and allowDuplicatedCategory.
| Month | Desktop |
|---|---|
| January | 186 |
| February | 305 |
| March | 237 |
| April | 73 |
| May | 209 |
| June | 214 |
| Month | Desktop |
|---|---|
| January | 186 |
| February | 305 |
| March | 237 |
| April | 73 |
| May | 209 |
| June | 214 |
| Month | Desktop |
|---|---|
| January | 186 |
| February | 305 |
| March | 237 |
| April | 73 |
| May | 209 |
| June | 214 |
| Month | Desktop |
|---|---|
| January | 186 |
| February | 305 |
| March | 237 |
| April | 73 |
| May | 209 |
| June | 214 |
| Month | Desktop |
|---|---|
| January | 186 |
| February | 305 |
| March | 237 |
| April | 73 |
| May | 209 |
| June | 214 |
| Month | Desktop |
|---|---|
| January | 186 |
| February | 305 |
| March | 237 |
| April | 73 |
| May | 209 |
| June | 214 |
| Month | Desktop |
|---|---|
| January | 1 |
| February | 3 |
| March | 2 |
| Month | Desktop |
|---|---|
| January | 186 |
| February | 305 |
| March | 237 |
| April | 73 |
| May | 209 |
| June | 214 |
| Month | Desktop |
|---|---|
| January | 186 |
| February | 305 |
| March | 237 |
| April | 73 |
| May | 209 |
| June | 214 |
| Month | Desktop |
|---|---|
| January | 186 |
| February | 305 |
| March | 237 |
| April | 73 |
| May | 209 |
| June | 214 |
| Month | Desktop |
|---|---|
| January | 186 |
| February | 305 |
| March | 237 |
| April | 73 |
| May | 209 |
| June | 214 |
| Month | Desktop |
|---|---|
| January | 186 |
| February | 305 |
| March | 237 |
| April | 73 |
| May | 209 |
| June | 214 |
| Month | Desktop |
|---|---|
| January | 186 |
| February | 305 |
| March | 237 |
| April | 73 |
| May | 209 |
| June | 214 |
| Month | Desktop |
|---|---|
| January | 186 |
| February | 305 |
| March | 237 |
| April | 73 |
| May | 209 |
| June | 214 |
| Month | Desktop |
|---|---|
| January | 186 |
| February | 305 |
| March | 237 |
| April | 73 |
| May | 209 |
| June | 214 |
| Month | Desktop | Mobile |
|---|---|---|
| January | 186 | 80 |
| February | 305 | 200 |
| March | 237 | 120 |
| April | 73 | 190 |
| May | 209 | 130 |
| June | 214 | 140 |
| Month | Desktop |
|---|---|
| January | 186 |
| January | 100 |
| February | 305 |
Code
<!-- category and value axis options, one per chart -->
<BarChart data={rows} {config} x="month" series={["desktop"]} valueAxis={{ reversed: true }} />
<BarChart ... categoryAxis={{ reversed: true }} />
<BarChart ... categoryAxis={{ angle: -45, size: 60, tickFormatter: shortMonth }} />
<BarChart ... valueAxis={{ label: "Visitors" }} categoryAxis={{ label: "Month" }} />
<BarChart ... valueAxis={{ unit: "k" }} />
<BarChart ... valueAxis={{ ticks: [0, 100, 250] }} />
<BarChart data={counts} ... valueAxis={{ allowDecimals: false, tickCount: 7 }} />
<BarChart ... categoryAxis={{ interval: 1 }} />
<BarChart ... valueAxis={{ mirror: true }} />
<BarChart ... categoryAxis={{ tickLine: true, tickSize: 12 }} />
<BarChart ... categoryAxis={{ tick: false, axisLine: true }} />
<BarChart ... valueAxis={{ domain: [0, 200], allowDataOverflow: true }} />
<BarChart ... valueAxis={{ domain: [0, "dataMax"], niceTicks: "none" }} />
<BarChart ... valueAxis={{ size: 80 }} categoryAxis={{ size: 50 }} />
<BarChart ... valueAxis={{ hide: true, domain: [0, 600] }} />
<BarChart ... series={["desktop", "mobile"]} legend={{ toggle: true }} valueAxis={{ includeHidden: true }} />
<BarChart data={duplicates} ... categoryAxis={{ allowDuplicatedCategory: false }} />Grid options
Dashed lines, a background fill, stripes between the lines, lines at given values or pixels, and lines kept to the axis ticks.
| Month | Desktop |
|---|---|
| January | 186 |
| February | 305 |
| March | 237 |
| April | 73 |
| May | 209 |
| June | 214 |
| Month | Desktop |
|---|---|
| January | 186 |
| February | 305 |
| March | 237 |
| April | 73 |
| May | 209 |
| June | 214 |
| Month | Desktop |
|---|---|
| January | 186 |
| February | 305 |
| March | 237 |
| April | 73 |
| May | 209 |
| June | 214 |
| Month | Desktop |
|---|---|
| January | 186 |
| February | 305 |
| March | 237 |
| April | 73 |
| May | 209 |
| June | 214 |
| Month | Desktop |
|---|---|
| January | 186 |
| February | 305 |
| March | 237 |
| April | 73 |
| May | 209 |
| June | 214 |
| Month | Desktop |
|---|---|
| January | 186 |
| February | 305 |
| March | 237 |
| April | 73 |
| May | 209 |
| June | 214 |
Code
<!-- grid options -->
<BarChart ... grid={{ strokeDasharray: "3 3" }} />
<BarChart ... grid={{ fill: "var(--bg-muted)", fillOpacity: 0.5 }} />
<BarChart ... grid={{ horizontalFill: ["var(--bg-muted)", "transparent"],
vertical: true, verticalFill: ["var(--bg-muted)", "transparent"] }} />
<BarChart ... grid={{ horizontalValues: [100, 200] }} />
<BarChart ... grid={{ horizontalPoints: [40, 80], vertical: true, verticalPoints: [100] }} />
<BarChart ... valueAxis={{ ticks: [0, 150, 300] }} grid={{ syncWithTicks: true }} />Moving to new data
animation matchBy pairs old and new bars by a key, so each month slides to its new place and the one that leaves fades; interpolate draws every frame with a function of your own, here one that grows each bar from its base.
| Month | Desktop |
|---|---|
| January | 186 |
| February | 305 |
| March | 237 |
| April | 73 |
| May | 209 |
| June | 214 |
| Month | Desktop |
|---|---|
| January | 186 |
| February | 305 |
| March | 237 |
| April | 73 |
| May | 209 |
| June | 214 |
Code
<!-- each month keeps its bar: the window slides -->
<BarChart data={rows} {config} x="month" animation={{ matchBy: matchByDataKey("month") }} />
<!-- (items, t) => the bars to draw: each grows from its base -->
<BarChart data={rows} {config} x="month" animation={{ interpolate: growBars }} />