About this course
<p>Charts look like a small task on a ticket. Then you open the Recharts docs and remember how much setup every chart needs: a config object for labels and colors, axes, a tooltip, a legend, colors that work in dark mode, and a container that resizes properly. Most of us copy all of that from the last project and rename the keys.</p>
<p>The shadcn/ui chart component removes part of this work. It wraps Recharts with theme-aware colors and tooltips. But you still write every chart by hand.</p>
<p>So I built ChartCN, a free and open-source <a href="https://www.shadcndeck.com/chartcn">shadcn chart generator</a>. You paste your data, pick a chart, and copy one TSX file that depends only on the shadcn/ui chart component and Recharts. It's MIT licensed, and there's no account or extra package.</p>
<p>In this tutorial, you'll learn how to add a revenue-versus-expenses bar chart to a fresh Next.js app. We'll walk through what each part of the generated code does, and then connect the chart to data loaded in a Server Component. The tool saves you the typing, but Steps 5 and 6 apply to any shadcn/ui chart, whether you generate it or write it yourself.</p>
<p><strong>Prerequisites:</strong> You should be comfortable with React and have Node.js installed. Basic familiarity with the Next.js App Router helps, but every step is explained.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-what-youll-build">What You'll Build</a></p>
</li>
<li><p><a href="#heading-step-1-create-a-nextjs-app-and-initialize-shadcnui">Step 1: Create a Next.js App and Initialize shadcn/ui</a></p>
</li>
<li><p><a href="#heading-step-2-add-the-shadcnui-chart-component">Step 2: Add the shadcn/ui Chart Component</a></p>
</li>
<li><p><a href="#heading-step-3-generate-the-chart-component-in-chartcn">Step 3: Generate the Chart Component in ChartCN</a></p>
</li>
<li><p><a href="#heading-step-4-add-the-chart-to-your-page">Step 4: Add the Chart to Your Page</a></p>
</li>
<li><p><a href="#heading-step-5-understand-the-generated-code">Step 5: Understand the Generated Code</a></p>
</li>
<li><p><a href="#heading-step-6-pass-live-data-as-a-prop">Step 6: Pass Live Data as a Prop</a></p>
</li>
<li><p><a href="#heading-common-issues-and-how-to-fix-them">Common Issues and How to Fix Them</a></p>
</li>
<li><p><a href="#heading-summary">Summary</a></p>
</li>
</ul>
<h2 id="heading-what-youll-build">What You'll Build</h2>
<p>By the end, you'll have two pages:</p>
<ul>
<li><p><code>/</code> shows a grouped bar chart with the data written directly in the component. This is the fastest way to get a chart on screen.</p>
</li>
<li><p><code>/live</code> shows the same chart, but the data comes from an async function running on the server. This is the version you'd use with a real database or API.</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/6a4ea012258c41204fefcf8b/546e7105-3d43-4bd0-a064-a877fffe930a.png" alt="Shadcn Charts - Finished Bar Chart" style="display: block;" width="600" height="400" loading="lazy">
<p>The chart has gradient bars, compact axis labels (40K instead of 40000), a legend, and a tooltip that shows the month's total and each series' share of it.</p>
<h2 id="heading-step-1-create-a-nextjs-app-and-initialize-shadcnui">Step 1: Create a Next.js App and Initialize shadcn/ui</h2>
<p>Create a new app and move into it:</p>
<pre><code class="language-bash">npx create-next-app@latest my-charts-app
cd my-charts-app
</code></pre>
<p>Accept the recommended defaults. You need TypeScript, Tailwind CSS, and the App Router.</p>
<p>Next, initialize shadcn/ui:</p>
<pre><code class="language-bash">npx shadcn@latest init
</code></pre>
<p>This command does three things you'll rely on later:</p>
<ol>
<li><p>It creates <code>components.json</code>, which tells the shadcn/ui CLI where to put components.</p>
</li>
<li><p>It adds <code>lib/utils.ts</code> with the <code>cn()</code> class-merging helper.</p>
</li>
<li><p>It writes theme variables into <code>app/globals.css</code>, including five chart colors: <code>--chart-1</code> through <code>--chart-5</code>, with separate values for light and dark mode.</p>
</li>
</ol>
<p>Those five chart variables matter. Every generated chart uses them, so changing a chart color across your whole app means editing one line of CSS.</p>
<h2 id="heading-step-2-add-the-shadcnui-chart-component">Step 2: Add the shadcn/ui Chart Component</h2>
<p>Now add the chart component:</p>
<pre><code class="language-bash">npx shadcn@latest add chart
</code></pre>
<p>This installs <code>recharts</code> and creates <code>components/ui/chart.tsx</code>. That file exports the building blocks you'll see in the generated code:</p>
<ul>
<li><p><code>ChartContainer</code> wraps Recharts' <code>ResponsiveContainer</code>, so the chart fills its parent.</p>
</li>
<li><p><code>ChartConfig</code> is the type for the object that maps each data key to a label and a color.</p>
</li>
<li><p><code>ChartTooltip</code>, <code>ChartTooltipContent</code>, <code>ChartLegend</code>, and <code>ChartLegendContent</code> are styled versions of the Recharts tooltip and legend.</p>
</li>
</ul>
<p>Before moving on, check which Recharts version was installed:</p>
<pre><code class="language-bash">npm ls recharts
</code></pre>
<p>The code ChartCN generates targets <strong>Recharts 3</strong>. If you see a 2.x version, upgrade it:</p>
<pre><code class="language-bash">npm install recharts@latest
</code></pre>
<p><strong>Takeaway:</strong> the shadcn/ui chart component is a thin layer over Recharts. It doesn't replace Recharts. It gives Recharts your theme.</p>
<h2 id="heading-step-3-generate-the-chart-component-in-chartcn">Step 3: Generate the Chart Component in ChartCN</h2>
<p>Open the <a href="https://www.shadcndeck.com/chartcn/charts/bar">ChartCN bar chart page</a>. The data panel has three tabs: <strong>Paste data</strong>, <strong>Upload file</strong>, and <strong>Edit table</strong>. Paste this CSV into the <strong>Paste data</strong> tab:</p>
<pre><code class="language-plaintext">Month,Revenue,Expenses
Jan,42000,31000
Feb,58000,34000
Mar,51000,29000
Apr,67000,38000
May,72000,41000
Jun,69000,37000
</code></pre>
<p>ChartCN treats the first column as the category axis (the x-axis for a bar chart) and every other column as a numeric series. JSON, TSV, and Markdown tables also work, and the format is detected automatically.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a4ea012258c41204fefcf8b/997a85da-f44d-4e88-8ffe-120ea66b8884.png" alt="Shadcn Charts - Bar Chart" style="display: block;" width="600" height="400" loading="lazy">
<p>Above the preview, set these options:</p>
<ul>
<li><p><strong>Layout:</strong> Grouped</p>
</li>
<li><p><strong>Tooltip:</strong> Breakdown</p>
</li>
<li><p><strong>Code:</strong> Inline data</p>
</li>
</ul>
<p>Then click <strong>Copy component</strong> in the <code>chart.tsx</code> panel below the preview.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a4ea012258c41204fefcf8b/3317c6c9-e4f4-4b59-ad19-2076728048cd.png" alt="Shadcn Charts - ChartCn Copy component" style="display: block;" width="600" height="400" loading="lazy">
<h2 id="heading-step-4-add-the-chart-to-your-page">Step 4: Add the Chart to Your Page</h2>
<p>Create a new file at <code>components/revenue-chart.tsx</code> and paste the copied code into it.</p>
<p>Don't save it as <code>components/ui/chart.tsx</code>. ChartCN labels its output <code>chart.tsx</code>, but that path already holds the <a href="https://www.shadcndeck.com/blog/shadcn-components">shadcn component</a> from Step 2, and overwriting it breaks every chart in your app.</p>
<p>The generated component is always exported as <code>Chart</code>, so give it a clearer name when you import it. Replace <code>app/page.tsx</code> with this:</p>
<pre><code class="language-tsx">// app/page.tsx
import { Chart as RevenueChart } from "@/components/revenue-chart"
export default function Home() {
return (
<main className="mx-auto max-w-3xl p-8">
<h1 className="mb-6 text-2xl font-semibold">Revenue vs. expenses</h1>
<RevenueChart />
</main>
)
}
</code></pre>
<p>Run <code>npm run dev</code> and open <code>http://localhost:3000</code>. You should see the chart.</p>
<p><strong>Takeaway:</strong> a generated chart is just a component in your project. There's nothing to configure and no package to keep in sync.</p>
<h2 id="heading-step-5-understand-the-generated-code">Step 5: Understand the Generated Code</h2>
<p>You own this file now, so it's worth knowing what each part does. Here are the important pieces, trimmed for length.</p>
<p>The file starts with a directive and its imports:</p>
<pre><code class="language-tsx">// components/revenue-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"
</code></pre>
<p>Recharts measures the DOM and handles mouse events, so the chart must be a Client Component. <code>app/page.tsx</code> can stay a Server Component because it only renders the chart.</p>
<p>Next come the data and the config:</p>
<pre><code class="language-tsx">const data = [
{ Month: "Jan", Revenue: 42000, Expenses: 31000 },
{ Month: "Feb", Revenue: 58000, Expenses: 34000 },
// ...
]
const chartConfig = {
Revenue: { label: "Revenue", color: "var(--chart-1)" },
Expenses: { label: "Expenses", color: "var(--chart-2)" },
} satisfies ChartConfig
</code></pre>
<p>The keys in <code>chartConfig</code> must match the keys in your data. <code>ChartContainer</code> reads this config and creates a CSS variable for each key, scoped to this chart: <code>--color-Revenue</code> and <code>--color-Expenses</code>. Each one points at a theme color, so the chart switches colors in dark mode on its own.</p>
<p>The chart itself uses those variables:</p>
<pre><code class="language-tsx"><ChartContainer config={chartConfig} className="aspect-auto h-[350px] w-full">
<BarChart accessibilityLayer data={data} barCategoryGap="30%" barGap={4}>
{/* ...gradient <defs>, grid, axes, tooltip, legend... */}
<Bar
dataKey="Revenue"
fill="var(--color-Revenue)"
shape={(props) => <Rectangle {...props} fill={`url(#${uid}-fill-0)`} />}
radius={[6, 6, 0, 0]}
maxBarSize={36}
/>
</BarChart>
</ChartContainer>
</code></pre>
<p>A few details are worth knowing:</p>
<ul>
<li><p><strong>Height</strong> is set by <code>h-[350px]</code> on <code>ChartContainer</code>. Change it there.</p>
</li>
<li><p><strong>The gradient</strong> is drawn by the <code>shape</code> prop, while <code>fill</code> stays a solid color. That's why the legend and tooltip dots show a clean color instead of a gradient.</p>
</li>
<li><p><code>uid</code> comes from <code>useId()</code>, so the gradient IDs stay unique if you render two charts on the same page.</p>
</li>
</ul>
<p>The file also includes a <code>ChartBreakdownTooltip</code> component, about 70 lines, that shows the total and each series' percentage. If you'd rather use the standard <a href="https://www.shadcndeck.com/blog/shadcn-tooltip-component">shadcn tooltip</a>, pick <strong>Tooltip: Simple</strong> in ChartCN before copying. The whole file then drops from about 120 lines to about 50.</p>
<p><strong>Takeaway:</strong> in shadcn/ui charts, colors flow from theme variables through <code>chartConfig</code> into <code>--color-<key></code> variables. Once you understand that path, you can edit any chart by hand.</p>
<h2 id="heading-step-6-pass-live-data-as-a-prop">Step 6: Pass Live Data as a Prop</h2>
<p>Hardcoded data is fine for a demo. For real data, go back to ChartCN, switch <strong>Code</strong> to <strong>Data as prop</strong>, and copy again. Save this version as <code>components/revenue-chart-live.tsx</code>.</p>
<p>The chart markup is identical. Only the top of the file changes: the <code>data</code> array is replaced by a type, and the component accepts <code>data</code> as a prop:</p>
<pre><code class="language-tsx">// components/revenue-chart-live.tsx
export type ChartRow = { Month: string; Revenue: number | null; Expenses: number | null }
export interface ChartProps {
data: ChartRow[]
}
// ...chartConfig is unchanged...
export function Chart({ data }: ChartProps) {
// ...same JSX as before
}
</code></pre>
<p>The series are typed as <code>number | null</code> because real data has gaps. A <code>null</code> value renders as a missing bar, not as a bar of zero.</p>
<p>Now create a function that loads the data on the server:</p>
<pre><code class="language-ts">// lib/get-revenue.ts
import type { ChartRow } from "@/components/revenue-chart-live"
export async function getRevenue(): Promise<ChartRow[]> {
// Replace this with your database query or API call.
// For example: const res = await fetch("https://api.example.com/revenue")
return [
{ 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: null },
]
}
</code></pre>
<p>Then call it from a Server Component page:</p>
<pre><code class="language-tsx">// app/live/page.tsx
import { Chart as RevenueChart } from "@/components/revenue-chart-live"
import { getRevenue } from "@/lib/get-revenue"
export default async function LivePage() {
const data = await getRevenue()
return (
<main className="mx-auto max-w-3xl p-8">
<h1 className="mb-6 text-2xl font-semibold">Revenue vs. expenses</h1>
<RevenueChart data={data} />
</main>
)
}
</code></pre>
<p>Open <code>http://localhost:3000/live</code>. May shows a Revenue bar with no Expenses bar next to it, because that value is <code>null</code>.</p>
<p>The page fetches on the server and sends only the rows to the client component. Your database credentials and API keys stay on the server. When you swap in a real <code>fetch</code> or database call, check the <a href="https://nextjs.org/docs/app/getting-started/fetching-data">Next.js data fetching docs</a> to control how often the data refreshes.</p>
<p><strong>Takeaway:</strong> keep data loading in a Server Component and rendering in a Client Component. The exported <code>ChartRow</code> type makes TypeScript check that your data matches what the chart expects.</p>
<h2 id="heading-common-issues-and-how-to-fix-them">Common Issues and How to Fix Them</h2>
<p><strong>Type errors such as "Property 'itemSorter' does not exist".</strong> Your project has Recharts 2 installed. Run <code>npm install recharts@latest</code> to upgrade to Recharts 3.</p>
<p><strong>The bars are black and the legend dots are missing.</strong> Your <code>app/globals.css</code> doesn't define <code>--chart-1</code> through <code>--chart-5</code>. Run <code>npx shadcn@latest init</code> again, or copy the chart variables from the shadcn/ui theming docs.</p>
<p><strong>Every chart stopped working after you pasted one.</strong> You probably saved the generated file over <code>components/ui/chart.tsx</code>. Restore it with <code>npx shadcn@latest add chart --overwrite</code> and save your chart under a different name.</p>
<p><strong>Two charts on one page have the same name.</strong> Every generated component is exported as <code>Chart</code>. Rename it when you import it, for example <code>import { Chart as SignupsChart } from "@/components/signups-chart"</code>.</p>
<h2 id="heading-summary">Summary</h2>
<ol>
<li><p><strong>The shadcn/ui chart component themes Recharts.</strong> It doesn't replace it, and ChartCN's output requires Recharts 3.</p>
</li>
<li><p><strong>Colors flow from CSS variables.</strong> <code>--chart-1</code> becomes <code>--color-Revenue</code> through <code>chartConfig</code>, so dark mode works without extra code.</p>
</li>
<li><p><strong>Save generated charts under their own names.</strong> <code>components/ui/chart.tsx</code> belongs to shadcn/ui.</p>
</li>
<li><p><strong>Use Data as prop for real data.</strong> Load it in a Server Component and pass the typed rows to the chart.</p>
</li>
<li><p><code>null</code> <strong>means "no data".</strong> Gaps render as gaps, not as zeros.</p>
</li>
</ol>
<p>Because ChartCN is a <a href="https://www.shadcndeck.com/chartcn">shadcn chart generator</a> rather than a component library, there is no ChartCN package to install or keep updated. You can browse all 13 <a href="https://www.shadcndeck.com/chartcn/charts">shadcn charts</a> it supports, including line, area, pie, radar, KPI cards, waterfall, and a heatmap. They all follow the same paste, pick, and copy workflow you used here.</p>
<p>The source is on <a href="https://github.com/ShadcnDeck/chartcn-shadcn-chart-generator">GitHub</a> under the MIT license. If it's useful, a star helps other developers find it.</p>