Drivn vs shadcn/ui — Chart Component Compared
Drivn vs shadcn/ui React Chart: both wrap Recharts. Drivn adds dot notation, preset Grid and XAxis, and theme colors from CSS tokens, not injected styles.
Neither Drivn nor shadcn/ui draws a single SVG path on its own. Both chart components are thin layers over Recharts: you still write <BarChart>, <Line>, and <Pie> from the recharts package, and the wrapper supplies the container, the series colors, the tooltip, and the legend. Rendering quality is therefore identical. The comparison worth making is how much glue each wrapper asks you to repeat in every chart file, and how its colors follow your theme.
shadcn exposes ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent, and ChartStyle as separate exports. Its tooltip and legend wrappers are the plain Recharts components, so each chart passes content={<ChartTooltipContent />} by hand, and grid and axis settings such as vertical={false} or tickLine={false} get retyped per file. Drivn hangs everything off one root — Chart.Grid, Chart.XAxis, Chart.Tooltip, Chart.Legend — with those defaults already applied, so a bar chart is your series plus four short tags.
The sections below cover the import shape, how each library turns a ChartConfig into CSS variables, the tooltip and legend defaults, and dark mode. Every Drivn snippet is copied from the Chart source the Drivn CLI writes into your project, so what you read here is what you will own after install.
Side-by-side comparison
| Feature | Drivn | shadcn/ui |
|---|---|---|
| Rendering engine | recharts | recharts |
| API style | Dot notation (Chart.Tooltip) | Named exports (ChartTooltip) |
| Grid and X axis | Chart.Grid, Chart.XAxis with defaults | Import from recharts, configure per chart |
| Tooltip content | Default on Chart.Tooltip | Pass content={<ChartTooltipContent />} |
| Hover cursor | Off by default | Recharts default (on) |
| Series colors | Inline --color-{key} style vars | Generated <style> tag (ChartStyle) |
| Dark mode | --chart-1…5 tokens switch on data-theme | config.theme light/dark pairs |
| Tooltip indicators | dot | line | dot | line | dashed |
| License | MIT | MIT |
| Copy-paste install |
API side-by-side
A standard two-series bar chart shows the gap clearly. In shadcn you import five names from the chart file and two more from recharts just for the grid and axis, then repeat the same vertical={false}, tickLine={false}, and axisLine={false} props you wrote in the last chart. The tooltip and legend need their content components passed explicitly, because ChartTooltip and ChartLegend are the unmodified Recharts primitives.
Drivn needs one import from the chart file. Chart.Grid and Chart.XAxis carry the clean defaults, and Chart.Tooltip and Chart.Legend already render the themed content components. The series — <Bar>, <Line>, <Area> — stay plain Recharts in both libraries, so nothing about your data mapping changes. The container also ships with w-full aspect-video min-h-[200px] in its styles.root, so it sizes itself without the className most shadcn examples add. Every prop is listed in the Chart docs.
1 // shadcn/ui — named exports plus raw recharts primitives 2 import { Bar, BarChart, CartesianGrid, XAxis } from 'recharts' 3 import { 4 ChartContainer, 5 ChartLegend, 6 ChartLegendContent, 7 ChartTooltip, 8 ChartTooltipContent, 9 } from '@/components/ui/chart' 10 11 <ChartContainer config={config} className="min-h-[200px] w-full"> 12 <BarChart data={data}> 13 <CartesianGrid vertical={false} /> 14 <XAxis dataKey="month" tickLine={false} axisLine={false} /> 15 <ChartTooltip content={<ChartTooltipContent />} /> 16 <ChartLegend content={<ChartLegendContent />} /> 17 <Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} /> 18 <Bar dataKey="mobile" fill="var(--color-mobile)" radius={4} /> 19 </BarChart> 20 </ChartContainer> 21 22 // Drivn — one import, defaults built in 23 import { Bar, BarChart } from 'recharts' 24 import { Chart, type ChartConfig } from '@/components/ui/chart' 25 26 <Chart config={config}> 27 <BarChart data={data}> 28 <Chart.Grid /> 29 <Chart.XAxis dataKey="month" /> 30 <Chart.Legend /> 31 <Chart.Tooltip /> 32 <Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} /> 33 <Bar dataKey="mobile" fill="var(--color-mobile)" radius={4} /> 34 </BarChart> 35 </Chart>
From ChartConfig to CSS variables
Both libraries let a series reference its color as var(--color-desktop), and both build those variables from the config object. The mechanism is where they part. shadcn generates a unique id with useId, stamps it on the container as data-chart, and renders a ChartStyle component that injects a <style> tag with one selector block per theme. That supports per-series light and dark values, at the cost of a style element per chart and an id to keep in sync.
Drivn does it in four lines inside ChartRoot: filter the config entries that have a color, map each to a --color-{key} pair, and spread the result onto the wrapper div as an inline style. No generated id, no dangerouslySetInnerHTML, nothing to inspect beyond the element itself. The variables are scoped to that one container by the cascade, so two charts on the same page can use the same key with different colors without colliding. The ChartConfig type stays small — label, icon, and color — and the theming guide explains how those colors adapt per theme.
1 // Drivn ChartRoot — verbatim from the Chart source 2 const vars = Object.fromEntries( 3 Object.entries(config) 4 .filter(([, v]) => v.color) 5 .map(([k, v]) => [`--color-${k}`, v.color]) 6 ) as React.CSSProperties 7 8 return ( 9 <Ctx.Provider value={{ config }}> 10 <div style={vars} className={cn(styles.root, className)}> 11 <ResponsiveContainer>{children}</ResponsiveContainer> 12 </div> 13 </Ctx.Provider> 14 )
Tooltip, legend, and axis defaults
The Drivn subcomponents are one-line wrappers that set the props nearly every dashboard chart wants, while still spreading ...props last so any default can be overridden. Chart.Grid turns off vertical lines, Chart.XAxis hides tick marks and the baseline, Chart.Tooltip disables the hover cursor and renders Chart.TooltipContent, and Chart.Legend sits at the bottom with Chart.LegendContent.
The tooltip panel resolves its heading through config[label]?.label, prints numeric values with toLocaleString() in font-mono tabular-nums so columns of numbers line up, and takes two options: hideLabel and indicator set to dot or line. shadcn's tooltip content exposes a wider surface — a dashed indicator, formatter, labelKey, and nameKey. Drivn keeps the shipped prop list short on purpose: the file lives in your repo, so adding a currency formatter is a three-line edit to TooltipContent rather than another prop to learn. The legend renders a config icon in place of the swatch when one is set, which pairs well with the icon-as-component pattern used across Drivn components.
1 // Drivn — verbatim from the Chart source 2 function Grid(props: React.ComponentProps<typeof CartesianGrid>) { 3 return <CartesianGrid vertical={false} {...props} /> 4 } 5 6 function XAxis(props: React.ComponentProps<typeof RechartsXAxis>) { 7 return <RechartsXAxis tickLine={false} axisLine={false} {...props} /> 8 } 9 10 function Tooltip(props: React.ComponentProps<typeof RechartsTooltip>) { 11 return ( 12 <RechartsTooltip 13 cursor={false} 14 content={<TooltipContent />} 15 {...props} 16 /> 17 ) 18 }
Dark mode through chart tokens
shadcn handles dark mode per series: a config entry can take theme: { light, dark } instead of color, and ChartStyle writes a second selector block scoped under the .dark class. That works, but every series in every chart carries two hand-picked values, and the setup assumes class-based theme switching.
Drivn moves the palette into the theme itself. Running npx drivn add chart installs recharts and appends five chart tokens to your globals file — skipped if --chart-1 is already defined. The tokens are a grayscale ramp that inverts under [data-theme="dark"], and they are exposed to Tailwind through @theme inline as --color-chart-1 to --color-chart-5. A config entry then points at a token, color: 'var(--color-chart-1)', and flips automatically with the rest of the site. Rebranding every chart is one edit to five variables, not a sweep through every config object. The installation guide covers where the globals file lives.
1 /* Drivn Chart Tokens — appended by the CLI */ 2 :root { 3 --chart-1: hsl(240 6% 10%); 4 --chart-2: hsl(240 5% 24%); 5 --chart-3: hsl(240 5% 38%); 6 --chart-4: hsl(240 4% 52%); 7 --chart-5: hsl(240 4% 65%); 8 } 9 10 [data-theme="dark"] { 11 --chart-1: hsl(0 0% 98%); 12 --chart-2: hsl(0 0% 82%); 13 --chart-3: hsl(0 0% 66%); 14 --chart-4: hsl(0 0% 50%); 15 --chart-5: hsl(0 0% 35%); 16 }
Install Drivn in one command
Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.
npx drivn@latest createRequires Node 18+. Works with npm, pnpm, and yarn.
Frequently asked questions
No. The CLI installs recharts as the only npm dependency for Chart, and the component file imports nothing else apart from React and the local cn helper. There is no Radix, no cva, and no charting abstraction on top of Recharts — every chart type, animation, and responsive behavior comes straight from the Recharts package you already know.
Swap the imports and rename the wrapper tags. ChartContainer becomes Chart, CartesianGrid and XAxis become Chart.Grid and Chart.XAxis, and the tooltip and legend lose their explicit content props. Your <Bar>, <Line>, and <Area> series stay exactly as written. Config entries that use theme pairs convert to a single color pointing at a chart token.
Yes, through tokens instead of per-series pairs. Point the config at var(--color-chart-1) and change the --chart-1 value under :root and [data-theme="dark"]. Any CSS color works in color, so you can also define your own custom properties for a brand palette and reference those — the chart reads whatever value the cascade resolves.
Because the grey hover band Recharts draws behind the active bar competes with the tooltip panel for attention, and most dashboard designs drop it. Chart.Tooltip passes cursor={false} before spreading your props, so adding cursor back on a single chart is one prop. The same override pattern applies to every other default the wrapper sets.
Yes. The file starts with 'use client' because Recharts measures its container and attaches pointer handlers in the browser. You can render it from a Server Component page and pass data as props — fetch on the server, hand the rows to the chart, and Next.js draws the client boundary at the import.