What are Tailwind charts, and can Tailwind draw them on its own?
No. Tailwind is a utility-first CSS framework, so it handles the card, the labels and the layout around a chart. The lines, slices and axes always come from a library or from hand-written markup.
The term covers any chart whose colors, spacing, type and dark mode behavior come from utility classes and theme variables, not from a separate chart theme file.
Chart.js or ApexCharts can draw the chart while Tailwind styles everything around it. shadcn/ui charts and Tremor skip some of that setup by shipping chart code that already reads Tailwind tokens. And if the chart is simple enough, divs sized with width and height utilities do the job with no library at all.
That last route stops at bar charts and progress bars built from width utilities. Line, pie and radar charts need SVG or canvas rendering.
Colors are where most projects trip. Libraries read them from an options object or a CSS variable, not from classes on the chart marks, so the Tailwind palette has to be passed in by hand.
Which chart library fits a Tailwind project best?
Your framework narrows this fast. Tremor and shadcn/ui charts are React only, so anything outside React rules out two of the five straight away.
| Library | Renderer | Frameworks | Tailwind styling channel |
|---|
| Tremor | SVG (Recharts) | React | Class tokens and color props |
| shadcn/ui charts | SVG (Recharts) | React, Next.js | CSS variables from a config object |
| Flowbite charts | SVG (ApexCharts) | Plain HTML first | Utility classes plus ApexCharts options |
| Chart.js | Canvas | Any, with React and Vue wrappers | Options object only |
| D3.js and visx | SVG (built by you) | Any, visx is React | Classes on every mark |
Weight differs more than the table shows. TanStack publishes a competing charting library, so read its benchmark as a rough gap, not a promise.
Its comparison (baseline date 2026-10-14) measures minified, gzip-compressed bundles and lists Chart.js at 45.78 to 59.61 kB across its test charts. Recharts lands at 156.76 to 172.30 kB, or 97.24 to 112.60 kB with React externalized.
Both shadcn/ui charts and Tremor sit on Recharts, so they inherit that weight.
Tremor
Vercel acquired Tremor on January 22, 2025, and made all Tremor products, including Tremor Blocks, free and open source (Vercel announcement).
The @tremor/react package builds on React, Tailwind CSS, Headless UI and Recharts. Its cards, KPI blocks and charts suit a Tailwind dashboard where everything should share one look.
Series colors take Tailwind palette names such as indigo and rose, and you get area, bar, line and donut charts plus sparklines. The KPI cards and charts share one design language, which is the real draw.
It only works with React. There’s also a version question. Its installation docs ask for Tailwind CSS v3.4+, the last stable @tremor/react release is 3.18.7 from January 2025, and most of its dashboard templates still list Tailwind CSS v3.4 in their stack as of October 2026. Confirm Tailwind v4 support before starting a new project.
shadcn/ui charts
Under the hood this is Recharts with a few Tailwind-aware helpers on top.
The docs state that the component does not wrap Recharts, so you write the Recharts components yourself. The chart.tsx file the CLI adds supplies ChartContainer, tooltip and legend helpers, and colors that follow CSS variables.
The source lives in your repo and the colors follow your theme variables, so there is no wrapper API to learn on top of Recharts. You pay for that in bundle weight, and every Recharts upgrade is yours to apply. It’s React only as well.
Flowbite charts
Flowbite’s chart components are MIT-licensed and sit on ApexCharts, which has its own license terms (see below), with Tailwind classes handling the card, text and spacing around them.
- Area, line, bar, column, pie and radial examples are ready to paste, and dark mode is supported by default per the Flowbite docs.
- It runs from a CDN script tag with no build step.
- Tailwind CSS, Flowbite and ApexCharts all have to be installed, which is more setup than it sounds like for a “just paste it” kit.
- Svelte projects use @flowbite-svelte-plugins/chart, because the flowbite-svelte docs say the older Chart component will be deprecated.
Flowbite’s changelog records the move to Tailwind v4 in 3.0.0, and newer entries shift color and dark mode handling to CSS variables.
Check which Flowbite and ApexCharts versions a copied example targets, because Flowbite’s chart docs still load apexcharts@3.46.0. ApexCharts releases up to 5.0.0 were MIT. Since 5.2.0 it uses a dual license: the free Community License covers organizations with less than US$2 million in annual revenue, budget or funding, and a paid commercial license applies at or above that, so read the LICENSE file before commercial use.
Chart.js
Chart.js draws on a canvas element, so Tailwind classes never reach the bars or lines. Tailwind styles the wrapper, and the colors, fonts and grid all come from the options object.
That has two annoying side effects. Palette values get copied from Tailwind by hand, so a theme change means editing JavaScript. And responsive sizing depends on a dedicated, relatively positioned wrapper element.
It does have an MIT license, community wrappers for React and Vue, and a lighter bundle than the Recharts-based options. Pick it for dense data and framework flexibility, not for class-level styling.
D3.js and visx for custom builds
Groww’s engineering blog reports replacing a Highcharts bundle of about 96 KB gzipped with an in-house library built on visx, which came in at 23.6 KB gzipped. That is roughly a 75% cut, which the blog itself rounds to about 80%.
D3.js and visx are toolkits, not chart libraries. Every mark is an SVG element you place yourself, so fill and stroke utilities apply to bars, lines and ticks without any workaround.
You get full control and a bundle you shape yourself. The cost is that axes, tooltips, legends and keyboard access are all yours to build, which is why Groww ended up writing a library.
SVG, canvas or plain HTML: which rendering approach works with Tailwind classes?
SVG and plain HTML elements take Tailwind classes directly. Canvas does not, because it paints pixels that no class can reach.
With SVG, every bar, slice and tick is a DOM element, so fill, stroke and dark: utilities apply to it. Canvas is a single element holding a bitmap, which means colors travel in as JavaScript values. Plain HTML elements, meaning divs sized by utilities, get full Tailwind support but no real axes or scales.
Recharts and ApexCharts render SVG, which covers Tremor, shadcn/ui charts and Flowbite. Chart.js renders canvas.
SVG output still does not mean you can put a class on every mark. The library writes the markup, so shadcn/ui’s chart component reaches in with arbitrary variants such as [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground.
Hand-written inline SVG in HTML avoids that detour, because every element is yours and takes fill and stroke utilities directly.
A decent rule of thumb: SVG while the chart stays under a few thousand points, canvas beyond that, plain HTML for a handful of bars.
ApexCharts’ docs describe the SVG node ceiling as typically several thousand points. Its canvas renderer (added in ApexCharts 6.0 and an opt-in import since 7.0) switches the series layer to canvas above 8,000 points by default when the renderer option is set to auto. Without the renderer-canvas import, charts stay on SVG, and axes, grid, tooltips and exports remain SVG either way.
How to build a CSS-only bar chart with utility classes
A flex row, bottom alignment and one div per bar is all it takes. This is the same idea behind CSS-only charts, written in utilities.
<div class="flex h-40 items-end gap-2">
<div class="w-8 rounded-t bg-sky-500" style="height: 40%"></div>
<div class="w-8 rounded-t bg-sky-500" style="height: 75%"></div>
<div class="w-8 rounded-t bg-sky-500" style="height: 55%"></div>
</div>
Heights go in inline styles on purpose. Tailwind scans source files as plain text and cannot read string interpolation, so a class name built from data, such as h-[${value}%], is never generated (Tailwind docs, detecting classes in source files).
This version has no axes or scales and no hover tooltips. Any text alternative is whatever you write by hand.
How to add a chart to a Tailwind project in React, Next.js, Vue or plain HTML
The steps barely change between frameworks. Only the package does. The sizing rule stays the same everywhere: the chart’s container needs a height the browser can measure.
- Set up Tailwind CSS and confirm utilities render.
- Install the chart package: Chart.js, ApexCharts, Recharts, Tremor or shadcn/ui charts.
- Wrap the chart in a card with an explicit height utility such as h-64.
- Pass the data, then map each series to a color.
- Resize the browser window and check the chart follows its container.
Tailwind owns the card, meaning padding, border, background and height, and a ready-made Tailwind card covers most of that.
React and Next.js
Tailwind has to work first. Setting up Tailwind CSS in a React project is a separate step that takes a few minutes.
- For shadcn/ui charts, run the CLI add command for chart, then give ChartContainer a min-h value, which the docs call required for responsiveness.
- Tremor needs @tremor/react installed and the “use client” directive at the top of the file, as its README example does.
- With Recharts directly, the parent needs a defined size. The responsive prop arrived in Recharts 3.3, and older versions use ResponsiveContainer.
In Next.js, a file that renders chart components goes behind the “use client” directive. The Next.js docs reserve it for components that need state or event handlers, effects, browser APIs or custom hooks, and charts need the browser.
A flex-1 parent is the classic Tailwind trap. A Recharts issue from May 2024 (Recharts 2.12.6) reports a chart that never appeared inside a flex-1 container and only rendered once the nearest parent got an explicit h-[230px].
Vue
Chart.js through the vue-chartjs wrapper is the usual route in Vue.
Install vue-chartjs and chart.js together, register the chart parts you use, then render components such as Bar with data and options props. The vue-chartjs README lists Chart.js v4 support.
Chart.js needs a wrapper that is relatively positioned and dedicated to the canvas, according to its responsive docs. In Tailwind that is a div with relative and a height utility such as h-64, plus maintainAspectRatio set to false.
Plain HTML
Flowbite is the shortest route to a chart in plain HTML. Its docs list Tailwind CSS, Flowbite and ApexCharts as prerequisites, and ApexCharts loads from a CDN script tag.
<div id="area-chart" class="h-64 w-full"></div>
<script>
const chart = new ApexCharts(document.getElementById("area-chart"), options);
chart.render();
</script>
Updating later is one call, updateSeries on the chart object, per the Flowbite docs. Chart.js loads from a script tag the same way, inside the same relative wrapper.
How to style chart colors with Tailwind v4 theme variables
Define the palette once as CSS variables and publish it to Tailwind with @theme. After that, each library needs the variable in its own form. SVG marks take a utility class. Canvas charts need a computed value, and the config-driven ones, shadcn/ui and Flowbite among them, want a variable name.
Tailwind v4 generates utilities from theme variable namespaces. A –color-chart-1 variable therefore produces bg-chart-1, fill-chart-1 and stroke-chart-1, which is what SVG marks need.
@import "tailwindcss";
:root {
--chart-1: oklch(0.65 0.19 255);
--chart-2: oklch(0.7 0.15 160);
}
@theme inline {
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
}
The inline keyword matters when a theme variable points at another variable, and Tailwind’s theme docs recommend it for that case. Point these variables at the tokens your design system already defines, and charts stay in step with buttons and cards.
- Hand-built SVG from D3.js or visx takes fill-chart-1 and stroke-chart-1 classes straight on the marks.
- shadcn/ui charts use five variables, –chart-1 to –chart-5, in the global stylesheet, with ChartConfig mapping each data key to a label and a color.
- Flowbite charts keep series colors in the ApexCharts options, while label text accepts Tailwind utilities through the cssClass option.
- Chart.js reads the variable at runtime with getComputedStyle, and the result goes into the options object.
In shadcn/ui’s chart component, ChartContainer turns each ChartConfig key into a –color-key variable. The series then reads it as fill=”var(–color-desktop)”.
Snippets from different eras clash here. The v3 docs site for shadcn/ui defines chart colors as HSL channel triplets such as 12 76% 61%, while setups for Tailwind v4 use full OKLCH values and read them as plain var(–chart-1).
A copied snippet that still wraps the variable in hsl() cannot parse an OKLCH value, so the series loses its intended color.
A developer write-up on bgenc.com (August 2023) hit the canvas limit in a React, Tailwind and DaisyUI app. Chart.js could not see the page’s CSS variables, so the fix was reading them with getComputedStyle.
How do Tailwind charts handle dark mode?
Anything you write yourself uses the dark: variant. Anything a library draws gets its colors from CSS variables swapped under a dark selector. SVG charts re-theme the moment the selector changes, while canvas charts have to read their colors again.
In Tailwind v4 the dark: variant follows prefers-color-scheme with no setup. A manual switch needs one line in your CSS, @custom-variant dark (&:where(.dark, .dark *));, which replaces the darkMode setting from v3 config.
A Tailwind toggle that adds or removes the dark class on the html element is all the control needs.
For shadcn/ui charts, declare a second palette under .dark. The chart.tsx file scopes each injected series variable per theme (light with no selector, dark under .dark), and ChartConfig accepts a theme object with light and dark values in place of a single color.
Hand-built SVG just gets dark: utilities on the marks, for example fill-sky-500 dark:fill-sky-400. Chart.js needs the variables re-read after the toggle and update called on each chart instance.
The bgenc.com example re-ran its color lookup whenever the theme switched, so the canvas never kept stale colors.
Flowbite’s own sources disagree here. Its chart docs describe dark mode as supported by default, while its changelog records removing a default dark mode setting from the plugin.
Test the toggle on your own Flowbite version instead of trusting either line.
Keep series order identical in both palettes. Series 1 stays blue in light mode and turns a lighter blue in dark mode, so a legend learned in one theme still works in the other.
Are Tailwind charts accessible?
Not by default. Tailwind adds no accessibility behavior of its own, and each library covers a different share of it, so most of the work lands on you.
Contrast for bars, lines and slices
WCAG 2.2 success criterion 1.4.11 requires a contrast ratio of at least 3:1 for graphical objects needed to understand the content, measured against adjacent colors (W3C).
Bars, lines and pie slices all count. The dark palette needs its own pass, because a color pair that clears 3:1 on white can fail on a dark card, and a color contrast checker run on both palettes catches it.
Axis and tick text follows the text rule, 4.5:1 under criterion 1.4.3. Pie slices with no percentages beside them need 3:1 against their neighbors and the chart background too, per Cornell University’s WCAG test guidance.
Keyboard and screen reader support by library
Recharts has an accessibilityLayer prop that adds arrow-key movement between data points. It is on by default since Recharts 3.0, and shadcn/ui’s examples still set it explicitly.
ApexCharts’ current docs describe keyboard navigation, ARIA support and a chart.accessibility.description option (as of October 2026), and tree-shaken builds import the keyboard feature separately.
Chart.js is the weak spot, since screen readers cannot read canvas content. The canvas needs role=”img” and an aria-label, or fallback content between its tags (Chart.js docs).
ApexCharts’ own conformance report (dated May 15, 2026, covering v5.x) rates criteria such as 1.1.1, 1.3.1 and 4.1.2 as Supports. It adds that custom colors you pass in are outside the library’s control, so final contrast depends on how you configure the chart.
Mantine’s chart package, built on Recharts, turns the accessibility layer on by default and offers an opt-out.
One failure mode is documented. A Recharts discussion (#4662) reports that with macOS VoiceOver running, the axes take over arrow-key navigation and the data points stop being reachable. A reply in the thread says turning VoiceOver’s QuickNav off restores arrow-key movement between the points.
Text alternatives and table fallbacks
Every chart needs its numbers in text form somewhere.
A data table gives screen reader users exact values that hover tooltips never expose. Build it as a properly structured accessible table and hide it visually with Tailwind’s sr-only utility, which keeps it available to assistive technology.
For a static chart, use role img plus a short aria-label that states the takeaway, not just the title. Interactive charts should keep the table next to the chart so keyboard users can switch to the raw values.
Color alone also fails the separate Use of Color criterion, so direct labels or line styles should back up the palette.
When do Tailwind charts fall short?
Mostly when the chart stops being a styled widget and starts acting like an app feature. Print output, server rendering, data-driven colors and copy-pasted code you have to maintain all expose limits in the approach.
Print and PDF output
Tailwind’s print: variant can reshape a layout, but Chart.js does not resize itself when print styles change the container, according to its responsive docs.
The documented workaround hooks the beforeprint event and calls resize on each chart instance. If the print layout is still off, pass an explicit size to resize in beforeprint, then call resize again in afterprint to restore automatic sizing.
Server-rendered pages
ApexCharts’ README now documents server rendering that outputs real SVG on the server and hydrates it on the client, replacing the dynamic import with ssr set to false that Next.js projects used to need (as of October 2026).
Charts that only render in the browser leave an empty card until JavaScript runs. A fixed height utility on the card stops the layout from jumping, and skeleton screens fill the space until the chart arrives.
Series colors that come from data
Colors chosen at runtime need a different fix than inline heights. Register the possible classes ahead of time with @source inline, which accepts brace expansion (Tailwind docs, detecting classes in source files).
@source inline("{fill,stroke}-sky-{300,400,500}");
Keep the safelist short. Every entry ships in the stylesheet whether a chart uses it or not.
Who maintains the chart code
With copy-paste code like shadcn/ui charts, the chart file lives in your repo, so fixes and upgrades are yours. Recent versions of its chart docs carry an Updating to Recharts v3 section.
Packages such as Chart.js, ApexCharts and @tremor/react deliver fixes by version bump, and breaking changes arrive the same way.
If the chart is the product itself, such as a dense trading view, a dedicated charting library with its own theming serves better than Tailwind styling around it. Tailwind still styles the shell.
Tailwind Charts FAQ
Are there free Tailwind chart templates?
Yes. Material Tailwind and David UI each give away one line chart block and keep the rest of their chart blocks behind a PRO tier, and both need the recharts package installed.
The freefrontend collections add Chart.js and ApexCharts examples as free CodePen demos.
Which chart type fits which kind of data?
Match the chart to the question the data answers. Line and area charts show change over time, bar charts compare categories, and donut charts show parts of a whole when there are only a handful of slices.
A sparkline puts a compact trend inside a KPI card.
How do I lazy load a chart below the fold?
Import the chart module only when its card nears the viewport. A dynamic import() fired by an IntersectionObserver works in any framework, and the lazy component APIs in React and Vue do the same job.
Reserve the card height first so the page does not jump when the chart arrives.
How can a chart update with live data?
Change the data, then trigger a redraw. In Recharts, new data held in state redraws the chart. In Chart.js, push values into the dataset and call update(), and in ApexCharts call updateSeries, as the Flowbite docs show.
Can Tailwind charts run in the TALL stack?
Yes. Beartropy Charts is a component library built for Tailwind, Alpine, Laravel and Livewire.
For ApexCharts, the community larapex-charts package wraps the library for Laravel.
Keeping Tailwind Charts Working Through Upgrades
Pin the chart library and Tailwind at fixed versions, and keep the palette in CSS variables. A release then touches one stylesheet and one dependency line instead of every chart.
In practice that means lockfile entries for both packages, series colors that live in variables and not in per-chart options, and a contrast and appearance check in both themes after each bump.
Pinning comes first because it turns a breaking change into a scheduled task. Variables come second so a new color format means editing one block, and the theme check comes last because it only holds once the first two do.
The downside is delay, since a pinned library misses fixes until someone raises the version on purpose. Teams that share tokens across products can settle who owns that bump by following design system best practices.