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 rechartsCreate 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.
chart component first. The bar chart example uses Recharts' BarChart, Bar, CartesianGrid, and XAxis.
Usage
Define your data and achartConfig 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 forChartContainer, ChartConfig, ChartTooltip, and ChartLegend. Recharts API: BarChart, Bar.