Components

Base Components

Line Chart

Line charts built with Recharts and the chart primitive. Use ChartContainer with LineChart for responsive, accessible line charts.

Example

Line charts display trends over time or categories. 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 line chart example uses Recharts' LineChart, Line, CartesianGrid, XAxis, and YAxis.

Usage

Define your data and a chartConfig, then wrap Recharts' LineChart in ChartContainer. Use ChartTooltip / ChartTooltipContent and ChartLegend / ChartLegendContent for tooltips and legend.
import { CartesianGrid, Line, LineChart, XAxis, YAxis } 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">
  <LineChart accessibilityLayer data={chartData}>
    <CartesianGrid vertical={false} strokeDasharray="3 3" />
    <XAxis dataKey="month" tickLine={false} axisLine={false} />
    <YAxis tickLine={false} axisLine={false} />
    <ChartTooltip content={<ChartTooltipContent />} />
    <ChartLegend content={<ChartLegendContent />} />
    <Line type="monotone" dataKey="desktop" stroke="var(--color-desktop)" strokeWidth={2} dot={false} />
    <Line type="monotone" dataKey="mobile" stroke="var(--color-mobile)" strokeWidth={2} dot={false} />
  </LineChart>
</ChartContainer>

Examples

Single series

A minimal line chart with one series and tooltip.

Two series (with tooltip and legend)

Desktop and mobile traffic with shared legend and tooltip.

Multi-series (four lines)

Multiple series with config-driven colors and legend.

API Reference

The line chart is built from the base Chart component and Recharts. See the Chart doc for ChartContainer, ChartConfig, ChartTooltip, and ChartLegend. Recharts API: LineChart, Line.

On this page