Docs

Installation

One command per component. The shadcn CLI copies the component and everything it imports into your project.

Requirements

  • React 19 (React 18 isn't supported), in Next.js or Vite.
  • A project set up for shadcn: npx shadcn@latest init creates components.json. Custom aliases and a Tailwind prefix are respected.
  • Recharts 3.10 or later, or ECharts 6.1 or later. The CLI installs or updates the engine the build uses, and only that one.
  • Tailwind isn't required by the charts: the theme brings its own stylesheet. If your shadcn tokens are HSL channels (the Tailwind 3 style, like 222.2 84% 4.9%), add class="bc-hsl-tokens" to <html>.

Install

npx shadcn@latest add https://beautifulcharts.dev/r/bar-chart.json

The CLI copies the chart and the shared files it imports into your components folder. Installing a second chart reuses the shared files.

components/beautiful-charts/
  core/                 engine-free model, formatting, keyboard, sound (MIT)
  themes/instrument/
    styles.tsx          the theme's stylesheet, injected once
    frame.tsx           card, header, legend, table and states
    recharts/bar-chart.tsx
    echarts/bar-chart.tsx   (only if you install the ECharts build)
  LICENSE.txt

Use it

Charts are client components. Import them in a file that starts with "use client" whenever you pass a function (a formatter, a callback or tooltip content), because functions can't cross from a Server Component. The first render happens on the server, and the card reserves its height while the plot measures itself.

"use client";

import { BarChart } from "@/components/beautiful-charts/themes/instrument/recharts/bar-chart";
import type { ChartConfig } from "@/components/beautiful-charts/core/contracts";

const data = [
  { month: "Jan", signups: 186, trials: 80 },
  { month: "Feb", signups: 305, trials: 200 },
  { month: "Mar", signups: 237, trials: 120 },
  { month: "Apr", signups: 73, trials: 190 },
  { month: "May", signups: 209, trials: 130 },
  { month: "Jun", signups: 214, trials: 140 },
];

const config = {
  signups: { label: "Sign-ups", color: "var(--chart-1)" },
  trials: { label: "Trials", color: "var(--chart-2)" },
} satisfies ChartConfig;

export function SignupsByMonth() {
  return (
    <BarChart
      data={data}
      xKey="month"
      config={config}
      title="Sign-ups and trials"
      description="January to June"
      stack="stacked"
      valueFormatter={(value) => value.toLocaleString("en-US")}
      onSelectedChange={(selection) => console.log(selection?.datum.month, selection?.seriesKey)}
    />
  );
}

Install names

Each component installs by its name: bar-chart, area-chart, mrr-bridge. The default build uses Recharts (or no engine, for the heatmap, KPI stat and gauge). Add -echarts for the Apache ECharts build, for example bar-chart-echarts. The longer names from before (instrument-bar-chart-recharts) still work and install the same files.

The heatmap's builds

Recharts has no heatmap, so the default build is plain SVG and needs no chart engine. For large grids (hundreds of cells and more), install the ECharts build, which draws on a canvas. Both take the same props.

npx shadcn@latest add https://beautifulcharts.dev/r/heatmap.json
npx shadcn@latest add https://beautifulcharts.dev/r/heatmap-echarts.json

Pro themes

Riso and Clay install from a private registry with a token from your account, once you have the pack. Set it up once per project, then install any Pro item with the command on its page. Each Pro component takes exactly the props of its Instrument version, so moving between themes changes the import path and nothing else.

// components.json (add next to what's there)
{
  "registries": {
    "@beautiful-charts-pro": {
      "url": "https://beautifulcharts.dev/api/pro/r/{name}.json",
      "headers": { "Authorization": "Bearer ${BC_PRO_TOKEN}" }
    }
  }
}

# .env.local (stays out of Git)
BC_PRO_TOKEN=<the token from your account>

If an install fails: 401 means the token is missing or revoked, 403 means your licence doesn't include that item, 404 means the name is wrong, and 429 means too many requests in an hour.

Updating

The installed files are your source, so updating is a reviewed change, not a silent package bump. Commit first, then preview and apply:

git add -A && git commit -m "before chart update"
npx shadcn@latest add https://beautifulcharts.dev/r/bar-chart.json --dry-run
npx shadcn@latest add https://beautifulcharts.dev/r/bar-chart.json --diff components/beautiful-charts/core/cartesian.ts
npx shadcn@latest add https://beautifulcharts.dev/r/bar-chart.json --overwrite
git diff

--dry-run lists what would change, --diff shows one file's changes, and --overwrite writes the new version. Charts from one release share identical copies of the shared files, so update the charts you use together.

Troubleshooting

  • “Functions cannot be passed directly to Client Components”: move the chart into a file that starts with "use client".
  • The chart has no height: set height (the plot height in px), and give a grid child min-width: 0 so the chart can shrink.
  • All series look the same colour: check that each key in config matches a field in your rows. In development, the console warns about invalid colours.
  • The CLI can't find your alias: finish npx shadcn@latest init first, so components.json exists.
  • The CLI asks to overwrite a shared file: you've edited it locally. Compare before accepting.

From the recipe charts

The earlier recipe charts still install from their old addresses. Moving one to the current components:

Recipe chartsNow
series={[{ key, label, color, stackId }]}config={{ key: { label, color, stackId } }} (shadcn ChartConfig), plus series={[...keys]} to choose and order them
showLegend, legend on/off booleanslegend={{ position, align, shape, action }} or legend={false}
showBrush and onRangeChange({ startIndex, endIndex })brush={{ range, defaultRange, onRangeChange }}; ranges are indexes into your data on both engines
onSelect({ dataIndex, datum }) or the native ECharts payloadonSelectedChange(selection): the same typed Selection on both engines
optionOverrides (shallow)option (deep-merged), or a function of the generated option; onReady for the instance
isLoadingloading, plus error, and emptyState / loadingState / errorState