Skip to content

BarChart ​

A responsive SVG bar chart supporting single, grouped, stacked, and overlay series in either vertical (column) or horizontal orientation. Shares axis, tooltip, menu, and CSV export wiring with LineChart.

Examples ​

Single series ​

01020CasesDayMonTueWedThuFri
vue
<BarChart
  :data="[12, 19, 7, 24, 16]"
  :categories="['Mon', 'Tue', 'Wed', 'Thu', 'Fri']"
  :height="220"
  x-label="Day"
  y-label="Cases"
  tooltip-trigger="hover"
/>

Grouped series ​

PediatricAdult0102030Doses (thousands)Q1Q2Q3Q4
vue
<BarChart
  :series="[
    { data: [10, 14, 9, 18], legend: 'Pediatric', color: '#0057b7' },
    { data: [22, 28, 19, 30], legend: 'Adult', color: '#f4a261' },
  ]"
  :categories="['Q1', 'Q2', 'Q3', 'Q4']"
  :height="220"
  y-label="Doses (thousands)"
  tooltip-trigger="hover"
/>

Stacked series ​

PediatricAdult02040Doses (thousands)Q1Q2Q3Q4
vue
<BarChart
  :series="[
    { data: [10, 14, 9, 18], legend: 'Pediatric', color: '#0057b7' },
    { data: [22, 28, 19, 30], legend: 'Adult', color: '#f4a261' },
  ]"
  :categories="['Q1', 'Q2', 'Q3', 'Q4']"
  layout="stacked"
  :height="220"
  y-label="Doses (thousands)"
  tooltip-trigger="hover"
/>

Overlay series ​

layout="overlay" draws each series at full group width from the shared baseline, painting later series on top of earlier ones. Use it to compare a backdrop value (e.g. target, capacity, prior period) against a shorter foreground value sharing the same axis.

Series render in the order they appear in series, so put the taller backdrop first and the shorter foreground after. Use opacity, a distinct color, or a per-series blendMode (see below) if bars might overlap fully.

TargetActual010203040Doses (thousands)Q1Q2Q3Q4
vue
<BarChart
  :series="[
    { data: [40, 40, 40, 40], legend: 'Target', color: '#d4d4d8' },
    { data: [22, 35, 28, 38], legend: 'Actual', color: '#0057b7' },
  ]"
  :categories="['Q1', 'Q2', 'Q3', 'Q4']"
  layout="overlay"
  :height="220"
  y-label="Doses (thousands)"
  tooltip-trigger="hover"
/>

Blend mode ​

Set blendMode on a series to apply a CSS mix-blend-mode to every bar in that series. Useful with layout="overlay" (or just two overlapping grouped bars) to combine colors instead of one obscuring the other. Common picks: "multiply" (darkens light fills where bars overlap), "screen" (lightens dark fills), "difference" (high contrast).

Treatment ATreatment B0102030Cases avoidedQ1Q2Q3Q4
vue
<BarChart
  :series="[
    {
      data: [22, 35, 28, 38],
      legend: 'Treatment A',
      color: '#ef4444',
      blendMode: 'multiply',
    },
    {
      data: [30, 24, 36, 32],
      legend: 'Treatment B',
      color: '#3b82f6',
      blendMode: 'multiply',
    },
  ]"
  :categories="['Q1', 'Q2', 'Q3', 'Q4']"
  layout="overlay"
  :height="220"
  y-label="Cases avoided"
  tooltip-trigger="hover"
/>

Horizontal orientation ​

On touch devices the tooltip scrubs along the category axis: drag horizontally on a vertical (column) chart and vertically on a horizontal chart. A swipe in the other direction scrolls the page instead of being captured by the chart.

010203040CasesAlphaBetaGammaDeltaEpsilon
vue
<BarChart
  :data="[42, 31, 18, 12, 7]"
  :categories="['Alpha', 'Beta', 'Gamma', 'Delta', 'Epsilon']"
  orientation="horizontal"
  :height="220"
  x-label="Cases"
  tooltip-trigger="hover"
/>

Value labels on bars ​

Set bar-labels to write values directly on the bars (pair with :value-axis="false" for an axis-free look). Pass an options object to tune:

  • format — a NumberFormat or (value, ctx) => string (ctx has categoryIndex / seriesIndex); return "" to drop a label.
  • align — "center" (default), "start", or "end" within the segment.
  • color — auto-contrast per segment by default, or a fixed color.
  • Small/overlapping labels overflow past the segment edge and are spread apart (overlap: "shift") or dropped (overlap: "hide").

category-header / value-header title the two columns, and category-align="start" left-aligns the category labels.

Projected outbreak size by scenarioProportion of simulations in each categoryScenarioHighMediumLowAlphaBetaGammaDelta99%<1%<1%8810250302012079
vue
<BarChart
  :series="[
    { data: [99, 88, 50, 1], legend: 'High', color: '#3f1f5e' },
    { data: [0.5, 10, 30, 20], legend: 'Medium', color: '#8a5fb0' },
    { data: [0.5, 2, 20, 79], legend: 'Low', color: '#c9aee0' },
  ]"
  :categories="['Alpha', 'Beta', 'Gamma', 'Delta']"
  title="Projected outbreak size by scenario"
  :title-style="{ fontSize: 16 }"
  orientation="horizontal"
  layout="stacked"
  :value-axis="false"
  category-header="Scenario"
  value-header="Proportion of simulations in each category"
  category-align="start"
  :height="260"
  :chart-padding="{ left: 18, right: 64 }"
  :tick-label-style="{ fontSize: 11, color: 'currentColor' }"
  :bar-labels="{
    align: 'start',
    fontWeight: 600,
    format: (v, { categoryIndex }) =>
      v < 1
        ? '<1%'
        : categoryIndex === 0
          ? `${Math.round(v)}%`
          : String(Math.round(v)),
  }"
/>

Small segments ​

A single proportion bar with two tiny tail segments (98% / 1% / 1%). The dominant segment labels inside; the two slivers can't fit their text, so the labels overflow to the right and the overlap pass spreads them apart instead of letting them pile up.

RecoveredHospitalizedDeceasedOutcome98%1%1%
vue
<BarChart
  :series="[
    { data: [98], legend: 'Recovered', color: '#3f1f5e' },
    { data: [1], legend: 'Hospitalized', color: '#8a5fb0' },
    { data: [1], legend: 'Deceased', color: '#c9aee0' },
  ]"
  :categories="['Outcome']"
  orientation="horizontal"
  layout="stacked"
  :value-axis="false"
  :height="120"
  :chart-padding="{ left: 60, right: 40 }"
  :bar-labels="{ format: (v) => `${v}%` }"
/>

Custom value tick format ​

Use value-tick-format to format the value-axis labels. tooltip-value-format controls the tooltip values independently; if omitted, the tooltip uses value-tick-format.

0%20%40%60%CoverageABCD
vue
<BarChart
  :data="[0.12, 0.34, 0.56, 0.78]"
  :categories="['A', 'B', 'C', 'D']"
  :value-tick-format="(v) => `${(v * 100).toFixed(0)}%`"
  :tooltip-value-format="(v) => `${(v * 100).toFixed(1)}%`"
  :height="220"
  y-label="Coverage"
  tooltip-trigger="hover"
/>

Date categories ​

When every entry of categories parses as a date (YYYY-MM-DD string or Date instance), labels switch to date-aware formatting and the chart thins them to clean date boundaries (week / month / year depending on range). Bar positions stay ordinal — every category gets a bar — only the labels are thinned.

010203040% ED visitsFeb 2026Mar 2026
vue
<BarChart
  :data="[12, 18, 24, 35, 41, 38, 29, 21, 14, 9, 7, 4]"
  :categories="[
    '2026-01-05',
    '2026-01-12',
    '2026-01-19',
    '2026-01-26',
    '2026-02-02',
    '2026-02-09',
    '2026-02-16',
    '2026-02-23',
    '2026-03-02',
    '2026-03-09',
    '2026-03-16',
    '2026-03-23',
  ]"
  :height="220"
  y-label="% ED visits"
/>

Use date-format to override the auto-picked preset. Same values as LineChart's x-tick-format (e.g. "iso", "month-year", an Intl.DateTimeFormatOptions object, or a (ms, unit?) => string function).

Crowded categorical labels ​

Non-date categorical labels are also thinned when they don't all fit: the chart estimates label width from character count and font size, then strides through the categories so adjacent labels never overlap. Every bar still renders — only the tick labels are skipped.

0102030FrequencyR₀2.112.563.003.443.894.334.78
vue
<BarChart
  :data="[3, 5, 8, 12, 18, 24, 31, 27, 22, 16, 11, 7, 4, 2]"
  :categories="[
    '2.11',
    '2.33',
    '2.56',
    '2.78',
    '3.00',
    '3.22',
    '3.44',
    '3.67',
    '3.89',
    '4.11',
    '4.33',
    '4.56',
    '4.78',
    '5.00',
  ]"
  :height="220"
  x-label="R₀"
  y-label="Frequency"
/>

Logarithmic value axis ​

Set value-scale-type="log" to switch the value axis to base-10 log scaling. Non-positive values collapse to the axis floor, and valueMin <= 0 is ignored. With layout="stacked", individual segment sizes are no longer proportional to their raw values, but the cumulative top still represents the sum.

101001,000CasesWeekWk1Wk2Wk3Wk4Wk5
vue
<BarChart
  :data="[3, 24, 180, 1450, 9800]"
  :categories="['Wk1', 'Wk2', 'Wk3', 'Wk4', 'Wk5']"
  value-scale-type="log"
  :height="220"
  x-label="Week"
  y-label="Cases"
/>

Summary line overlay ​

Layer a curve (KDE, rolling mean, target) on top of the bars with summary-lines. Each line scales to its own value extent — independent of the bar axis — so a probability-density curve in [0, 0.02] and a histogram in [0, 500] can share the same chart.

KDE0102030FrequencyR₀2.112.563.003.443.894.334.78
vue
<BarChart
  :data="[3, 5, 8, 12, 18, 24, 31, 27, 22, 16, 11, 7, 4, 2]"
  :categories="[
    '2.11',
    '2.33',
    '2.56',
    '2.78',
    '3.00',
    '3.22',
    '3.44',
    '3.67',
    '3.89',
    '4.11',
    '4.33',
    '4.56',
    '4.78',
    '5.00',
  ]"
  :summary-lines="[
    {
      data: [
        0.05, 0.1, 0.2, 0.4, 0.75, 1.1, 1.32, 1.2, 0.92, 0.6, 0.32, 0.16, 0.07,
        0.03,
      ],
      color: '#ef4444',
      strokeWidth: 2,
      legend: 'KDE',
    },
  ]"
  :height="240"
  x-label="R₀"
  y-label="Frequency"
/>

Each line accepts color, strokeWidth, dashed, opacity, blendMode, dots, and dotRadius for styling — same vocabulary as LineChart series (both extend LineMarkStyle). Override the line's extent with valueMin / valueMax. Pass x: number[] (in category-index space — 0 is the first category center, fractional values land between) to plot at custom positions; useful when the summary samples are denser or sparser than the histogram bins.

Annotations ​

Pin callouts to specific bars with annotations. x is the category index (fractional values land between categories — e.g. x: 1.5 sits at the boundary between categories 1 and 2). y is on the value axis. See LineChart → Annotations for the full ChartAnnotation shape.

01020CasesDayMonTueWedThuFriPeak day
vue
<BarChart
  :data="[12, 19, 7, 24, 16]"
  :categories="['Mon', 'Tue', 'Wed', 'Thu', 'Fri']"
  :annotations="[{ x: 3, y: 24, offset: { x: 18, y: -22 }, text: 'Peak day' }]"
  :chart-padding="{ top: 32, right: 32 }"
  :height="240"
  x-label="Day"
  y-label="Cases"
/>

chart-padding reserves extra space outside the plot so the annotation label and pointer don't get clipped by the data area. Pass a number for uniform padding or an object with top / right / bottom / left.

Set pointer to a rule value ("ruleX", "ruleY", "ruleUp", "ruleDown", "ruleFromLeft", "ruleFromRight") to draw a line through the anchor instead of the default curved connector. The first two span the full plot; the latter four extend from one edge to the anchor. lineColor, lineWidth, and lineDash style the line.

01020CasesDayMonTueWedThuFriAvg 15.6Target hit
vue
<BarChart
  :data="[12, 19, 7, 24, 16]"
  :categories="['Mon', 'Tue', 'Wed', 'Thu', 'Fri']"
  :annotations="[
    {
      x: 0,
      y: 15.6,
      offset: { x: 6, y: -6 },
      text: 'Avg 15.6',
      pointer: 'ruleY',
      lineDash: '4 3',
    },
    {
      x: 3,
      y: 20,
      offset: { x: 8, y: 4 },
      text: 'Target hit',
      pointer: 'ruleFromLeft',
      lineColor: '#0a7',
    },
  ]"
  :chart-padding="{ top: 24, right: 24 }"
  :height="240"
  x-label="Day"
  y-label="Cases"
/>

Zoom ​

Set zoom to let readers click and drag on the plot to zoom. One gesture covers two kinds of zoom:

  • Along the categories: a roughly straight drag along the category axis (horizontal for vertical bars, vertical for horizontal bars) selects a run of whole categories. The value axis rescales to the visible bars.
  • Box: once the drag also covers about 24px on the value axis, the selection becomes a box and the value axis zooms to it too.

Pass zoom="x" to allow only the category zoom, whatever the drag's shape. While zoomed, a reset button appears at the top right; double-clicking the plot also resets, and Esc cancels a drag in progress.

Drag-to-zoom is a mouse gesture. On touch screens (and with a pen) a drag keeps scrubbing the tooltip and scrolling the page, so the chart stays at its full extent there.

050100150200CasesWeek036912151821242730333639
vue
<BarChart
  :data="weeklyCases"
  :height="220"
  x-label="Week"
  y-label="Cases"
  tooltip-trigger="hover"
  zoom
/>

Toggle series from the legend ​

Set legend-toggle to let readers show and hide marks by clicking their legend item. Every series and summary line with a legend label becomes toggleable; marks that share a label toggle together. A hidden mark leaves the plot and the tooltip, its legend item dims with a strikethrough, and the value axis rescales to what is still shown. CSV downloads always include every series.

The chart tracks the hidden labels itself. Bind v-model:hidden-series (an array of legend labels) to read the state, set an initial selection, or drive it from your own controls; hidden-series works without legend-toggle too.

UnvaccinatedVaccinated050100150Cases0-1718-4950-6465+
vue
<BarChart
  :series="[
    { data: unvaccinated, legend: 'Unvaccinated', color: '#0057b7' },
    { data: vaccinated, legend: 'Vaccinated', color: '#fb7e38' },
  ]"
  :categories="['0-17', '18-49', '50-64', '65+']"
  :height="220"
  y-label="Cases"
  tooltip-trigger="hover"
  legend-toggle
  v-model:hidden-series="hidden"
/>

Accessibility ​

The chart's <svg> is exposed as a single labeled image so screen readers announce one accessible name instead of wandering through the individual bars. When the chart has a title, the <svg> gets role="img" and an aria-label set to the title. The menu and download controls live outside the <svg>, so they stay reachable regardless.

Set ariaLabel to give screen readers a fuller summary than the visible title (it overrides title for the accessible name only):

vue
<BarChart
  :data="cases"
  :categories="regions"
  title="Cases by region"
  aria-label="Cases by region: North 120, South 80, West 45"
/>

Pass role to override the default "img" (e.g. role="figure", or a role of your own).

With legend-toggle, each toggleable legend item is a real <button> laid over the legend, outside the <svg>. The buttons sit in a group named "Show or hide series", are reachable with Tab, activate with Enter or Space, and report whether the mark is shown through aria-pressed. They stay in place while the chart is expanded.

API ​

Props ​

PropTypeRequiredDefault
widthnumberNo—
heightnumberNo—
titlestringNo—
titleStyleTitleStyleNo—
rolestringNo—
ariaLabelstringNo—
axisLabelStyleLabelStyleNo—
tickLabelStyleLabelStyleNo—
legendStyleLabelStyleNo—
legendTogglebooleanNo—
hiddenSeriesstring[]No—
xLabelstringNo—
yLabelstringNo—
debouncenumberNo—
menuboolean | stringNotrue
tooltipDataArrayLike&lt;unknown&gt;No—
tooltipTrigger"hover" | "click"No—
tooltipClamp"none" | "chart" | "window"No"window"
tooltipValueFormatNumberFormatNo—
csvstring | (() =&gt; string)No—
filenamestringNo—
downloadLinkboolean | stringNo—
downloadButtonboolean | stringNo—
fullscreenTargetstring | HTMLElementNo—
zoomboolean | "x"No—
annotationsreadonly ChartAnnotation[]No—
chartPaddingChartPaddingNo—
dataBarChartDataNo—
yBarChartDataNo—
seriesBarSeries[]No—
summaryLinesBarSummaryLine[]No—
categoriesreadonly (string | Date)[]No—
orientation"vertical" | "horizontal"No"vertical"
layout"grouped" | "stacked" | "overlay"No"grouped"
valueMinnumberNo0
valueScaleType"linear" | "log"No"linear"
valueTicksnumber | number[]No—
valueTickFormatNumberFormatNo—
categoryFormat(label: string, index: number) =&gt; stringNo—
categoryAlign"start" | "center" | "end"No—
dateFormatDateFormatNo—
barPaddingnumberNo0.2
groupGapnumberNo1
valueGridbooleanNotrue
valueAxisbooleanNotrue
barLabelsboolean | BarLabelStyleNo—
categoryHeaderstringNo—
valueHeaderstringNo—