Components

Base Components

Chart

Base chart primitive built with Recharts. Use ChartContainer, ChartTooltip, and ChartLegend to build bar, line, and other charts.

Example

Charts are built with Recharts. The chart component provides a responsive container, config-driven colors, and custom tooltip and legend content.
Series: 1
Series: 2
Series: 3
Line Chart
Bar Chart

Examples

Overview

The grid below shows single-series, multi-series, and stacked chart variants built with the same config and primitives.
Series: 1
Series: 2
Series: 3
Line Chart
Bar Chart

Line chart

Line chart with two series, tooltip, and legend.

Bar chart

Bar chart with two series, tooltip, and legend.

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.

Usage

Wrap your Recharts chart (e.g. BarChart, LineChart) in ChartContainer and pass a config object for labels and colors. Use var(--color-KEY) in your series to reference config colors.
import { Bar, BarChart } from "recharts";
import {
  ChartConfig,
  ChartContainer,
  ChartTooltip,
  ChartTooltipContent,
} from "@/components/ui/chart";

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

<ChartContainer config={chartConfig} className="h-[200px] w-full">
  <BarChart data={data}>
    <ChartTooltip content={<ChartTooltipContent />} />
    <Bar dataKey="desktop" fill="var(--color-desktop)" />
  </BarChart>
</ChartContainer>

Chart config

Define a ChartConfig with a key per series: label, optional icon, and color (or theme for light/dark).

API Reference

config*ChartConfig
Default: -
PropTypeDefaultDescription
config*ChartConfig--
Exports: ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent, ChartStyle, and type ChartConfig.

On this page