Bar Chart

A composable bar chart with spring animations, stacked bars, horizontal orientation, and grouped series support

Installation

pnpm dlx shadcn@latest add @bklit/bar-chart

Usage

The Bar Chart uses the same composable API as the Line and Area charts. Build charts by combining components:

import {
  BarChart,
  Bar,
  BarXAxis,
  Grid,
  ChartTooltip,
} from "@bklitui/ui/charts";

const data = [
  { month: "Jan", revenue: 12000, profit: 4500 },
  { month: "Feb", revenue: 15500, profit: 5200 },
  // ... more data
];

export default function RevenueChart() {
  return (
    <BarChart data={data} xDataKey="month">
      <Grid horizontal />
      <Bar dataKey="revenue" fill="var(--chart-line-primary)" lineCap="round" />
      <Bar
        dataKey="profit"
        fill="var(--chart-line-secondary)"
        lineCap="round"
      />
      <BarXAxis />
      <ChartTooltip />
    </BarChart>
  );
}

See the charts gallery for stacked bars, horizontal layouts, gradients, patterns, the Shape square-column variant, and more.

Components

BarChart

The root component that provides context to all children.

PropTypeDefaultDescription
dataRecord<string, unknown>[]requiredArray of data points
xDataKeystring"name"Key in data for categorical axis values
marginPartial<Margin>{ top: 40, right: 40, bottom: 40, left: 40 }Chart margins
animationDurationnumber1100Animation duration in ms
aspectRatiostring"2 / 1"CSS aspect ratio
barGapnumber0.2Gap between bar groups (0-1 fraction of band width)
barWidthnumber-Fixed bar width in pixels (auto-sizes if not set)
orientation"vertical" | "horizontal""vertical"Bar chart orientation
stackedbooleanfalseStack bars instead of grouping them
stackGapnumber0Gap between stacked bar segments in pixels
squareSnap{ squareGap: number; groupGap?: number; fit?: boolean }Enables square-column tooltip snapping when using <BarSquares>
classNamestring""Additional CSS class

Bar

Renders a bar for each data point with configurable styling and animations.

PropTypeDefaultDescription
dataKeystringrequiredKey in data for values
yAxisIdstring | number"left"Y-scale group for vertical biaxial charts (pair with YAxis)
fillstringvar(--chart-line-primary)Bar fill color (can be gradient/pattern url)
strokestring-Tooltip dot color. Use when fill is a gradient/pattern
lineCap"round" | "butt" | number"round"Bar end cap style or custom radius
animatebooleantrueEnable animation
animationType"grow" | "fade""grow"Animation style
fadedOpacitynumber0.3Opacity when another bar is hovered
staggerDelaynumberautoDelay between bars (auto-calculated based on bar count)
stackGapnumber0Gap between stacked bars in pixels
perspectivebooleanfalseMake this a 3D depth bar: trims the front-face top to meet the lid and forces a flat top. Pass true when pairing with BarDepthBack/BarDepthFront (see 3D Depth)
minBarHeightnumber0Minimum rendered height in px (non-stacked, vertical) — floors short/zero bars so they stay visible. Pair with <BarDepthProvider minBarHeight>

BarSquares

Renders a Shape bar as a vertical stack of discrete squares instead of a continuous <Bar>. Use one <BarSquares> per series (grouped, vertical charts only). Squares cascade upward on enter; supports solid fills, bar-spanning gradients, and pattern presets.

PropTypeDefaultDescription
dataKeystringrequiredKey in data for values
yAxisIdstring | numberseries defaultY-scale group for biaxial charts
fillstringvar(--chart-line-primary)Solid color or pattern url(#…)
strokestringTooltip ring/dot color when fill is gradient/pattern
squareGapnumber3Gap between stacked squares in pixels
squareRadiusnumber0.25Corner radius as a fraction of square size (0–0.5)
squareFitbooleanfalseRedistribute gap so columns fit bar height exactly
useGradientbooleanfalseApply a bar-spanning gradient from gradientStops
gradientStops{ offset: number; color: string }[]Gradient stops (0–100) when useGradient is true
patternPresetpattern preset idPattern preset when fill is a pattern url
groupGapnumber4Gap between grouped series columns in pixels
fadedOpacitynumber0.3Opacity when another bar is hovered
animatebooleantrueEnable enter animation
staggerDelaynumberautoOverride per-column stagger delay

Pass squareSnap on <BarChart> with the same squareGap, groupGap, and squareFit so tooltip ring indicators align with square columns:

<BarChart
  data={data}
  xDataKey="month"
  squareSnap={{ squareGap: 3, groupGap: 4 }}
>
  <BarSquares dataKey="desktop" fill="url(#desktop-pattern)" stroke="var(--chart-5)" squareGap={3} groupGap={4} />
  <BarSquares
    dataKey="mobile"
    fill="var(--chart-1)"
    useGradient
    gradientStops={[
      { offset: 0, color: "var(--chart-1)" },
      { offset: 25, color: "var(--chart-2)" },
      { offset: 50, color: "var(--chart-3)" },
      { offset: 75, color: "var(--chart-4)" },
      { offset: 100, color: "var(--chart-5)" },
    ]}
    stroke="var(--chart-5)"
    squareGap={3}
    groupGap={4}
  />
  <BarXAxis />
  <ChartTooltip showCrosshair={false} dotVariant="ring" dotScale={1.05} />
</BarChart>

See the Shape & Gradient and Shape Squircle Ring examples on the bar chart gallery.

BarColumnTrack

Optional underlay for Shape bars: patterned or solid fill in the empty space above each square column (not behind the bars). Place before <BarSquares>.

PropTypeDefaultDescription
fillstringvar(--chart-grid)Solid color or pattern url(#…)
opacitynumber0.3Track opacity
squareGapnumber3Match <BarSquares squareGap>
squareRadiusnumber0.25Match <BarSquares squareRadius>
squareFitbooleanfalseMatch <BarSquares squareFit>
groupGapnumber4Match <BarSquares groupGap>

Tracks animate from full column height on enter and shrink in sync as squares stack upward.

BarXAxis

Displays categorical labels along the x-axis (for vertical bar charts).

PropTypeDefaultDescription
tickerHalfWidthnumber50Width of ticker for fade calculation
showAllLabelsbooleanfalseShow all labels (may crowd)
maxLabelsnumber12Maximum labels to show

BarYAxis

Displays categorical labels along the y-axis (for horizontal bar charts).

PropTypeDefaultDescription
showAllLabelsbooleantrueShow all labels
maxLabelsnumber20Maximum labels to show

Grid

The Grid component now supports fadeVertical for vertical grid lines.

PropTypeDefaultDescription
horizontalbooleantrueShow horizontal grid lines
verticalbooleanfalseShow vertical grid lines
fadeHorizontalbooleantrueFade horizontal lines at left/right edges
fadeVerticalbooleanfalseFade vertical lines at top/bottom edges

Background

Pattern fill for the plot area when you omit Grid. See the Background utility and Pattern Background examples on the bar chart gallery.

BarDepthBack

Renders the 3D side + top faces. Place before <Bar> so the bar's front face occludes depth that would extend into the next column.

PropTypeDefaultDescription
dataKeystringrequiredKey in data for the bar value (match the sibling <Bar dataKey>)
colorstringvar(--chart-line-primary)Solid color for the side + top faces
colorAccessor(datum, index) => string-Per-bar color override; takes precedence over color

BarDepthFront

Renders the glossy glass sheen over the bar's fill. Place after <Bar>.

PropTypeDefaultDescription
dataKeystringrequiredKey in data for the bar value (match the sibling <Bar dataKey>)

BarPulse

Optional looping vertical sweep over a single active bar (e.g. a live value). Place after <BarDepthFront>.

PropTypeDefaultDescription
dataKeystringrequiredKey in data for the bar value
activeIndexnumber-Index (in the data array) of the bar to pulse
pulsePausedbooleanfalseFreeze the sweep while keeping the 3D/glass treatment

BarDepthProvider

Optional wrapper supplying shared depth config to every layer beneath it. Wrap it around <BarChart>, not inside it. Use it to split stacked bars (segmentsAccessor) and/or tune the baseline contact shadow (groundShadow).

PropTypeDefaultDescription
segmentsAccessor(datum) => { value: number; color: string }[]-Stacked segments (bottom→top) for each datum
groundShadownumber0.26Opacity (0–1) of the dark "contact shadow" that grounds each bar at the baseline. Set 0 to remove it (e.g. on bright fills).
minBarHeightnumber0Floors short/zero bars to a min px height so they stay visible. Pass the same value to <Bar minBarHeight>.

3D Depth & Glass Surfaces

Give bars a head-on 3D perspective and a glass-block sheen by layering BarDepthBack (side + top faces, before Bar) and BarDepthFront (front glass, after Bar). The layers read geometry from the chart context, so the only required prop is the matching dataKey.

Add perspective to the Bar and you're done — it shrinks the front face's top so the lid sits flush on the bar (instead of floating above it) and forces a flat top so the lid meets it with no gap. Always pass it when using the depth layers.

import {
  BarChart,
  Bar,
  BarDepthBack,
  BarDepthFront,
  BarXAxis,
  Grid,
  ChartTooltip,
} from "@bklitui/ui/charts";

<BarChart data={data} xDataKey="month">
  <Grid horizontal />
  <BarDepthBack dataKey="revenue" color="var(--chart-1)" />
  <Bar dataKey="revenue" fill="var(--chart-1)" perspective />
  <BarDepthFront dataKey="revenue" />
  <BarXAxis />
  <ChartTooltip />
</BarChart>;

Custom indicator (optional)

The 3D Depth gallery example pairs depth bars with a rising line indicator. Disable the default crosshair and dots on ChartTooltip, then render your own overlay via useChart and a portal — see Custom Indicator.

Color bars individually with colorAccessor:

<BarDepthBack
  dataKey="revenue"
  colorAccessor={(d) =>
    (d.revenue as number) >= 15000 ? "var(--chart-1)" : "var(--chart-3)"
  }
/>

Highlight a live / in-progress bar with BarPulse (render it after BarDepthFront):

<BarPulse dataKey="revenue" activeIndex={data.length - 1} />

Stacked bars (multiple data sources)

To give a stacked bar 3D depth, wrap the chart in BarDepthProvider with a segmentsAccessor returning one { value, color } per data source (bottom→top). The side face splits into a matching parallelogram per segment, and the lid takes the topmost segment's color. The depth's height is the sum of the segments, so you don't need a separate total column — just stack a <Bar> per source (each with perspective) in the same order:

<BarDepthProvider
  segmentsAccessor={(d) => [
    { value: d.desktop as number, color: "var(--chart-1)" },
    { value: d.mobile as number, color: "var(--chart-3)" },
  ]}
>
  <BarChart data={data} xDataKey="month" stacked>
    <BarDepthBack dataKey="desktop" />
    <Bar dataKey="desktop" fill="var(--chart-1)" perspective />
    <Bar dataKey="mobile" fill="var(--chart-3)" perspective />
    <BarDepthFront dataKey="desktop" />
    <BarXAxis />
    <ChartTooltip showCrosshair={false} showDots={false} />
  </BarChart>
</BarDepthProvider>;

Each bar gets a subtle dark "contact shadow" at the baseline so it reads as sitting on the axis. Tune or remove it with groundShadow on the provider (it reads strongest over bright fills):

<BarDepthProvider groundShadow={0.12}>
  {/* ...BarChart with depth layers... */}
</BarDepthProvider>

To keep zero or very short bars visible as a tiny bar instead of vanishing, set minBarHeight on both the <Bar> (floors the front face) and <BarDepthProvider> (floors the 3D surfaces) — they must match:

<BarDepthProvider minBarHeight={6}>
  <BarChart data={data} xDataKey="month">
    <BarDepthBack dataKey="value" color="var(--chart-1)" />
    <Bar dataKey="value" fill="var(--chart-1)" perspective minBarHeight={6} />
    <BarDepthFront dataKey="value" />
  </BarChart>
</BarDepthProvider>

Negative values are supported (bars grow downward from the baseline) as long as the chart's value scale includes negatives. See the charts gallery for a live 3D depth example.

Loading state

BarChart accepts a status prop. While status="loading", it replaces the bars with a shimmer skeleton: placeholder bars with a soft diagonal shimmer sweeping across them on a loop. On status="ready" it switches to the real bars. No chart data is required during loading. Bar heights come from a deterministic seed (SSR-safe, no Math.random()), and it respects prefers-reduced-motion by rendering a calm, static skeleton.

Preview

const [status, setStatus] = useState<"loading" | "ready">("loading");<BarChart data={data} status={status} xDataKey="month">  <Grid horizontal />  <Bar dataKey="revenue" />  <BarXAxis /></BarChart>

Toggle Loading / Ready in the preview to swap the shimmer skeleton for the real bars.

import { BarChart, Bar, BarXAxis } from "@bklitui/ui/charts";

function RevenueChart({ data, isLoading }) {
  return (
    <BarChart data={data} xDataKey="month" status={isLoading ? "loading" : "ready"}>
      <Bar dataKey="revenue" />
      <BarXAxis />
    </BarChart>
  );
}

For the common "render a placeholder until data arrives" case, BarChartLoading is a turnkey shortcut for <BarChart status="loading" />:

import { BarChartLoading } from "@bklitui/ui/charts";

<BarChartLoading />;
PropTypeDefaultDescription
status"loading" | "ready""ready"On BarChart. "loading" shows the shimmer skeleton
marginPartial<Margin>-On BarChartLoading. Chart margins
aspectRatiostring"2 / 1"On BarChartLoading. Container aspect ratio
classNamestring-On BarChartLoading. Additional container class

Animation

Bars animate with the same easing as Line charts (cubic-bezier(0.85, 0, 0.15, 1)) for a smooth, organic feel. The stagger delay is automatically calculated based on the number of bars to ensure all animations complete within the total animation duration (~1.2s).

Grow Animation (Default)

Bars grow from zero to their final size:

<Bar dataKey="revenue" animationType="grow" />

Fade Animation

Bars fade in with a blur effect:

<Bar dataKey="revenue" animationType="fade" />

Custom Stagger

Stagger is calculated automatically, but you can override it:

// Override automatic stagger with custom delay
<Bar dataKey="revenue" staggerDelay={0.02} />

// No stagger (all bars animate together)
<Bar dataKey="revenue" staggerDelay={0} />

Line Cap Styles

Control the bar end style:

// Rounded caps (default) - full rounding
<Bar dataKey="revenue" lineCap="round" />

// Square caps - no rounding
<Bar dataKey="revenue" lineCap="butt" />

// Custom radius in pixels
<Bar dataKey="revenue" lineCap={4} />
<Bar dataKey="revenue" lineCap={8} />

Theming

The Bar Chart uses the same CSS variables as other charts:

:root {
  --chart-background: oklch(1 0 0);
  --chart-foreground: oklch(0.145 0.004 285);
  --chart-foreground-muted: oklch(0.55 0.014 260);
  --chart-line-primary: oklch(0.623 0.214 255);
  --chart-line-secondary: oklch(0.705 0.015 265);
  --chart-crosshair: oklch(0.4 0.1828 274.34);
  --chart-grid: oklch(0.9 0 0);
}

.dark {
  --chart-background: oklch(0.145 0 0);
  --chart-foreground: oklch(0.45 0 0);
  --chart-crosshair: oklch(0.45 0 0);
  --chart-grid: oklch(0.25 0 0);
}

Dependencies

This component requires the same packages as the other charts:

pnpm add @visx/shape @visx/scale @visx/responsive @visx/event @visx/grid d3-array motion react-use-measure