Shadcn Area Chart Generator

Show volume over time with a shadcn/ui area chart: soft gradient fills, overlapping or stacked series, and a 100% mode for shares.

CSV, TSV from Excel/Sheets, a Markdown table, or JSON.

CSV
Layout
Tooltip
Code
+83%Growth
Product A67K
Product B42K
Product C28.1K
Image
chart.tsx
"use client"

import { useId } from "react"
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"
import {
  type ChartConfig,
  ChartContainer,
  ChartLegend,
  ChartLegendContent,
  ChartTooltip,
} from "@/components/ui/chart"

const data = [
  { Quarter: "Q1", "Product A": 12000, "Product B": 8000, "Product C": 5000 },
  { Quarter: "Q2", "Product A": 15000, "Product B": 9500, "Product C": 6200 },
  { Quarter: "Q3", "Product A": 18000, "Product B": 11000, "Product C": 7800 },
  { Quarter: "Q4", "Product A": 22000, "Product B": 13500, "Product C": 9100 },
]

const chartConfig = {
  "Product A": { label: "Product A", color: "var(--chart-1)" },
  "Product B": { label: "Product B", color: "var(--chart-2)" },
  "Product C": { label: "Product C", color: "var(--chart-3)" },
} satisfies ChartConfig

/** Tooltip for multi-series charts: the row total plus each series' share. */
function ChartBreakdownTooltip({
  active,
  payload,
  label,
  labelFormatter,
  config,
}: {
  active?: boolean
  payload?: ReadonlyArray<{ dataKey?: unknown; value?: unknown }>
  label?: string | number
  labelFormatter?: (label: unknown) => string
  config: ChartConfig
}) {
  if (!active || !payload?.length) return null

  const entries = payload
    .map((item) => {
      const key = String(item.dataKey)
      const value = Math.abs(Number(item.value))
      return {
        key,
        label: config[key]?.label ?? key,
        color: config[key]?.color ?? "var(--chart-1)",
        value: Number.isFinite(value) ? value : 0,
      }
    })
    .filter((entry) => entry.value > 0)
  if (entries.length === 0) return null

  const total = entries.reduce((sum, entry) => sum + entry.value, 0)
  const stops = entries.map((entry, index) => {
    const before = entries.slice(0, index).reduce((sum, e) => sum + e.value, 0)
    return `${entry.color} ${(before / total) * 100}% ${((before + entry.value) / total) * 100}%`
  })

  return (
    <div className="min-w-52 rounded-xl border border-border bg-card p-3 text-xs shadow-lg">
      <div className="mb-2 flex items-center justify-between gap-3">
        <span className="font-medium text-foreground">
          {label !== undefined && labelFormatter ? labelFormatter(label) : label}
        </span>
        <span className="font-mono font-semibold text-foreground">
          {new Intl.NumberFormat("en-US", { notation: "compact", maximumFractionDigits: 1 }).format(total)}
        </span>
      </div>
      <div className="flex items-center gap-3">
        <div
          className="relative size-14 shrink-0 rounded-full"
          style={{ background: `conic-gradient(${stops.join(", ")})` }}
        >
          <div className="absolute inset-1.5 rounded-full bg-card" />
        </div>
        <div className="flex flex-1 flex-col gap-1">
          {entries.map((entry) => (
            <div key={entry.key} className="flex items-center justify-between gap-3">
              <span className="flex items-center gap-1.5 text-muted-foreground">
                <span className="size-1.5 shrink-0 rounded-full" style={{ backgroundColor: entry.color }} />
                <span className="truncate">{entry.label}</span>
              </span>
              <span className="shrink-0 font-medium text-foreground">
                {Math.round((entry.value / total) * 100)}%
              </span>
            </div>
          ))}
        </div>
      </div>
    </div>
  )
}

export function Chart() {
  const uid = useId().replace(/[^\w-]/g, "")

  return (
    <ChartContainer config={chartConfig} className="aspect-auto h-[350px] w-full">
      <AreaChart accessibilityLayer data={data}>
        <defs>
          <linearGradient id={`${uid}-fill-0`} x1="0" y1="0" x2="0" y2="1">
            <stop offset="0%" stopColor="var(--chart-1)" stopOpacity={0.85} />
            <stop offset="100%" stopColor="var(--chart-1)" stopOpacity={0.04} />
          </linearGradient>
          <linearGradient id={`${uid}-fill-1`} x1="0" y1="0" x2="0" y2="1">
            <stop offset="0%" stopColor="var(--chart-2)" stopOpacity={0.85} />
            <stop offset="100%" stopColor="var(--chart-2)" stopOpacity={0.04} />
          </linearGradient>
          <linearGradient id={`${uid}-fill-2`} x1="0" y1="0" x2="0" y2="1">
            <stop offset="0%" stopColor="var(--chart-3)" stopOpacity={0.85} />
            <stop offset="100%" stopColor="var(--chart-3)" stopOpacity={0.04} />
          </linearGradient>
        </defs>
        <CartesianGrid vertical={false} strokeDasharray="3 5" />
        <XAxis dataKey="Quarter" tickLine={false} axisLine={false} tickMargin={10} />
        <YAxis tickLine={false} axisLine={false} tickMargin={8} width={40} tickFormatter={(value) => new Intl.NumberFormat("en-US", { notation: "compact", maximumFractionDigits: 1 }).format(value)} />
        <ChartTooltip content={<ChartBreakdownTooltip config={chartConfig} />} />
        <ChartLegend content={<ChartLegendContent />} itemSorter={null} />
        <Area dataKey="Product A" type="natural" fill={`url(#${uid}-fill-0)`} fillOpacity={1} stroke="var(--chart-1)" strokeWidth={2.5} />
        <Area dataKey="Product B" type="natural" fill={`url(#${uid}-fill-1)`} fillOpacity={1} stroke="var(--chart-2)" strokeWidth={2.5} animationBegin={120} />
        <Area dataKey="Product C" type="natural" fill={`url(#${uid}-fill-2)`} fillOpacity={1} stroke="var(--chart-3)" strokeWidth={2.5} animationBegin={240} />
      </AreaChart>
    </ChartContainer>
  )
}

When to use an area chart

Area charts emphasize magnitude over time, and stacking shows how parts add up to a total.

  • Revenue by product over quarters
  • Traffic by source, stacked to a total
  • Share of usage by plan (100% stacked)

Data format

  • Column 1 is the time axis; each other column is an area.
  • Paste CSV, JSON from your API, TSV copied from Excel or Google Sheets, or a Markdown table. The generated component keeps your column names as its data keys.
CSV
Quarter,Product A,Product B,Product C
Q1,12000,8000,5000
Q2,15000,9500,6200
Q3,18000,11000,7800
Q4,22000,13500,9100
Same data as JSON
[
  {"quarter": "Q1", "productA": 12000, "productB": 8000, "productC": 5000},
  {"quarter": "Q2", "productA": 15000, "productB": 9500, "productC": 6200},
  {"quarter": "Q3", "productA": 18000, "productB": 11000, "productC": 7800},
  …
]

Options

Overlap / Stacked / 100%
Overlapping translucent areas, a stacked total, or normalized shares.
Tooltip
Breakdown tooltip or shadcn's default.
Colors
Custom color per series; gradients follow the color.

How to add it to your project

  1. 1. Add the shadcn/ui chart component (it installs Recharts):
    npx shadcn@latest add chart
  2. 2. Paste your data above, pick the options, and copy the generated chart.tsx.
  3. 3. Save it in your project (e.g. components/area-chart.tsx) and render <Chart />, or <Chart data={rows} /> in Data as prop mode.

Area Chart FAQ