Shadcn Bar Chart Generator

Turn a CSV or JSON array into a shadcn/ui bar chart with rounded gradient bars, a breakdown tooltip, and grouped or stacked layouts, then copy one self-contained React component.

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

CSV
Layout
Tooltip
Code
+64%Growth
Revenue359K
Expenses210K
Image
chart.tsx
"use client"

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

const data = [
  { Month: "Jan", Revenue: 42000, Expenses: 31000 },
  { Month: "Feb", Revenue: 58000, Expenses: 34000 },
  { Month: "Mar", Revenue: 51000, Expenses: 29000 },
  { Month: "Apr", Revenue: 67000, Expenses: 38000 },
  { Month: "May", Revenue: 72000, Expenses: 41000 },
  { Month: "Jun", Revenue: 69000, Expenses: 37000 },
]

const chartConfig = {
  Revenue: { label: "Revenue", color: "var(--chart-1)" },
  Expenses: { label: "Expenses", color: "var(--chart-2)" },
} 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">
      <BarChart accessibilityLayer data={data} barCategoryGap="30%" barGap={4}>
        <defs>
          <linearGradient id={`${uid}-fill-0`} x1="0" y1="0" x2="0" y2="1">
            <stop offset="0%" stopColor="var(--color-Revenue)" stopOpacity={1} />
            <stop offset="100%" stopColor="var(--color-Revenue)" stopOpacity={0.55} />
          </linearGradient>
          <linearGradient id={`${uid}-fill-1`} x1="0" y1="0" x2="0" y2="1">
            <stop offset="0%" stopColor="var(--color-Expenses)" stopOpacity={1} />
            <stop offset="100%" stopColor="var(--color-Expenses)" stopOpacity={0.55} />
          </linearGradient>
        </defs>
        <CartesianGrid vertical={false} strokeDasharray="3 5" />
        <XAxis dataKey="Month" 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 cursor={{ fill: "var(--muted)", opacity: 0.6 }} content={<ChartBreakdownTooltip config={chartConfig} />} />
        <ChartLegend content={<ChartLegendContent />} itemSorter={null} />
        <Bar dataKey="Revenue" fill="var(--color-Revenue)" shape={(props) => <Rectangle {...props} fill={`url(#${uid}-fill-0)`} />} radius={[6, 6, 0, 0]} maxBarSize={36} />
        <Bar dataKey="Expenses" fill="var(--color-Expenses)" shape={(props) => <Rectangle {...props} fill={`url(#${uid}-fill-1)`} />} radius={[6, 6, 0, 0]} maxBarSize={36} animationBegin={120} />
      </BarChart>
    </ChartContainer>
  )
}

When to use a bar chart

Use a bar chart to compare values across a handful of categories or periods, where the exact height of each bar matters.

  • Monthly revenue vs. expenses
  • Signups per channel or campaign
  • Stacked composition, e.g. revenue by plan per month
  • 100% stacked shares when proportions matter more than totals

Data format

  • Column 1 is the category axis (months, channels, dates); every other column becomes a series of bars.
  • Dates like 2024-01-01 get short date ticks automatically.
  • 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
Month,Revenue,Expenses
Jan,42000,31000
Feb,58000,34000
Mar,51000,29000
Apr,67000,38000
May,72000,41000
Jun,69000,37000
Same data as JSON
[
  {"month": "Jan", "revenue": 42000, "expenses": 31000},
  {"month": "Feb", "revenue": 58000, "expenses": 34000},
  {"month": "Mar", "revenue": 51000, "expenses": 29000},
  …
]

Options

Grouped / Stacked / 100%
Side-by-side bars, stacked totals, or each bar normalized to 100%.
Tooltip
A breakdown tooltip with each series' share of the bar, or shadcn's default tooltip.
Colors
Pick any color per series; the copied chartConfig updates to match.
Inline data / Data as prop
Bake your data into the component or accept it as a typed data prop.

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/bar-chart.tsx) and render <Chart />, or <Chart data={rows} /> in Data as prop mode.

Bar Chart FAQ