7

Bar Chart

PreviousNext

A flexible bar chart component for visualizing categorical data with support for multiple variants including horizontal, stacked, grouped, and interactive layouts.

"use client"

import { BarChart } from "@/components/ui/charts"

// Page view data - 30 days of traffic
const chartData = [
  186, 205, 237, 173, 209, 214, 278, 312, 245, 289, 234, 276, 198, 267, 289,
  312, 256, 234, 289, 267, 298, 312, 278, 256, 234, 267, 289, 312, 334, 356,
].map((value, i) => ({
  label: `${i + 1}`,
  value,
}))

export function BarChartDemo() {
  return (
    <BarChart
      data={chartData}
      showGrid
      showTooltip
      barRadius={0}
      aspectRatio={2}
    />
  )
}

About

The Bar Chart component is built on top of Recharts and provides a simple, declarative way to create bar charts. It supports various configurations including:

  • Vertical and horizontal orientations
  • Single and multiple data series
  • Stacked and grouped bar layouts
  • Custom tooltips with theme-aware styling
  • Responsive design that adapts to container width

Emphasis and annotation

activeLabel (or activeIndex) keeps one bar vivid and drops the rest to mutedColor; pattern / mutedPattern give either group a texture. A referenceLine takes a literal value or a statistic ("avg", "min", "max").

Nothing about the annotation look is fixed. labelVariant picks between plain (text only — the default, since a filled chip floating over a plot reads as a stuck tooltip), pill and flag; activeTickVariant picks plain, pill or underline. Set them per chart, set them once on a ChartThemeProvider, or pass your own renderer to label and skip the built-ins entirely.

<BarChart
  data={data}
  activeLabel="May"
  referenceLine={{ value: "avg", label: "Average", labelVariant: "flag" }}
  activeTickVariant="pill"
/>
"use client"

import {
  BarChart,
  ChartThemeProvider,
} from "@/components/ui/charts"

const chartData = [
  { label: "Jan", value: 5120 },
  { label: "Feb", value: 6840 },
  { label: "Mar", value: 5960 },
  { label: "Apr", value: 9980 },
  { label: "May", value: 9467 },
  { label: "Jun", value: 8210 },
  { label: "Jul", value: 7340 },
]

const currency = (value: number) => `$${value.toLocaleString("en-US")}`

export function BarChartEmphasisDemo() {
  return (
    <ChartThemeProvider preset="dashboard">
      <BarChart
        className="max-w-md"
        data={chartData}
        activeLabel="May"
        showActiveValue
        showGrid={false}
        showYAxis={false}
        aspectRatio={1.6}
        valueFormatter={currency}
        referenceLine={{ value: "avg", label: "Average" }}
      />
    </ChartThemeProvider>
  )
}

Mobile, zoom and overflow

Every category chart measures its own box, not the viewport, so a chart in a narrow card behaves like a chart on a phone. aspectRatio and legendPosition take a value per container-width breakpoint, minHeight stops a phone-width plot collapsing to a strip, and the category ticks thin themselves out until the labels stop colliding.

When the categories outnumber the pixels there are only two honest options, and overflow picks between them: compress squeezes them in, scroll gives each one minCategorySize and lets the plot overflow — sideways for vertical bars, downwards for horizontal ones.

Zoom works from every input: drag to box-select, ⌘/ctrl + wheel (or wheelZoom for a bare wheel), two-finger pinch, one-finger pan once zoomed, double-tap to reset, plus the +/−/reset buttons. It turns itself off while the plot is scrolling, since scroll already owns that axis.

Scroll — each bar keeps a floor width and the plot overflows sideways.

Zoom — drag to select, pinch or ⌘-scroll to scale, double-click to reset.

"use client"

import { BarChart } from "@/components/ui/charts"

// 48 days is more categories than a phone has pixels — compressing them
// would leave bars thinner than the gaps between them.
const chartData = Array.from({ length: 48 }, (_, i) => ({
  label: `D${i + 1}`,
  value: 220 + Math.round(90 * Math.sin(i / 2.3) + 60 * Math.cos(i / 5)),
}))

export function BarChartScrollDemo() {
  return (
    <div className="w-full space-y-8">
      <div className="space-y-2">
        <p className="text-muted-foreground text-sm">
          <span className="text-foreground font-medium">Scroll</span> — each bar
          keeps a floor width and the plot overflows sideways.
        </p>
        <BarChart
          data={chartData}
          overflow="scroll"
          minCategorySize={34}
          aspectRatio={{ base: 1.3, md: 2.6 }}
        />
      </div>

      <div className="space-y-2">
        <p className="text-muted-foreground text-sm">
          <span className="text-foreground font-medium">Zoom</span> — drag to
          select, pinch or ⌘-scroll to scale, double-click to reset.
        </p>
        <BarChart
          data={chartData}
          aspectRatio={{ base: 1.3, md: 2.6 }}
          wheelZoom
        />
      </div>
    </div>
  )
}
PropTypeDefaultDescription
aspectRationumber | { base?, sm?, md?, lg?, xl? }2Width ÷ height, per container width.
minHeightnumber180Floor on plot height, px.
overflow"compress" | "scroll""compress"What to do when categories outnumber pixels.
minCategorySizenumber32Room one category needs before scroll overflows.
zoomablebooleantrueDrag / pinch / wheel zoom.
wheelZoombooleanfalseLet a bare wheel zoom, not just ⌘/ctrl + wheel.
showZoomControlsbooleantrueThe +/−/reset buttons.
xAxisIntervalnumber | "preserveStartEnd"autoTicks skipped between labels.

Installation

pnpm dlx shadcn@latest add https://ui.simplifyingai.com/r/bar-chart.json

Usage

import { BarChart } from "@/components/ui/charts"
 
const data = [
  { label: "Jan", value: 4000 },
  { label: "Feb", value: 3000 },
  { label: "Mar", value: 5000 },
]
 
export function MyChart() {
  return <BarChart data={data} />
}

Examples

Default

The default bar chart displays categorical data with sharp-edged bars, horizontal grid lines, and an interactive tooltip on hover.

"use client"

import { BarChart } from "@/components/ui/charts"

// Page view data - 30 days of traffic
const chartData = [
  186, 205, 237, 173, 209, 214, 278, 312, 245, 289, 234, 276, 198, 267, 289,
  312, 256, 234, 289, 267, 298, 312, 278, 256, 234, 267, 289, 312, 334, 356,
].map((value, i) => ({
  label: `${i + 1}`,
  value,
}))

export function BarChartDemo() {
  return (
    <BarChart
      data={chartData}
      showGrid
      showTooltip
      barRadius={0}
      aspectRatio={2}
    />
  )
}

With Labels

Use the showLabel prop to display values directly on top of each bar. This is useful when you want to show exact values without requiring users to hover.

"use client"

import {
  Bar,
  BarChart,
  CartesianGrid,
  LabelList,
  ResponsiveContainer,
  Tooltip,
  XAxis,
  YAxis,
} from "recharts"

const chartData = [
  { month: "January", desktop: 186 },
  { month: "February", desktop: 305 },
  { month: "March", desktop: 237 },
  { month: "April", desktop: 73 },
  { month: "May", desktop: 209 },
  { month: "June", desktop: 214 },
]

export function BarChartLabelDemo() {
  return (
    <div className="w-full max-w-md">
      <ResponsiveContainer width="100%" aspect={1.5}>
        <BarChart
          data={chartData}
          margin={{
            top: 30,
            right: 10,
            left: 10,
            bottom: 40,
          }}
        >
          <CartesianGrid
            strokeDasharray="3 3"
            vertical={false}
            stroke="hsl(var(--border))"
            strokeOpacity={0.5}
          />
          <XAxis
            dataKey="month"
            axisLine={false}
            tickLine={false}
            tick={{ fontSize: 12 }}
            tickFormatter={(value) => value.slice(0, 3)}
            tickMargin={8}
          />
          <YAxis hide />
          <Tooltip
            cursor={{ fill: "hsl(var(--muted-foreground))", opacity: 0.08 }}
            content={({ active, payload, label }) => {
              if (!active || !payload?.length) return null
              return (
                <div className="bg-background rounded-lg border px-3 py-2 shadow-lg">
                  <p className="text-foreground text-sm font-medium">{label}</p>
                  <p className="text-sm" style={{ color: "#2563eb" }}>
                    Desktop: {payload[0]?.value}
                  </p>
                </div>
              )
            }}
          />
          <Bar dataKey="desktop" fill="#2563eb" radius={8}>
            <LabelList
              dataKey="desktop"
              position="top"
              offset={12}
              className="fill-foreground"
              fontSize={12}
            />
          </Bar>
        </BarChart>
      </ResponsiveContainer>
    </div>
  )
}
<BarChart data={data} showLabel labelPosition="top" />

Horizontal

Horizontal bar charts are ideal for displaying data with long category names or when you have many categories. Use layout="vertical" to switch orientation (Recharts naming convention).

"use client"

import {
  Bar,
  BarChart,
  CartesianGrid,
  ResponsiveContainer,
  Tooltip,
  XAxis,
  YAxis,
} from "recharts"

const chartData = [
  { month: "January", desktop: 186 },
  { month: "February", desktop: 305 },
  { month: "March", desktop: 237 },
  { month: "April", desktop: 73 },
  { month: "May", desktop: 209 },
  { month: "June", desktop: 214 },
]

export function BarChartHorizontalDemo() {
  return (
    <div className="w-full max-w-md">
      <ResponsiveContainer width="100%" aspect={1.5}>
        <BarChart
          data={chartData}
          layout="vertical"
          margin={{
            top: 10,
            right: 10,
            left: 10,
            bottom: 10,
          }}
        >
          <CartesianGrid
            strokeDasharray="3 3"
            horizontal={false}
            stroke="hsl(var(--border))"
            strokeOpacity={0.5}
          />
          <YAxis
            dataKey="month"
            type="category"
            axisLine={false}
            tickLine={false}
            tick={{ fontSize: 12 }}
            tickFormatter={(value) => value.slice(0, 3)}
            width={40}
          />
          <XAxis
            type="number"
            axisLine={false}
            tickLine={false}
            tick={{ fontSize: 12 }}
          />
          <Tooltip
            cursor={{ fill: "hsl(var(--muted-foreground))", opacity: 0.08 }}
            content={({ active, payload, label }) => {
              if (!active || !payload?.length) return null
              return (
                <div className="bg-background rounded-lg border px-3 py-2 shadow-lg">
                  <p className="text-foreground text-sm font-medium">{label}</p>
                  <p className="text-sm" style={{ color: "#2563eb" }}>
                    Desktop: {payload[0]?.value}
                  </p>
                </div>
              )
            }}
          />
          <Bar dataKey="desktop" fill="#2563eb" radius={4} />
        </BarChart>
      </ResponsiveContainer>
    </div>
  )
}
<BarChart data={data} layout="vertical" />

Multiple / Grouped

Display multiple data series side-by-side to compare different metrics across categories. Use the MultiBarChart component with a series configuration.

Desktop
Mobile
"use client"

import {
  Bar,
  BarChart,
  CartesianGrid,
  ResponsiveContainer,
  Tooltip,
  XAxis,
  YAxis,
} from "recharts"

const chartData = [
  { month: "January", desktop: 186, mobile: 80 },
  { month: "February", desktop: 305, mobile: 200 },
  { month: "March", desktop: 237, mobile: 120 },
  { month: "April", desktop: 73, mobile: 190 },
  { month: "May", desktop: 209, mobile: 130 },
  { month: "June", desktop: 214, mobile: 140 },
]

export function BarChartMultipleDemo() {
  return (
    <div className="w-full max-w-md">
      <ResponsiveContainer width="100%" aspect={1.5}>
        <BarChart
          data={chartData}
          margin={{
            top: 10,
            right: 10,
            left: 10,
            bottom: 40,
          }}
        >
          <CartesianGrid
            strokeDasharray="3 3"
            vertical={false}
            stroke="hsl(var(--border))"
            strokeOpacity={0.5}
          />
          <XAxis
            dataKey="month"
            axisLine={false}
            tickLine={false}
            tick={{ fontSize: 12 }}
            tickFormatter={(value) => value.slice(0, 3)}
            tickMargin={8}
          />
          <YAxis
            axisLine={false}
            tickLine={false}
            tick={{ fontSize: 12 }}
            width={36}
          />
          <Tooltip
            cursor={{ fill: "hsl(var(--muted-foreground))", opacity: 0.08 }}
            content={({ active, payload, label }) => {
              if (!active || !payload?.length) return null
              return (
                <div className="bg-background rounded-lg border px-3 py-2 shadow-lg">
                  <p className="text-foreground mb-1 text-sm font-medium">
                    {label}
                  </p>
                  {payload.map((entry, index) => (
                    <div
                      key={index}
                      className="flex items-center gap-2 text-sm"
                    >
                      <div
                        className="size-2 rounded-full"
                        style={{ backgroundColor: entry.color }}
                      />
                      <span className="text-muted-foreground">
                        {entry.name}:
                      </span>
                      <span style={{ color: entry.color }}>{entry.value}</span>
                    </div>
                  ))}
                </div>
              )
            }}
          />
          <Bar dataKey="desktop" fill="#2563eb" radius={4} />
          <Bar dataKey="mobile" fill="#60a5fa" radius={4} />
        </BarChart>
      </ResponsiveContainer>

      {/* Legend */}
      <div className="mt-4 flex flex-wrap items-center justify-center gap-4">
        <div className="flex items-center gap-2 text-sm">
          <div className="size-3 rounded-sm bg-[#2563eb]" />
          <span className="text-muted-foreground">Desktop</span>
        </div>
        <div className="flex items-center gap-2 text-sm">
          <div className="size-3 rounded-sm bg-[#60a5fa]" />
          <span className="text-muted-foreground">Mobile</span>
        </div>
      </div>
    </div>
  )
}
import { MultiBarChart } from "@/components/ui/charts"
 
const data = [
  { label: "Jan", desktop: 186, mobile: 80 },
  { label: "Feb", desktop: 305, mobile: 200 },
]
 
const series = [
  { name: "Desktop", dataKey: "desktop", color: "#2563eb" },
  { name: "Mobile", dataKey: "mobile", color: "#60a5fa" },
]
 
<MultiBarChart data={data} series={series} />

Stacked

Stacked bar charts show the cumulative total of multiple series. Add a stackId to each series to group them into stacks.

Desktop
Mobile
"use client"

import {
  Bar,
  BarChart,
  CartesianGrid,
  ResponsiveContainer,
  Tooltip,
  XAxis,
  YAxis,
} from "recharts"

const chartData = [
  { month: "January", desktop: 186, mobile: 80 },
  { month: "February", desktop: 305, mobile: 200 },
  { month: "March", desktop: 237, mobile: 120 },
  { month: "April", desktop: 73, mobile: 190 },
  { month: "May", desktop: 209, mobile: 130 },
  { month: "June", desktop: 214, mobile: 140 },
]

export function BarChartStackedDemo() {
  return (
    <div className="w-full max-w-md">
      <ResponsiveContainer width="100%" aspect={1.5}>
        <BarChart
          data={chartData}
          margin={{
            top: 10,
            right: 10,
            left: 10,
            bottom: 40,
          }}
        >
          <CartesianGrid
            strokeDasharray="3 3"
            vertical={false}
            stroke="hsl(var(--border))"
            strokeOpacity={0.5}
          />
          <XAxis
            dataKey="month"
            axisLine={false}
            tickLine={false}
            tick={{ fontSize: 12 }}
            tickFormatter={(value) => value.slice(0, 3)}
            tickMargin={8}
          />
          <YAxis
            axisLine={false}
            tickLine={false}
            tick={{ fontSize: 12 }}
            width={36}
          />
          <Tooltip
            cursor={{ fill: "hsl(var(--muted-foreground))", opacity: 0.08 }}
            content={({ active, payload, label }) => {
              if (!active || !payload?.length) return null
              const total = payload.reduce(
                (sum, entry) => sum + (entry.value as number),
                0
              )
              return (
                <div className="bg-background rounded-lg border px-3 py-2 shadow-lg">
                  <p className="text-foreground mb-1 text-sm font-medium">
                    {label}
                  </p>
                  {payload.map((entry, index) => (
                    <div
                      key={index}
                      className="flex items-center gap-2 text-sm"
                    >
                      <div
                        className="size-2 rounded-full"
                        style={{ backgroundColor: entry.color }}
                      />
                      <span className="text-muted-foreground">
                        {entry.name}:
                      </span>
                      <span style={{ color: entry.color }}>{entry.value}</span>
                    </div>
                  ))}
                  <div className="text-muted-foreground mt-1 border-t pt-1 text-sm">
                    Total: {total}
                  </div>
                </div>
              )
            }}
          />
          <Bar
            dataKey="desktop"
            fill="#2563eb"
            stackId="stack"
            radius={[0, 0, 4, 4]}
          />
          <Bar
            dataKey="mobile"
            fill="#60a5fa"
            stackId="stack"
            radius={[4, 4, 0, 0]}
          />
        </BarChart>
      </ResponsiveContainer>

      {/* Legend */}
      <div className="mt-4 flex flex-wrap items-center justify-center gap-4">
        <div className="flex items-center gap-2 text-sm">
          <div className="size-3 rounded-sm bg-[#2563eb]" />
          <span className="text-muted-foreground">Desktop</span>
        </div>
        <div className="flex items-center gap-2 text-sm">
          <div className="size-3 rounded-sm bg-[#60a5fa]" />
          <span className="text-muted-foreground">Mobile</span>
        </div>
      </div>
    </div>
  )
}
const series = [
  { name: "Desktop", dataKey: "desktop", color: "#2563eb", stackId: "stack" },
  { name: "Mobile", dataKey: "mobile", color: "#60a5fa", stackId: "stack" },
]
 
<MultiBarChart data={data} series={series} />

Negative Values

Bar charts can display both positive and negative values. Bars automatically extend above or below the zero reference line.

"use client"

import {
  Bar,
  BarChart,
  CartesianGrid,
  Cell,
  ReferenceLine,
  ResponsiveContainer,
  Tooltip,
  XAxis,
  YAxis,
} from "recharts"

const chartData = [
  { month: "January", value: 186 },
  { month: "February", value: -125 },
  { month: "March", value: 237 },
  { month: "April", value: -73 },
  { month: "May", value: 209 },
  { month: "June", value: -156 },
]

export function BarChartNegativeDemo() {
  return (
    <div className="w-full max-w-md">
      <ResponsiveContainer width="100%" aspect={1.5}>
        <BarChart
          data={chartData}
          margin={{
            top: 10,
            right: 10,
            left: 10,
            bottom: 40,
          }}
        >
          <CartesianGrid
            strokeDasharray="3 3"
            vertical={false}
            stroke="hsl(var(--border))"
            strokeOpacity={0.5}
          />
          <XAxis
            dataKey="month"
            axisLine={false}
            tickLine={false}
            tick={{ fontSize: 12 }}
            tickFormatter={(value) => value.slice(0, 3)}
            tickMargin={8}
          />
          <YAxis
            axisLine={false}
            tickLine={false}
            tick={{ fontSize: 12 }}
            width={40}
          />
          <ReferenceLine y={0} stroke="hsl(var(--border))" strokeWidth={1} />
          <Tooltip
            cursor={{ fill: "hsl(var(--muted-foreground))", opacity: 0.08 }}
            content={({ active, payload, label }) => {
              if (!active || !payload?.length) return null
              const value = payload[0]?.value as number
              return (
                <div className="bg-background rounded-lg border px-3 py-2 shadow-lg">
                  <p className="text-foreground text-sm font-medium">{label}</p>
                  <p
                    className="text-sm"
                    style={{ color: value >= 0 ? "#2563eb" : "#60a5fa" }}
                  >
                    Value: {value}
                  </p>
                </div>
              )
            }}
          />
          <Bar dataKey="value" radius={4}>
            {chartData.map((entry, index) => (
              <Cell
                key={`cell-${index}`}
                fill={entry.value >= 0 ? "#2563eb" : "#60a5fa"}
              />
            ))}
          </Bar>
        </BarChart>
      </ResponsiveContainer>
    </div>
  )
}

Interactive

Create interactive charts with state-controlled data series. Users can toggle between different views of the data.

"use client"

import * as React from "react"
import {
  Bar,
  BarChart,
  CartesianGrid,
  ResponsiveContainer,
  Tooltip,
  XAxis,
  YAxis,
} from "recharts"

const chartData = [
  { date: "2024-04-01", desktop: 222, mobile: 150 },
  { date: "2024-04-02", desktop: 97, mobile: 180 },
  { date: "2024-04-03", desktop: 167, mobile: 120 },
  { date: "2024-04-04", desktop: 242, mobile: 260 },
  { date: "2024-04-05", desktop: 373, mobile: 290 },
  { date: "2024-04-06", desktop: 301, mobile: 340 },
  { date: "2024-04-07", desktop: 245, mobile: 180 },
  { date: "2024-04-08", desktop: 409, mobile: 320 },
  { date: "2024-04-09", desktop: 59, mobile: 110 },
  { date: "2024-04-10", desktop: 261, mobile: 190 },
  { date: "2024-04-11", desktop: 327, mobile: 350 },
  { date: "2024-04-12", desktop: 292, mobile: 210 },
  { date: "2024-04-13", desktop: 342, mobile: 380 },
  { date: "2024-04-14", desktop: 137, mobile: 220 },
  { date: "2024-04-15", desktop: 120, mobile: 170 },
  { date: "2024-04-16", desktop: 138, mobile: 190 },
  { date: "2024-04-17", desktop: 446, mobile: 360 },
  { date: "2024-04-18", desktop: 364, mobile: 410 },
  { date: "2024-04-19", desktop: 243, mobile: 180 },
  { date: "2024-04-20", desktop: 89, mobile: 150 },
]

export function BarChartInteractiveDemo() {
  const [activeChart, setActiveChart] = React.useState<"desktop" | "mobile">(
    "desktop"
  )

  const total = React.useMemo(
    () => ({
      desktop: chartData.reduce((acc, curr) => acc + curr.desktop, 0),
      mobile: chartData.reduce((acc, curr) => acc + curr.mobile, 0),
    }),
    []
  )

  return (
    <div className="w-full">
      {/* Header with toggles */}
      <div className="flex">
        <button
          className={`flex flex-1 flex-col justify-center gap-1 border-b px-6 py-4 text-left transition-colors even:border-l sm:border-l sm:px-8 sm:py-6 ${
            activeChart === "desktop" ? "bg-muted/50" : "hover:bg-muted/30"
          }`}
          onClick={() => setActiveChart("desktop")}
        >
          <span className="text-muted-foreground text-xs">Desktop</span>
          <span className="text-foreground text-lg leading-none font-bold sm:text-3xl">
            {total.desktop.toLocaleString()}
          </span>
        </button>
        <button
          className={`flex flex-1 flex-col justify-center gap-1 border-b px-6 py-4 text-left transition-colors even:border-l sm:border-l sm:px-8 sm:py-6 ${
            activeChart === "mobile" ? "bg-muted/50" : "hover:bg-muted/30"
          }`}
          onClick={() => setActiveChart("mobile")}
        >
          <span className="text-muted-foreground text-xs">Mobile</span>
          <span className="text-foreground text-lg leading-none font-bold sm:text-3xl">
            {total.mobile.toLocaleString()}
          </span>
        </button>
      </div>

      {/* Chart */}
      <div className="px-2 pt-4 sm:px-6 sm:pt-6">
        <ResponsiveContainer width="100%" aspect={2.5}>
          <BarChart
            data={chartData}
            margin={{
              top: 10,
              right: 10,
              left: 10,
              bottom: 40,
            }}
          >
            <CartesianGrid
              strokeDasharray="3 3"
              vertical={false}
              stroke="hsl(var(--border))"
              strokeOpacity={0.5}
            />
            <XAxis
              dataKey="date"
              axisLine={false}
              tickLine={false}
              tick={{ fontSize: 12 }}
              tickFormatter={(value) => {
                const date = new Date(value)
                return date.toLocaleDateString("en-US", {
                  month: "short",
                  day: "numeric",
                })
              }}
              tickMargin={8}
              minTickGap={32}
            />
            <YAxis hide />
            <Tooltip
              cursor={{ fill: "hsl(var(--muted-foreground))", opacity: 0.08 }}
              content={({ active, payload, label }) => {
                if (!active || !payload?.length) return null
                const date = new Date(label)
                return (
                  <div className="bg-background rounded-lg border px-3 py-2 shadow-lg">
                    <p className="text-foreground text-sm font-medium">
                      {date.toLocaleDateString("en-US", {
                        month: "short",
                        day: "numeric",
                        year: "numeric",
                      })}
                    </p>
                    <p
                      className="text-sm"
                      style={{
                        color:
                          activeChart === "desktop" ? "#2563eb" : "#60a5fa",
                      }}
                    >
                      {activeChart === "desktop" ? "Desktop" : "Mobile"}:{" "}
                      {payload[0]?.value}
                    </p>
                  </div>
                )
              }}
            />
            <Bar
              dataKey={activeChart}
              fill={activeChart === "desktop" ? "#2563eb" : "#60a5fa"}
              radius={[4, 4, 0, 0]}
            />
          </BarChart>
        </ResponsiveContainer>
      </div>
    </div>
  )
}

Mixed (Bar + Line)

Combine bars with a line chart to show both absolute values and trends. Uses Recharts' ComposedChart component.

Desktop
Trend
"use client"

import {
  Bar,
  CartesianGrid,
  ComposedChart,
  Line,
  ResponsiveContainer,
  Tooltip,
  XAxis,
  YAxis,
} from "recharts"

const chartData = [
  { month: "January", desktop: 186, trend: 160 },
  { month: "February", desktop: 305, trend: 220 },
  { month: "March", desktop: 237, trend: 250 },
  { month: "April", desktop: 73, trend: 180 },
  { month: "May", desktop: 209, trend: 200 },
  { month: "June", desktop: 214, trend: 230 },
]

export function BarChartMixedDemo() {
  return (
    <div className="w-full max-w-md">
      <ResponsiveContainer width="100%" aspect={1.5}>
        <ComposedChart
          data={chartData}
          margin={{
            top: 10,
            right: 10,
            left: 10,
            bottom: 40,
          }}
        >
          <CartesianGrid
            strokeDasharray="3 3"
            vertical={false}
            stroke="hsl(var(--border))"
            strokeOpacity={0.5}
          />
          <XAxis
            dataKey="month"
            axisLine={false}
            tickLine={false}
            tick={{ fontSize: 12 }}
            tickFormatter={(value) => value.slice(0, 3)}
            tickMargin={8}
          />
          <YAxis
            axisLine={false}
            tickLine={false}
            tick={{ fontSize: 12 }}
            width={36}
          />
          <Tooltip
            cursor={{ fill: "hsl(var(--muted-foreground))", opacity: 0.08 }}
            content={({ active, payload, label }) => {
              if (!active || !payload?.length) return null
              return (
                <div className="bg-background rounded-lg border px-3 py-2 shadow-lg">
                  <p className="text-foreground mb-1 text-sm font-medium">
                    {label}
                  </p>
                  {payload.map((entry, index) => (
                    <div
                      key={index}
                      className="flex items-center gap-2 text-sm"
                    >
                      <div
                        className="size-2 rounded-full"
                        style={{ backgroundColor: entry.color }}
                      />
                      <span className="text-muted-foreground">
                        {entry.name}:
                      </span>
                      <span style={{ color: entry.color }}>{entry.value}</span>
                    </div>
                  ))}
                </div>
              )
            }}
          />
          <Bar dataKey="desktop" name="Desktop" fill="#2563eb" radius={4} />
          <Line
            type="monotone"
            dataKey="trend"
            name="Trend"
            stroke="#60a5fa"
            strokeWidth={2}
            dot={{ r: 4, fill: "#60a5fa" }}
            activeDot={{ r: 6, fill: "#60a5fa" }}
          />
        </ComposedChart>
      </ResponsiveContainer>

      {/* Legend */}
      <div className="mt-4 flex flex-wrap items-center justify-center gap-4">
        <div className="flex items-center gap-2 text-sm">
          <div className="size-3 rounded-sm bg-[#2563eb]" />
          <span className="text-muted-foreground">Desktop</span>
        </div>
        <div className="flex items-center gap-2 text-sm">
          <div className="h-0.5 w-3 rounded-full bg-[#60a5fa]" />
          <span className="text-muted-foreground">Trend</span>
        </div>
      </div>
    </div>
  )
}

Active Bar

Highlight a specific bar using a custom shape with a dashed border effect. Useful for emphasizing a selected or current data point.

"use client"

import {
  Bar,
  BarChart,
  Rectangle,
  ResponsiveContainer,
  Tooltip,
  XAxis,
  YAxis,
} from "recharts"

const chartData = [
  { month: "January", sales: 187, fill: "#93c5fd" },
  { month: "February", sales: 200, fill: "#3b82f6" },
  { month: "March", sales: 275, fill: "#2563eb" },
  { month: "April", sales: 173, fill: "#1d4ed8" },
  { month: "May", sales: 209, fill: "#1e40af" },
]

// Index of the active (highlighted) bar
const ACTIVE_INDEX = 2

interface BarShapeProps {
  x?: number
  y?: number
  width?: number
  height?: number
  fill?: string
  index?: number
  payload?: { fill: string }
  radius?: number | [number, number, number, number]
  [key: string]: unknown
}

export function BarChartActiveDemo() {
  return (
    <div className="w-full max-w-md">
      <ResponsiveContainer width="100%" aspect={1.5}>
        <BarChart
          data={chartData}
          margin={{
            top: 10,
            right: 10,
            left: 10,
            bottom: 40,
          }}
        >
          <XAxis
            dataKey="month"
            axisLine={false}
            tickLine={false}
            tick={{ fontSize: 12 }}
            tickFormatter={(value) => value.slice(0, 3)}
            tickMargin={10}
          />
          <YAxis hide />
          <Tooltip
            cursor={false}
            content={({ active, payload }) => {
              if (!active || !payload?.length) return null
              const data = payload[0]
              return (
                <div className="bg-background flex items-center gap-2 rounded-lg border px-3 py-2 shadow-lg">
                  <div
                    className="size-3 rounded-sm"
                    style={{ backgroundColor: data.payload?.fill }}
                  />
                  <span className="text-muted-foreground text-sm">Sales</span>
                  <span className="text-foreground text-sm font-medium">
                    {data.value}
                  </span>
                </div>
              )
            }}
          />
          <Bar
            dataKey="sales"
            radius={12}
            shape={(props: unknown) => {
              const { index, fill, ...rest } = props as BarShapeProps
              return index === ACTIVE_INDEX ? (
                <Rectangle
                  {...rest}
                  fill={fill}
                  fillOpacity={0.6}
                  stroke={fill}
                  strokeWidth={2}
                  strokeDasharray="4 4"
                />
              ) : (
                <Rectangle {...rest} fill={fill} />
              )
            }}
          />
        </BarChart>
      </ResponsiveContainer>
    </div>
  )
}
import { Bar, BarChart, Rectangle } from "recharts"
 
const ACTIVE_INDEX = 2
 
<Bar
  dataKey="sales"
  radius={12}
  shape={({ index, fill, ...props }) =>
    index === ACTIVE_INDEX ? (
      <Rectangle
        {...props}
        fill={fill}
        fillOpacity={0.6}
        stroke={fill}
        strokeWidth={2}
        strokeDasharray="4 4"
      />
    ) : (
      <Rectangle {...props} fill={fill} />
    )
  }
/>

API Reference

BarChart

PropTypeDefaultDescription
dataBarChartDataPoint[]requiredArray of data points to display
colorstring"#2563eb"Fill color for bars
classNamestring-Additional CSS classes
showGridbooleantrueShow horizontal grid lines
showTooltipbooleantrueEnable tooltip on hover
showLabelbooleanfalseShow value labels on bars
labelPosition"top" | "center" | "bottom" | "inside""top"Position of value labels
valueFormatter(value: number) => string-Format displayed values
labelFormatter(label: string) => string-Format category labels
layout"vertical" | "horizontal""horizontal"Bar orientation (vertical = horizontal bars)
barRadiusnumber4Corner radius of bars
aspectRationumber2Width to height ratio
yAxisWidthnumber48Width reserved for Y-axis labels

MultiBarChart

PropTypeDefaultDescription
dataMultiBarChartDataPoint[]requiredArray of data points
seriesMultiBarChartSeries[]requiredConfiguration for each data series
classNamestring-Additional CSS classes
showGridbooleantrueShow horizontal grid lines
showTooltipbooleantrueEnable tooltip on hover
showLegendbooleantrueShow legend below chart
valueFormatter(value: number) => string-Format displayed values
labelFormatter(label: string) => string-Format category labels
layout"vertical" | "horizontal""horizontal"Bar orientation
barRadiusnumber4Corner radius of bars
aspectRationumber2Width to height ratio
yAxisWidthnumber48Width reserved for Y-axis labels

Data Types

// Single series data point
interface BarChartDataPoint {
  label: string // Category name (e.g., "January")
  value: number // Numeric value
  fill?: string // Optional: override color for this bar
}
 
// Multiple series data point
interface MultiBarChartDataPoint {
  label: string // Category name
  [key: string]: string | number // Dynamic keys for each series
}
 
// Series configuration
interface MultiBarChartSeries {
  name: string // Display name in tooltip/legend
  dataKey: string // Key in data object
  color: string // Bar fill color
  stackId?: string // Optional: group into stack
}

Customization

Custom Colors

Pass a color prop or use per-bar colors via the fill property in data:

const data = [
  { label: "Success", value: 80, fill: "#22c55e" },
  { label: "Warning", value: 15, fill: "#eab308" },
  { label: "Error", value: 5, fill: "#ef4444" },
]
 
<BarChart data={data} />

Custom Tooltip

For advanced tooltip customization, use the Recharts components directly:

import { Bar, BarChart, Tooltip } from "recharts"
 
;<BarChart data={data}>
  <Tooltip
    content={({ active, payload }) => (
      <div className="custom-tooltip">{/* Your custom content */}</div>
    )}
  />
  <Bar dataKey="value" />
</BarChart>

Accessibility

  • Charts include proper ARIA labels
  • Tooltips are keyboard accessible
  • Color schemes maintain sufficient contrast
  • Grid lines help users track values across the chart

Notes

  • Bar charts are best suited for comparing categorical data across different groups
  • Use horizontal layout (layout="vertical") when dealing with long category labels
  • Stacked bar charts are ideal for showing part-to-whole relationships over categories
  • The MultiBarChart component automatically handles legend generation and color coordination
  • For time-series data with many points, consider using a line or area chart instead
  • Bar radius can be adjusted for different visual styles (sharp edges vs rounded corners)