Components

Base Components

Bar Chart

Bar charts built with Recharts and the chart primitive. Use ChartContainer with BarChart for responsive, accessible bar charts.

Example

Bar charts display categorical data with rectangular bars. Built with Recharts and the shared chart components (ChartContainer, ChartTooltip, ChartLegend).

Installation

Install the following dependencies:

npm install recharts

Create a chart.tsx file and paste the following code into it.

"use client"import * as React from "react"import * as RechartsPrimitive from "recharts"import type {  DefaultLegendContentProps,  LegendPayload,  TooltipContentProps,  TooltipPayloadEntry,} from "@/types/recharts-chart-shim"import { cn } from "@/lib/utils"/** Props Recharts passes when `content={<ChartTooltipContent />}` */type TooltipPropsInjectedByRecharts = Pick<  TooltipContentProps,  | "active"  | "payload"  | "label"  | "coordinate"  | "accessibilityLayer"  | "activeIndex">const THEMES = { light: "", dark: ".dark" } as constexport type ChartConfig = {  [k in string]: {    label?: React.ReactNode    icon?: React.ComponentType<{ className?: string }>  } & (    | { color?: string; theme?: never }    | { color?: never; theme: Record<keyof typeof THEMES, string> }  )}type ChartContextProps = {  config: ChartConfig}const ChartContext = React.createContext<ChartContextProps | null>(null)function useChart() {  const context = React.useContext(ChartContext)  if (!context) {    throw new Error("useChart must be used within a ChartContainer")  }  return context}function getPayloadConfigFromPayload(  config: ChartConfig,  payload: unknown,  key: string) {  if (typeof payload !== "object" || payload === null) return undefined  const payloadPayload =    "payload" in payload &&    typeof (payload as { payload?: unknown }).payload === "object" &&    (payload as { payload?: unknown }).payload !== null    ? (payload as { payload: Record<string, unknown> }).payload    : undefined  let configLabelKey: string = key  const pl = payload as Record<string, unknown>  if (key in pl && typeof pl[key] === "string") {    configLabelKey = pl[key] as string  } else if (    payloadPayload &&    key in payloadPayload &&    typeof payloadPayload[key] === "string"  ) {    configLabelKey = payloadPayload[key] as string  }  return configLabelKey in config    ? config[configLabelKey]    : config[key as keyof typeof config]}const ChartContainer = React.forwardRef<  HTMLDivElement,  React.ComponentProps<"div"> & {    config: ChartConfig    /**     * Recharts `ResponsiveContainer` forwards width/height to each valid child.     * Use a single chart root for best results; multiple siblings (e.g. chart + overlay)     * are supported, but overlays should not block pointer events (`pointer-events-none`)     * if tooltips need the chart surface.     */    children: React.ReactNode  }>(({ id, className, children, config, ...props }, ref) => {  const uniqueId = React.useId()  const chartId = `chart-${id ?? uniqueId.replace(/:/g, "")}`  return (    <ChartContext.Provider value={{ config }}>      <div        ref={ref}        data-chart={chartId}        className={cn(          "w-full min-h-0 min-w-0 [&_.recharts-responsive-container]:min-h-0",          className,        )}        {...props}      >        <ChartStyle id={chartId} config={config} />        <RechartsPrimitive.ResponsiveContainer          width="100%"          height="100%"          minWidth={0}          minHeight={1}          initialDimension={{ width: 1, height: 1 }}        >          {/* Recharts types only `ReactElement`; runtime supports multiple roots via Children.map */}          {children as React.ReactElement}        </RechartsPrimitive.ResponsiveContainer>      </div>    </ChartContext.Provider>  )})ChartContainer.displayName = "ChartContainer"function ChartStyle({ id, config }: { id: string; config: ChartConfig }) {  const colorConfig = Object.entries(config).filter(    ([, c]) => c.theme != null || c.color != null  )  if (!colorConfig.length) return null  const css = Object.entries(THEMES)    .map(([theme, prefix]) => {      const selector = prefix ? `${prefix} [data-chart=${id}]` : `[data-chart=${id}]`      const vars = colorConfig        .map(([key, itemConfig]) => {          const color =            itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ??            itemConfig.color          return color ? `  --color-${key}: ${color};` : null        })        .filter(Boolean)        .join("\n")      return `${selector} {\n${vars}\n}`    })    .join("\n\n")  return <style dangerouslySetInnerHTML={{ __html: css }} />}const ChartTooltip = RechartsPrimitive.Tooltipconst ChartTooltipContent = React.forwardRef<  HTMLDivElement,  Omit<TooltipContentProps, keyof TooltipPropsInjectedByRecharts> &    Partial<TooltipPropsInjectedByRecharts> &    React.ComponentPropsWithoutRef<"div"> & {      hideLabel?: boolean      hideIndicator?: boolean      indicator?: "line" | "dot" | "dashed"      nameKey?: string      labelKey?: string    }>(  (    {      active,      payload,      className,      indicator = "dot",      hideLabel = false,      hideIndicator = false,      label,      labelFormatter,      labelClassName,      formatter,      color,      nameKey,      labelKey,    },    ref  ) => {    const { config } = useChart()    const tooltipLabel = React.useMemo(() => {      if (hideLabel || !payload?.length) return null      const [item] = payload      const key = `${labelKey ?? (item?.dataKey ?? item?.name) ?? "value"}`      const itemConfig = getPayloadConfigFromPayload(config, item, key)      const value =        labelKey == null && typeof label === "string"          ? config[label as keyof typeof config]?.label ?? label          : itemConfig?.label      if (labelFormatter != null) {        return <>{labelFormatter(value, payload)}</>      }      if (value == null) return null      return <span className={labelClassName}>{value}</span>    }, [label, labelFormatter, payload, hideLabel, labelClassName, config, labelKey])    if (!active || !payload?.length) return null    const nestLabel = payload.length === 1 && indicator !== "dot"    return (      <div        ref={ref}        className={cn(          "rounded-lg border bg-popover px-2.5 py-1.5 text-sm text-popover-foreground shadow-md",          className        )}      >        {!nestLabel ? tooltipLabel : null}        {payload.map((item: TooltipPayloadEntry, index: number) => {          const key = `${nameKey ?? item.name ?? item.dataKey ?? "value"}`          const itemConfig = getPayloadConfigFromPayload(config, item, key)          const indicatorColor =            color ?? (item.payload as { fill?: string })?.fill ?? item.color          const rowKey =            typeof item.dataKey === "string" || typeof item.dataKey === "number"              ? item.dataKey              : index          return (            <div key={rowKey} className="flex items-center gap-2">              {formatter != null && item?.value !== undefined && item.name != null ? (                formatter(item.value, item.name, item, index, payload)              ) : (                <>                  {itemConfig?.icon != null ? (                    <itemConfig.icon className="size-4 shrink-0" />                  ) : (                    !hideIndicator && (                      <span                        className={cn(                          "rounded-full shrink-0",                          indicator === "dot" && "size-2.5",                          indicator === "line" && "h-0.5 w-4",                          indicator === "dashed" && "h-0.5 w-4 border-t-2 border-dashed"                        )}                        style={{                          backgroundColor:                            indicator === "dot" ? indicatorColor : undefined,                          borderColor: indicator === "dashed" ? indicatorColor : undefined,                        }}                      />                    )                  )}                  {nestLabel ? tooltipLabel : null}                  <span>{itemConfig?.label ?? item.name}</span>                  {item.value != null && (                    <span className="font-medium tabular-nums">                      {item.value.toLocaleString()}                    </span>                  )}                </>              )}            </div>          )        })}      </div>    )  })ChartTooltipContent.displayName = "ChartTooltipContent"const ChartLegend = RechartsPrimitive.Legendconst ChartLegendContent = React.forwardRef<  HTMLDivElement,  DefaultLegendContentProps &    React.ComponentPropsWithoutRef<"div"> & {      hideIcon?: boolean      nameKey?: string    }>(({ className, hideIcon = false, payload, nameKey }, ref) => {  const { config } = useChart()  if (!payload?.length) return null  return (    <div      ref={ref}      className={cn(        "flex flex-wrap justify-center gap-4 gap-y-2 [&_svg]:size-3.5",        className      )}    >      {payload.map((item: LegendPayload) => {        const key = `${nameKey ?? item.dataKey ?? "value"}`        const itemConfig = getPayloadConfigFromPayload(config, item, key)        return (          <div key={item.value} className="flex items-center gap-1.5">            {itemConfig?.icon != null && !hideIcon ? (              <itemConfig.icon className="size-4 shrink-0" />            ) : (              <span                className="size-2.5 shrink-0 rounded-full"                style={{                  backgroundColor: item.color ?? (item.payload as { fill?: string })?.fill,                }}              />            )}            <span className="text-muted-foreground text-xs">              {itemConfig?.label ?? item.value}            </span>          </div>        )      })}    </div>  )})ChartLegendContent.displayName = "ChartLegendContent"export {  ChartContainer,  ChartTooltip,  ChartTooltipContent,  ChartLegend,  ChartLegendContent,  ChartStyle,}

Check the import paths to ensure they match your project setup.

Install the base chart component first. The bar chart example uses Recharts' BarChart, Bar, CartesianGrid, and XAxis.

Usage

Define your data and a chartConfig for labels and colors, then wrap Recharts' BarChart in ChartContainer. Use ChartTooltip / ChartTooltipContent and ChartLegend / ChartLegendContent for tooltips and legend.
import { Bar, BarChart, CartesianGrid, XAxis } from "recharts";
import {
  ChartConfig,
  ChartContainer,
  ChartLegend,
  ChartLegendContent,
  ChartTooltip,
  ChartTooltipContent,
} from "@/components/ui/chart";

const chartData = [
  { month: "January", desktop: 186, mobile: 80 },
  { month: "February", desktop: 305, mobile: 200 },
];

const chartConfig = {
  desktop: { label: "Desktop", color: "var(--chart-1)" },
  mobile: { label: "Mobile", color: "var(--chart-2)" },
} satisfies ChartConfig;

<ChartContainer config={chartConfig} className="h-[200px] w-full">
  <BarChart accessibilityLayer data={chartData}>
    <CartesianGrid vertical={false} />
    <XAxis dataKey="month" tickLine={false} axisLine={false} />
    <ChartTooltip content={<ChartTooltipContent />} />
    <ChartLegend content={<ChartLegendContent />} />
    <Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
    <Bar dataKey="mobile" fill="var(--color-mobile)" radius={4} />
  </BarChart>
</ChartContainer>

Examples

Single series

A minimal bar chart with one series and tooltip.

Two series (with tooltip and legend)

Desktop and mobile side by side with shared legend and tooltip.

Stacked bars

Multiple series stacked in the same bars.

API Reference

The bar chart is built from the base Chart component and Recharts. See the Chart doc for ChartContainer, ChartConfig, ChartTooltip, and ChartLegend. Recharts API: BarChart, Bar.

On this page