Chart
Interactive charts themed with shadcn CSS variables and rendered on Chart.js canvas.
Requires JavaScript
Requires Chart.js
| Month | Desktop | Mobile |
|---|---|---|
| January | 186 | 80 |
| February | 305 | 200 |
| March | 237 | 120 |
| April | 73 | 190 |
| May | 209 | 130 |
| June | 214 | 140 |
chart/default
<% data = {
labels: ["January", "February", "March", "April", "May", "June"],
datasets: [
{ key: :desktop, label: "Desktop", data: [186, 305, 237, 73, 209, 214] },
{ key: :mobile, label: "Mobile", data: [80, 200, 120, 190, 130, 140] }
]
} %>
<% config = {
desktop: { label: "Desktop", color: "hsl(var(--chart-1))" },
mobile: { label: "Mobile", color: "hsl(var(--chart-2))" }
} %>
<%= render Shadcn::Chart.new(
type: :bar,
data: data,
config: config,
aria_label: "Monthly visitors by device"
) do |chart| %>
<% chart.with_fallback do %>
<table class="w-full text-sm">
<caption class="mb-2 font-medium">Monthly visitors</caption>
<thead>
<tr>
<th class="text-left">Month</th>
<th class="text-right">Desktop</th>
<th class="text-right">Mobile</th>
</tr>
</thead>
<tbody>
<% data[:labels].each_with_index do |month, index| %>
<tr>
<td><%= month %></td>
<td class="text-right"><%= data[:datasets][0][:data][index] %></td>
<td class="text-right"><%= data[:datasets][1][:data][index] %></td>
</tr>
<% end %>
</tbody>
</table>
<% end %>
<% end %>
Installation
Add the component, install the Chart.js peer dependency, and register the opt-in Chart controller entry:
rails generate shadcn:add chart
npm install chart.js@^4.5.1
# importmap apps can pin the dynamic import instead:
./bin/importmap pin chart.js/auto
import { registerShadcnControllers } from "shadcn-rails-stimulus"
import { registerShadcnChartController } from "shadcn-rails-stimulus/chart"
registerShadcnControllers(application)
registerShadcnChartController(application)
The all-in-one Stimulus entry intentionally excludes Chart so apps that do not render charts never resolve chart.js/auto.
Usage
Pass Chart.js-style data plus a config hash keyed by dataset key, dataset label, or pie label.
<% data = {
labels: ["January", "February", "March"],
datasets: [
{ key: :desktop, label: "Desktop", data: [186, 305, 237] },
{ key: :mobile, label: "Mobile", data: [80, 200, 120] }
]
} %>
<% config = {
desktop: { label: "Desktop", color: "hsl(var(--chart-1))" },
mobile: { label: "Mobile", color: "hsl(var(--chart-2))" }
} %>
<%= render Shadcn::Chart.new(
type: :bar,
data: data,
config: config,
aria_label: "Monthly visitors by device"
) do |chart| %>
<% chart.with_fallback do %>
<table>
<caption>Monthly visitors by device</caption>
<thead>
<tr>
<th>Month</th>
<th>Desktop</th>
<th>Mobile</th>
</tr>
</thead>
<tbody>
<% data[:labels].each_with_index do |month, index| %>
<tr>
<td><%= month %></td>
<td><%= data[:datasets][0][:data][index] %></td>
<td><%= data[:datasets][1][:data][index] %></td>
</tr>
<% end %>
</tbody>
</table>
<% end %>
<% end %>
Examples
Line
chart/line
<% data = {
labels: ["January", "February", "March", "April", "May", "June"],
datasets: [
{ key: :desktop, label: "Desktop", data: [186, 305, 237, 73, 209, 214] },
{ key: :mobile, label: "Mobile", data: [80, 200, 120, 190, 130, 140] }
]
} %>
<% config = {
desktop: { label: "Desktop", color: "hsl(var(--chart-1))" },
mobile: { label: "Mobile", color: "hsl(var(--chart-2))" }
} %>
<%= render Shadcn::Chart.new(
type: :line,
data: data,
config: config,
aria_label: "Monthly visitor trends by device"
) do |chart| %>
<% chart.with_fallback do %>
<table class="w-full text-sm">
<caption class="mb-2 font-medium">Monthly visitor trends by device</caption>
<thead>
<tr>
<th class="text-left">Month</th>
<th class="text-right">Desktop</th>
<th class="text-right">Mobile</th>
</tr>
</thead>
<tbody>
<% data[:labels].each_with_index do |month, index| %>
<tr>
<td><%= month %></td>
<td class="text-right"><%= data[:datasets][0][:data][index] %></td>
<td class="text-right"><%= data[:datasets][1][:data][index] %></td>
</tr>
<% end %>
</tbody>
</table>
<% end %>
<% end %>
app/code_examples/chart/line.txt
<% data = {
labels: ["January", "February", "March", "April", "May", "June"],
datasets: [
{ key: :desktop, label: "Desktop", data: [186, 305, 237, 73, 209, 214] },
{ key: :mobile, label: "Mobile", data: [80, 200, 120, 190, 130, 140] }
]
} %>
<% config = {
desktop: { label: "Desktop", color: "hsl(var(--chart-1))" },
mobile: { label: "Mobile", color: "hsl(var(--chart-2))" }
} %>
<%= render Shadcn::Chart.new(
type: :line,
data: data,
config: config,
aria_label: "Monthly visitor trends by device"
) do |chart| %>
<% chart.with_fallback do %>
<table class="w-full text-sm">
<caption class="mb-2 font-medium">Monthly visitor trends by device</caption>
<thead>
<tr>
<th class="text-left">Month</th>
<th class="text-right">Desktop</th>
<th class="text-right">Mobile</th>
</tr>
</thead>
<tbody>
<% data[:labels].each_with_index do |month, index| %>
<tr>
<td><%= month %></td>
<td class="text-right"><%= data[:datasets][0][:data][index] %></td>
<td class="text-right"><%= data[:datasets][1][:data][index] %></td>
</tr>
<% end %>
</tbody>
</table>
<% end %>
<% end %>
Area
chart/area
<% data = {
labels: ["January", "February", "March", "April", "May", "June"],
datasets: [
{ key: :organic, label: "Organic", data: [320, 401, 380, 520, 610, 750] },
{ key: :paid, label: "Paid", data: [180, 230, 280, 310, 390, 420] }
]
} %>
<% config = {
organic: { label: "Organic", color: "hsl(var(--chart-1))" },
paid: { label: "Paid", color: "hsl(var(--chart-2))" }
} %>
<%= render Shadcn::Chart.new(
type: :area,
data: data,
config: config,
aria_label: "Monthly acquisition volume by channel"
) do |chart| %>
<% chart.with_fallback do %>
<table class="w-full text-sm">
<caption class="mb-2 font-medium">Monthly acquisition volume by channel</caption>
<thead>
<tr>
<th class="text-left">Month</th>
<th class="text-right">Organic</th>
<th class="text-right">Paid</th>
</tr>
</thead>
<tbody>
<% data[:labels].each_with_index do |month, index| %>
<tr>
<td><%= month %></td>
<td class="text-right"><%= data[:datasets][0][:data][index] %></td>
<td class="text-right"><%= data[:datasets][1][:data][index] %></td>
</tr>
<% end %>
</tbody>
</table>
<% end %>
<% end %>
app/code_examples/chart/area.txt
<% data = {
labels: ["January", "February", "March", "April", "May", "June"],
datasets: [
{ key: :organic, label: "Organic", data: [320, 401, 380, 520, 610, 750] },
{ key: :paid, label: "Paid", data: [180, 230, 280, 310, 390, 420] }
]
} %>
<% config = {
organic: { label: "Organic", color: "hsl(var(--chart-1))" },
paid: { label: "Paid", color: "hsl(var(--chart-2))" }
} %>
<%= render Shadcn::Chart.new(
type: :area,
data: data,
config: config,
aria_label: "Monthly acquisition volume by channel"
) do |chart| %>
<% chart.with_fallback do %>
<table class="w-full text-sm">
<caption class="mb-2 font-medium">Monthly acquisition volume by channel</caption>
<thead>
<tr>
<th class="text-left">Month</th>
<th class="text-right">Organic</th>
<th class="text-right">Paid</th>
</tr>
</thead>
<tbody>
<% data[:labels].each_with_index do |month, index| %>
<tr>
<td><%= month %></td>
<td class="text-right"><%= data[:datasets][0][:data][index] %></td>
<td class="text-right"><%= data[:datasets][1][:data][index] %></td>
</tr>
<% end %>
</tbody>
</table>
<% end %>
<% end %>
Pie
chart/pie
<% data = {
labels: ["Subscriptions", "Services", "Licensing", "Support"],
datasets: [
{ label: "Revenue", data: [52, 26, 14, 8] }
]
} %>
<% config = {
subscriptions: { label: "Subscriptions", color: "hsl(var(--chart-1))" },
services: { label: "Services", color: "hsl(var(--chart-2))" },
licensing: { label: "Licensing", color: "hsl(var(--chart-3))" },
support: { label: "Support", color: "hsl(var(--chart-4))" }
} %>
<%= render Shadcn::Chart.new(
type: :pie,
data: data,
config: config,
aria_label: "Revenue mix by product line"
) do |chart| %>
<% chart.with_fallback do %>
<table class="w-full text-sm">
<caption class="mb-2 font-medium">Revenue mix by product line</caption>
<thead>
<tr>
<th class="text-left">Product line</th>
<th class="text-right">Revenue share</th>
</tr>
</thead>
<tbody>
<% data[:labels].each_with_index do |label, index| %>
<tr>
<td><%= label %></td>
<td class="text-right"><%= data[:datasets][0][:data][index] %>%</td>
</tr>
<% end %>
</tbody>
</table>
<% end %>
<% end %>
app/code_examples/chart/pie.txt
<% data = {
labels: ["Subscriptions", "Services", "Licensing", "Support"],
datasets: [
{ label: "Revenue", data: [52, 26, 14, 8] }
]
} %>
<% config = {
subscriptions: { label: "Subscriptions", color: "hsl(var(--chart-1))" },
services: { label: "Services", color: "hsl(var(--chart-2))" },
licensing: { label: "Licensing", color: "hsl(var(--chart-3))" },
support: { label: "Support", color: "hsl(var(--chart-4))" }
} %>
<%= render Shadcn::Chart.new(
type: :pie,
data: data,
config: config,
aria_label: "Revenue mix by product line"
) do |chart| %>
<% chart.with_fallback do %>
<table class="w-full text-sm">
<caption class="mb-2 font-medium">Revenue mix by product line</caption>
<thead>
<tr>
<th class="text-left">Product line</th>
<th class="text-right">Revenue share</th>
</tr>
</thead>
<tbody>
<% data[:labels].each_with_index do |label, index| %>
<tr>
<td><%= label %></td>
<td class="text-right"><%= data[:datasets][0][:data][index] %>%</td>
</tr>
<% end %>
</tbody>
</table>
<% end %>
<% end %>
Donut
chart/donut
<% data = {
labels: ["Subscriptions", "Services", "Licensing", "Support"],
datasets: [
{ label: "Revenue", data: [52, 26, 14, 8] }
]
} %>
<% config = {
subscriptions: { label: "Subscriptions", color: "hsl(var(--chart-1))" },
services: { label: "Services", color: "hsl(var(--chart-2))" },
licensing: { label: "Licensing", color: "hsl(var(--chart-3))" },
support: { label: "Support", color: "hsl(var(--chart-4))" }
} %>
<%= render Shadcn::Chart.new(
type: :donut,
data: data,
config: config,
aria_label: "Revenue mix by product line as a donut chart"
) do |chart| %>
<% chart.with_fallback do %>
<table class="w-full text-sm">
<caption class="mb-2 font-medium">Revenue mix by product line</caption>
<thead>
<tr>
<th class="text-left">Product line</th>
<th class="text-right">Revenue share</th>
</tr>
</thead>
<tbody>
<% data[:labels].each_with_index do |label, index| %>
<tr>
<td><%= label %></td>
<td class="text-right"><%= data[:datasets][0][:data][index] %>%</td>
</tr>
<% end %>
</tbody>
</table>
<% end %>
<% end %>
app/code_examples/chart/donut.txt
<% data = {
labels: ["Subscriptions", "Services", "Licensing", "Support"],
datasets: [
{ label: "Revenue", data: [52, 26, 14, 8] }
]
} %>
<% config = {
subscriptions: { label: "Subscriptions", color: "hsl(var(--chart-1))" },
services: { label: "Services", color: "hsl(var(--chart-2))" },
licensing: { label: "Licensing", color: "hsl(var(--chart-3))" },
support: { label: "Support", color: "hsl(var(--chart-4))" }
} %>
<%= render Shadcn::Chart.new(
type: :donut,
data: data,
config: config,
aria_label: "Revenue mix by product line as a donut chart"
) do |chart| %>
<% chart.with_fallback do %>
<table class="w-full text-sm">
<caption class="mb-2 font-medium">Revenue mix by product line</caption>
<thead>
<tr>
<th class="text-left">Product line</th>
<th class="text-right">Revenue share</th>
</tr>
</thead>
<tbody>
<% data[:labels].each_with_index do |label, index| %>
<tr>
<td><%= label %></td>
<td class="text-right"><%= data[:datasets][0][:data][index] %>%</td>
</tr>
<% end %>
</tbody>
</table>
<% end %>
<% end %>
API Reference
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
| type |
Symbol/String
|
required
|
One of :bar, :line, :area, :pie, or :donut. |
| data |
Hash
|
required
|
Chart.js-compatible labels and datasets. |
| config |
Hash
|
{}
|
Series labels and colors. Colors default to --chart-1 through --chart-5. |
| aria_label |
String
|
"Chart"
|
Accessible name for the chart region. |
| class_name |
String
|
nil
|
Additional CSS classes for the chart container. |
Slots
| Slot | Description |
|---|---|
| with_fallback | Visually hidden fallback content, usually a data table for assistive technology. |
Accessibility
- The chart container renders with
role="img"and an accessible label. - The canvas is hidden from assistive technology, since canvas content is not meaningfully exposed.
- Use
with_fallbackto provide a screen-reader-only table with the same data.
On This Page