Charts
Rails helpers for charts your product dashboard can keep.
Rails UI Charts wraps ApexCharts in Ruby helpers. Most examples start with an ActiveRecord group query and render a themed chart, reserved loading space, dark mode support, and a screen-reader table.
The chart gem stands on its own. Use it in any Rails app, with or without the main Rails UI gem.
Rails UI Charts is free and open source. Rails UI Charts Pro comes with Full Access and builds on the open gem. Keep the same helpers, then add metric suites, cohorts, funnels, waterfalls, financial charts, forecasts, trackers, export, and live updates when your dashboard needs them.
See every chart rendered on one page
Installation
# Gemfile
gem "railsui_charts"
bundle install
rails g railsui_charts:install
# Build mode (esbuild, Vite, Webpack)
yarn add @getrailsui/charts apexcharts
# No-build (importmap)
bin/importmap pin apexcharts
The installer imports the stylesheet. Importmap apps load controllers from the gem. Bundled apps register controllers from @getrailsui/charts, so updates move with the package instead of leaving copied controller files behind in your app.
// app/javascript/controllers/index.js
import { registerRailsuiCharts } from "@getrailsui/charts"
registerRailsuiCharts(application)
Charts wait until they scroll into view. The server still reserves their height, so long dashboard pages do less work and do not jump while charts mount.
Quick start
Most dashboard charts start with one grouped query.
def show
@revenue = Order.paid.group_by_month(:created_at).sum(:total)
end
Data for chart rui-chart-f33628a6| Category | Value |
|---|
| Jan | 32000 |
| Feb | 35000 |
| Mar | 42000 |
| Apr | 38000 |
| May | 48290 |
| Jun | 52000 |
app/controllers/dashboards_controller.rb
@revenue = Order.paid.group_by_month(:created_at).sum(:total)
app/views/dashboards/show.html.erb
<%= railsui_chart @revenue, type: :area, format: :short_currency, height: 240 %>
Pass a label => value hash to railsui_chart. The rest of the page builds from that shape.
Chart types
Choose a chart with type:. The helpers keep data, formatting, and height options consistent across chart types.
Line
Use a line chart for change over time when the shape matters more than filled volume.
Data for chart rui-chart-249f277d| Category | Signups |
|---|
| Mon | 42 |
| Tue | 38 |
| Wed | 51 |
| Thu | 47 |
| Fri | 62 |
| Sat | 55 |
| Sun | 68 |
app/views/dashboards/show.html.erb
<%= railsui_chart @signups, type: :line, label: "Signups", height: 240 %>
Data for chart rui-chart-3ed62412| Category | Value |
|---|
| Mon | 42 |
| Tue | 38 |
| Wed | 51 |
| Thu | 47 |
| Fri | 62 |
| Sat | 55 |
| Sun | 68 |
app/views/dashboards/show.html.erb
<%# Smooth it when the shape matters more than each point %>
<%= railsui_chart @signups, type: :line, curve: "smooth", height: 240 %>
Area
Use an area chart when the filled volume helps, like revenue, usage, or signups over a period.
Data for chart rui-chart-2c8330f1| Category | Value |
|---|
| Jan | 32000 |
| Feb | 35000 |
| Mar | 42000 |
| Apr | 38000 |
| May | 48290 |
| Jun | 52000 |
app/controllers/dashboards_controller.rb
@revenue = Order.paid.group_by_month(:created_at).sum(:total)
app/views/dashboards/show.html.erb
<%= railsui_chart @revenue, type: :area, format: :short_currency, height: 240 %>
Column
Use columns for discrete buckets like months, plans, and traffic sources.
Data for chart rui-chart-9aabbb14| Category | Value |
|---|
| Jan | 128 |
| Feb | 142 |
| Mar | 165 |
| Apr | 151 |
| May | 188 |
| Jun | 204 |
app/views/dashboards/show.html.erb
<%= railsui_chart @orders_by_month, type: :column, format: :number, height: 240 %>
Data for chart rui-chart-cf2a933b| Category | Solo | Team |
|---|
| Jan | 64 | 38 |
| Feb | 71 | 42 |
| Mar | 82 | 51 |
| Apr | 79 | 48 |
| May | 94 | 59 |
| Jun | 103 | 66 |
app/views/dashboards/show.html.erb
<%# `stacked: "percent"` makes it 100% stacked instead %>
<%= railsui_chart @plans_by_month, type: :column, stacked: true, height: 240 %>
Bar
Use horizontal bars when category labels are long. They give labels room without rotated axis text.
Data for chart rui-chart-e8ca9764| Category | Value |
|---|
| Organic search | 18400 |
| Direct | 9200 |
| Referral | 5100 |
| Social | 2800 |
app/views/dashboards/show.html.erb
<%= railsui_chart @traffic_by_source, type: :bar, format: :human, height: 240 %>
Sparkline
Sparklines drop axes and grids, but keep hover values. They fit inside cards and table rows where nearby text gives the context.
Data for chart rui-chart-41b7ae39| Category | Value |
|---|
| 1 | 18 |
| 2 | 22 |
| 3 | 19 |
| 4 | 27 |
| 5 | 24 |
| 6 | 31 |
| 7 | 29 |
| 8 | 34 |
| 9 | 30 |
| 10 | 38 |
| 11 | 41 |
| 12 | 37 |
| 13 | 44 |
| 14 | 48 |
app/views/dashboards/show.html.erb
<%= railsui_chart @last_30_days, type: :sparkline, height: 60 %>
Pie and donut
Use pie and donut charts for part-to-whole data with a short category list. Keep card-sized charts to four slices and fold the tail into "Other".
Data for chart rui-chart-af4ab6e4| Category | Value |
|---|
| Solo | 412 |
| Team | 168 |
| Enterprise | 54 |
| Other | 31 |
app/views/dashboards/show.html.erb
<%= railsui_chart @customers_by_plan, type: :donut, height: 260 %>
Data for chart rui-chart-ce27bbad| Category | Value |
|---|
| Solo | 412 |
| Team | 168 |
| Enterprise | 54 |
| Other | 31 |
app/views/dashboards/show.html.erb
<%= railsui_chart @customers_by_plan, type: :pie, height: 260 %>
Radar and polar area
Use radar and polar area charts for profiles, scores, and benchmark comparisons. They work poorly for exact value reading.
Data for chart rui-chart-4159d843| Category | Value |
|---|
| Dashboards | 82 |
| Reports | 64 |
| Exports | 47 |
| Alerts | 38 |
| API | 71 |
app/views/dashboards/show.html.erb
<%= railsui_chart @feature_usage, type: :radar, height: 280 %>
Data for chart rui-chart-4f593b3e| Category | Value |
|---|
| Dashboards | 82 |
| Reports | 64 |
| Exports | 47 |
| Alerts | 38 |
| API | 71 |
app/views/dashboards/show.html.erb
<%= railsui_chart @feature_usage, type: :polar_area, height: 280 %>
Scatter and bubble
Use scatter and bubble charts for numeric relationships. These take point data, not a labelled hash.
Data for chart rui-chart-2bd238a6| Category | Accounts |
|---|
| 1 | 3, 290 |
| 2 | 5, 460 |
| 3 | 8, 720 |
| 4 | 12, 980 |
| 5 | 18, 1420 |
| 6 | 24, 2100 |
| 7 | 31, 2480 |
app/controllers/dashboards_controller.rb
@seats_vs_spend = Account.pluck(:seats, :mrr).map { |x, y| { x: x, y: y } }
app/views/dashboards/show.html.erb
<%= railsui_chart [{ name: "Accounts", data: @seats_vs_spend }], type: :scatter, height: 260 %>
Data for chart rui-chart-8b4dc62d| Category | Accounts |
|---|
| 3 | 3, 290, 4 |
| 8 | 8, 720, 11 |
| 12 | 12, 980, 6 |
| 18 | 18, 1420, 19 |
| 24 | 24, 2100, 9 |
app/controllers/dashboards_controller.rb
# z becomes the radius
@accounts = Account.pluck(:seats, :mrr, :tickets).map { |x, y, z| { x:, y:, z: } }
app/views/dashboards/show.html.erb
<%= railsui_chart [{ name: "Accounts", data: @accounts }], type: :bubble, height: 260 %>
Multiple series
Pass an array of { name:, data: } hashes. The data key separates series, so single-series charts can stay plain hashes.
Data for chart rui-chart-8dfacf78| Category | Pro | Team |
|---|
| Jan | 64 | 38 |
| Feb | 71 | 42 |
| Mar | 82 | 51 |
| Apr | 79 | 48 |
| May | 94 | 59 |
| Jun | 103 | 66 |
app/views/dashboards/show.html.erb
<%= railsui_chart [
{ name: "Pro", data: @pro_by_month },
{ name: "Team", data: @team_by_month }
], type: :area, height: 260 %>
The palette assigns colors in order. Once a chart needs more than eight series, split the data into small multiples.
Combo and dual axis
Set type: on a series to mix shapes. Set axis: :right when a series needs its own scale.
A common dashboard chart pairs volume columns with a rate line. Revenue and churn do not share a useful range. Put both on one scale and the churn line disappears along the floor.
Data for chart rui-chart-32b4b60d| Category | Revenue | Churn rate |
|---|
| Jan | 32000 | 4.2 |
| Feb | 35000 | 3.9 |
| Mar | 42000 | 3.4 |
| Apr | 38000 | 3.6 |
| May | 48290 | 3.1 |
| Jun | 52000 | 2.8 |
app/controllers/dashboards_controller.rb
# Two measures that share an x-axis and nothing else: money in the tens
# of thousands, a rate in single-digit percent. Plotted against one
# scale the churn line sits flat on the floor and says nothing.
@revenue_by_month = Order.paid.group_by_month(:created_at).sum(:total)
@churn_by_month = Account.churn_rate_by_month
app/views/dashboards/show.html.erb
<%= railsui_chart [
{ name: "Revenue", data: @revenue_by_month, type: :column, format: :short_currency },
{ name: "Churn rate", data: @churn_by_month, type: :line, axis: :right, format: :percentage }
], height: 300 %>
Each side can format itself. Put format: on a series so money and percentages keep their own units on axes and tooltips.
The helper keeps both scales on the same tick count, so the gridlines line up.
Axis labels use the color of the series they measure, which helps when both sides carry a scale.
The helper draws one ruler per side. If two series share the left scale, they share the left ruler.
Comparing periods
compare: adds a dashed previous-period series and changes the tooltip into current, previous, and delta rows.
Data for chart rui-chart-69d99c53| Category | Comparison category | Value | Previous period |
|---|
| Mon | Mon | 4100 | 3800 |
| Tue | Tue | 4400 | 3900 |
| Wed | Wed | 5200 | 4600 |
| Thu | Thu | 4900 | 4500 |
| Fri | Fri | 6100 | 5300 |
| Sat | Sat | 5600 | 5100 |
| Sun | Sun | 6800 | 5900 |
app/views/dashboards/show.html.erb
<%= railsui_chart @this_week,
type: :area,
compare: @last_week,
format: :currency,
height: 260 %>
Anchor both windows to the same date. If each window measures from its own end, the delta loses meaning.
For metrics where lower is better, such as churn, refunds, or latency, pass trend_up_is_good: false. The delta color then follows the result, not the direction.
Small multiples
Small multiples give each series its own chart while keeping a shared y-scale. Use them when one chart has too many lines to scan.
Solo
Data for chart rui-chart-adbceeaa| Category | Value |
|---|
| Jan | 64 |
| Feb | 71 |
| Mar | 82 |
| Apr | 79 |
| May | 94 |
| Jun | 103 |
Team
Data for chart rui-chart-29e13c00| Category | Value |
|---|
| Jan | 38 |
| Feb | 42 |
| Mar | 51 |
| Apr | 48 |
| May | 59 |
| Jun | 66 |
Enterprise
Data for chart rui-chart-c23d0079| Category | Value |
|---|
| Jan | 8 |
| Feb | 9 |
| Mar | 12 |
| Apr | 14 |
| May | 17 |
| Jun | 21 |
app/views/dashboards/show.html.erb
<%= railsui_small_multiples @plans, type: :area, columns: 3, height: 120 %>
The shared scale keeps small values from looking inflated.
Metric cards
Metric cards pair a headline number with period change and a sparkline. Use them when the number matters as much as the trend.
MRR
$18,450.00+8.36%
$17,027.00 previous period
Data for chart rui-chart-c523cdfb| Category | Comparison category | MRR | Previous period |
|---|
| Mon | Mon | 17200 | 16100 |
| Tue | Tue | 17450 | 16300 |
| Wed | Wed | 17680 | 16480 |
| Thu | Thu | 17900 | 16610 |
| Fri | Fri | 18050 | 16740 |
| Sat | Sat | 18280 | 16900 |
| Sun | Sun | 18450 | 17027 |
MonSun
app/views/dashboards/show.html.erb
<%= railsui_metric_card label: "MRR",
value: @mrr,
previous: @previous_mrr,
history: @mrr_by_day,
compare: @previous_mrr_by_day,
format: :currency,
expand: true %>
expand: true turns the footer link into a larger view of the same series. A compact sparkline shows direction; the expanded chart gives values room.
For a compact stack without the card, use railsui_metric:
<%= railsui_metric label: "Active trials", value: 128, change: 12.4 %>
Filters
Use one filter row for a dashboard. Every card then renders against the same date range and comparison setting.
MRR
$18,450.000%
$18,450.00 previous period
Data for chart rui-chart-8ec34455| Category | Comparison category | MRR | Previous period |
|---|
| Aug 31 | Aug 24 | 17359 | 17338 |
| Sep 1 | Aug 25 | 17451 | 17384 |
| Sep 2 | Aug 26 | 17716 | 17696 |
| Sep 3 | Aug 27 | 17845 | 17858 |
| Sep 4 | Aug 28 | 18062 | 18115 |
| Sep 5 | Aug 29 | 18325 | 18218 |
| Sep 6 | Aug 30 | 18450 | 18450 |
Aug 31Sep 6
app/controllers/dashboards_controller.rb
def show
@filters = RailsuiCharts::Filters.new(params)
current = revenue_for(@filters.range)
previous = @filters.previous_range && revenue_for(@filters.previous_range)
@metrics = [
{ label: "MRR", value: current.values.last, previous: previous&.values&.last,
format: :currency, history: current, compare: previous }
]
end
private
def revenue_for(range)
Order.paid.where(created_at: range)
.group_by_period(@filters.interval, :created_at).sum(:total)
end
app/views/dashboards/show.html.erb
<%= turbo_frame_tag "docs-filter-preview" do %>
<%= railsui_chart_filters @filters, url: docs_charts_path, frame: "docs-filter-preview" %>
<div class="mt-4 max-w-2xl">
<%= railsui_metric_card(**@filter_metrics.first, chart_height: 140) %>
</div>
<% end %>
The filter row submits as a GET form, so users can share the URL. With Turbo, changes submit in place and refresh the named frame.
Presets are 24h, 7d, 30d, 90d, 12m, and mtd. Pass select_class: and checkbox_class: when you want the controls to match your own form styles.
Empty, loading, and error
Plan for day one, loading, and failure states. Each state keeps the chart's footprint so the page stays still.
Empty
Use an empty state when the query succeeds but returns no rows.
No orders yet
They will show up here as they come in.
app/views/dashboards/show.html.erb
<%= railsui_chart_empty title: "No orders yet",
description: "They will show up here as they come in.",
height: 180 %>
Loading
Use a chart skeleton while a Turbo frame or background query fetches the real data.
app/views/dashboards/show.html.erb
<%= railsui_chart_skeleton height: 180, type: :area, label: "Loading revenue" %>
Error
Use an error state when the chart could not load and the user needs a clear next step.
Couldn't load revenue
The query timed out. Try a shorter range.
app/views/dashboards/show.html.erb
<%= railsui_chart_error title: "Couldn't load revenue",
description: "The query timed out. Try a shorter range.",
height: 180 %>
railsui_chart renders the empty state when data is blank. Pass empty: to set the message for that chart. Use railsui_chart_skeleton while data is in flight, and railsui_chart_error when the request fails. Loading skeletons align left by default; pass align: :center or align: :right when the surrounding layout calls for it.
Theming
Charts read appearance from CSS variables and geometry from Ruby. ApexCharts handles those values differently. Colors and type can arrive as strings. Geometry feeds arithmetic, so it needs numeric settings.
Color and type
Colors and font sizes reach ApexCharts as CSS strings. Set variables on :root for the app, or scope them to one card when a chart needs its own treatment.
:root {
--rui-chart-primary: #4f46e5;
--rui-chart-secondary: #0ea5e9;
--rui-chart-accent: #10b981;
--rui-chart-muted: #94a3b8;
--rui-chart-surface: #ffffff;
--rui-chart-text: #64748b;
--rui-chart-grid: rgba(148, 163, 184, 0.2);
--rui-chart-positive: #047857;
--rui-chart-negative: #b91c1c;
/* Categorical slots, used in order for multi-series charts */
--rui-chart-series-1: #6366f1;
--rui-chart-series-2: #ea580c;
/* ...through series-8 */
/* Type read by the chart itself */
--rui-chart-font-family: inherit;
--rui-chart-font-size: 12px;
--rui-chart-font-size-sm: 11px;
/* Type for the parts drawn in HTML: tooltip, metric cards, tables */
--rui-chart-text-size: 0.8125rem;
--rui-chart-text-size-sm: 0.75rem;
--rui-chart-value-size: 1.375rem;
}
/* Scoped: only the charts inside this card */
.dense-card {
--rui-chart-font-size: 10px;
}
Chart labels stay smaller than nearby component text. They support the chart; they do not compete with the card copy.
Dark mode
Charts support prefers-color-scheme, a root dark class, and data-theme="dark". They redraw when the theme changes.
Geometry
Border radius, stroke width, and marker size stay in Ruby because ApexCharts does arithmetic with them. A CSS variable would arrive as a string, which can break geometry without raising a clear error.
RailsuiCharts.configure do |config|
config.default_height = 250
config.default_currency = "$"
config.geometry[:bar_radius] = 4
config.geometry[:stroke_width] = 2
config.geometry[:marker_size] = 0
config.geometry[:marker_hover_size] = 6
end
The categorical palette depends on order. We validated that order against lightness, chroma, colorblind separation, and 3:1 contrast in light and dark themes. If you reorder the series slots, recheck those constraints.
Every variable
Use these variables on :root for the whole app, or on a container when one group of charts needs its own treatment.
Palette
| Variable |
Default |
Applies to |
| --rui-chart-primary |
#6366f1 |
Single-series charts and the leading line in a comparison |
| --rui-chart-secondary |
#0ea5e9 |
Available to your own passthrough options |
| --rui-chart-accent |
#10b981 |
Available to your own passthrough options |
| --rui-chart-muted |
#94a3b8 |
The trailing series in a comparison |
| --rui-chart-series-1 … -8 |
8 hues |
Categorical slots, used in order: indigo, orange, pink, fuchsia, lime, sky, red, teal |
| --rui-chart-positive |
#047857 |
A delta that is good news |
| --rui-chart-negative |
#b91c1c |
A delta that is not |
Surfaces and chrome
| Variable |
Default |
Applies to |
| --rui-chart-surface |
#ffffff |
Chart card background. Touching marks use this color as their gap |
| --rui-chart-background |
transparent |
The plot area itself |
| --rui-chart-grid |
rgba(148, 163, 184, 0.22) |
Gridlines and axis rules |
| --rui-chart-text |
#64748b |
Axis labels and legend text |
| --rui-chart-border |
rgba(17, 24, 39, 0.14) |
Dividers, the filter row, export buttons |
| --rui-chart-hover |
rgba(17, 24, 39, 0.06) |
Hover fills on interactive chrome |
| --rui-chart-switch-off |
#d1d5db |
The compare toggle when it is off |
Metric cards
| Variable |
Default |
Applies to |
| --rui-chart-metric-label |
#111827 |
The label above the figure |
| --rui-chart-metric-value |
#111827 |
The figure itself |
| --rui-chart-metric-muted |
#6b7280 |
Previous-period line and footer |
Tooltip
| Variable |
Default |
Applies to |
| --rui-chart-tooltip-bg |
#ffffff |
Follows the theme rather than staying a dark slab |
| --rui-chart-tooltip-text |
#111827 |
Values |
| --rui-chart-tooltip-muted |
#6b7280 |
Series names and dates |
| --rui-chart-tooltip-border |
rgba(17, 24, 39, 0.14) |
Outline |
| --rui-chart-tooltip-radius |
0.5rem |
Corner radius |
| --rui-chart-tooltip-shadow |
0 8px 24px rgba(15, 23, 42, 0.12) |
Drop shadow |
Loading states
| Variable |
Default |
Applies to |
| --rui-chart-skeleton |
#f8f9fa |
Placeholder bars |
| --rui-chart-skeleton-line |
rgba(148, 163, 184, 0.13) |
Subtle separators for custom loading placeholders |
| --rui-chart-skeleton-sheen |
rgba(255, 255, 255, 0.65) |
The sweep across skeleton bars |
Type
| Variable |
Default |
Applies to |
| --rui-chart-font-family |
inherit |
Nothing imposes a typeface; charts take the page's |
| --rui-chart-font-size |
12px |
Axis labels and data labels, read by the chart itself |
| --rui-chart-font-size-sm |
11px |
Legend, annotation labels, and under 640px |
| --rui-chart-text-size |
0.8125rem |
Tooltip rows, metric labels, table cells |
| --rui-chart-text-size-sm |
0.75rem |
Captions and deltas |
| --rui-chart-value-size |
1.375rem |
The metric card headline |
| Variable |
Default |
Applies to |
| --rui-cohort-hue |
var(--rui-chart-primary) |
The single hue a cohort grid steps through |
| --rui-cohort-ceiling |
55% |
Caps the darkest cell so text stays legible on every step |
| --rui-bar-list-fill |
0.16 |
Opacity of the bar behind each row |
| --rui-tracker-degraded |
#d97706 |
The middle status color. Up and down use positive and negative |
| --rui-tracker-none |
muted at 26% |
A period with no data |
| --rui-tracker-height |
2rem |
Height of the strip |
| --rui-tracker-radius |
2px |
Corner radius on each mark |
| --rui-financial-up |
#0f766e |
Candles that close above their open |
| --rui-financial-down |
#be123c |
Candles that close below their open |
| --rui-financial-overlay |
#475569 |
The price legend, moving-average lines, and related overlays |
| --rui-financial-outline |
slate at 64% |
The candle outline and wick stroke |
| --rui-financial-volume |
#64748b |
The volume series paired with candles |
| --rui-box-plot-upper |
#818cf8 |
The upper box plot fill |
| --rui-box-plot-lower |
#bae6fd |
The lower box plot fill |
| --rui-box-plot-stroke |
slate at 72% |
The box plot outline |
| --rui-heatmap-color |
#2563eb |
The hue heatmap cells shade from |
| --rui-forecast-actual |
#4f46e5 |
The observed line in a forecast chart |
| --rui-forecast-line |
#0f766e |
The projected line in a forecast chart |
| --rui-forecast-band |
#818cf8 |
The forecast range area |
Funnel and treemap ramps stay in Ruby as literal hex values. ApexCharts computes shading from color values, and CSS var() strings collapse the treemap fill. Set RailsuiChartsPro.config.treemap_ramp and funnel_ramp instead.
Styling with your own CSS
HTML pieces get stable class names. Use them when variables are too broad, such as matching a bar list to your table styles or removing a metric-card border.
/* Free */
.railsui-chart /* the chart element itself */
.railsui-chart-state /* empty and error states */
.railsui-chart-skeleton /* the loading placeholder */
.railsui-chart-tooltip /* __head, __title, __rows, __key, __delta */
.railsui-chart-filters /* __select, __checkbox */
.railsui-metric-card /* __head, __value, __previous, __footer */
.railsui-metric /* the compact label / value / delta stack */
.railsui-metric-delta /* --positive, --negative */
.railsui-small-multiples /* .railsui-small-multiple, __title */
/* Pro */
.railsui-bar-list /* __row, __track, __bar, __label, __value, __link */
.railsui-cohort /* __label, __cell, __cell--empty */
.railsui-tracker /* __header, __label, __summary, __track */
.railsui-tracker__mark /* --up, --degraded, --down, --none */
.railsui-chart-export__toolbar /* __button, __icon */
ApexCharts still owns the SVG plot markup and its apexcharts- classes. You can target them, but changes there follow ApexCharts releases.
Prefer variables when they exist. A class override can drift from the theme. For example, cohort ink is derived per theme; hard-coding cell color bypasses dark mode.
Accessibility
Every chart includes a visually hidden data table. Pass accessible: false when the same values also appear in a visible table nearby.
Charts respect reduced motion. Tooltips use the same variables as the chart, so they match the page theme.
Passing raw ApexCharts options
Pass any ApexCharts option the helper does not expose. Rails UI merges it into the config before rendering.
<%= railsui_chart @revenue,
type: :area,
height: 320,
legend: { show: true, position: "bottom" },
plotOptions: { area: { fillTo: "end" } } %>
Rails UI Charts Pro
Charts Pro adds dashboard helpers for the questions SaaS products ask after the basic charts are covered. It builds on the open gem, so the helpers above keep working.
It covers four jobs:
Metric suites. A suite composes metric cards, expanded charts, export, breakdown tables, trackers, and timelines into one dashboard row.
Computed chart helpers. Waterfalls, cohorts, funnels, treemaps, ranked bar lists, and gap-filled time series handle the arithmetic and shaping that dashboards usually repeat by hand.
Analytics chart helpers. Candlesticks, box plots, heatmaps, and forecasts keep the Rails data shape close to the view while still producing ApexCharts-native series.
Compact status and comparison UI. Trackers, sparkline tables, and timelines handle the rows and strips that do not need a full chart.
Operational extras. Turbo Stream live updates plus PNG and CSV export.
Charts Pro is included with an active Rails UI Full Access subscription. See pricing for current subscription details.
Sign in to view
This Pro source is available to active Rails UI Full Access subscribers.
Sign in to view
This Pro source is available to active Rails UI Full Access subscribers.
Metric suite
Use a metric grid when a dashboard needs a consistent row of headline metrics. Each card can carry its value, comparison series, and history without setting up a larger dashboard composition.
MRR
$18,450.00+8.36%
$17,027.00 previous period
Data for chart rui-chart-6e4fe28a| Category | Comparison category | MRR | Previous period |
|---|
| Mon | Mon | 17200 | 16100 |
| Tue | Tue | 17450 | 16300 |
| Wed | Wed | 17680 | 16480 |
| Thu | Thu | 17900 | 16610 |
| Fri | Fri | 18050 | 16740 |
| Sat | Sat | 18280 | 16900 |
| Sun | Sun | 18450 | 17027 |
MonSun
ARR
$221,400.00+8.36%
$204,324.00 previous period
Data for chart rui-chart-48ec183f| Category | Comparison category | ARR | Previous period |
|---|
| Mon | Mon | 206400 | 193200 |
| Tue | Tue | 209400 | 195600 |
| Wed | Wed | 212160 | 197760 |
| Thu | Thu | 214800 | 199320 |
| Fri | Fri | 216600 | 200880 |
| Sat | Sat | 219360 | 202800 |
| Sun | Sun | 221400 | 204324 |
MonSun
Active subscribers
842+5.91%
795 previous period
Data for chart rui-chart-344df524| Category | Comparison category | Active subscribers | Previous period |
|---|
| Mon | Mon | 795 | 760 |
| Tue | Tue | 804 | 766 |
| Wed | Wed | 812 | 774 |
| Thu | Thu | 821 | 781 |
| Fri | Fri | 830 | 786 |
| Sat | Sat | 837 | 791 |
| Sun | Sun | 842 | 795 |
MonSun
Churn rate
2.4%-11.11%
2.7% previous period
Data for chart rui-chart-9ec495fa| Category | Churn rate |
|---|
| Mon | 3.1 |
| Tue | 3.0 |
| Wed | 2.8 |
| Thu | 2.8 |
| Fri | 2.6 |
| Sat | 2.5 |
| Sun | 2.4 |
MonSun
Sign in to view
This Pro source is available to active Rails UI Full Access subscribers.
Use railsui_metric_suite when those cards also need breakdowns, trackers, timelines, or export controls around them.
Waterfall
Use a waterfall to explain the movement between two totals. The helper calculates each floating bar, so gains and losses line up with the running total.
Data for chart rui-chart-991e8087| Category | Change | Running total |
|---|
| Starting | | 17027.0 |
| New | 2140.0 | 19167.0 |
| Reactivation | 420.0 | 19587.0 |
| Expansion | 860.0 | 20447.0 |
| Contraction | -410.0 | 20037.0 |
| Churn | -1167.0 | 18870.0 |
| Ending | | 18870.0 |
Sign in to view
This Pro source is available to active Rails UI Full Access subscribers.
Cohort grid
Use cohorts for retention by starting period. Rows get shorter for newer cohorts because those users have less history.
| Cohort | Month 0 | Month 1 | Month 2 | Month 3 | Month 4 | Month 5 | Month 6 | Month 7 |
|---|
| Jan 2026 | 100% | 82% | 74% | 69% | 65% | 62% | 60% | 58% |
|---|
| Feb 2026 | 100% | 79% | 71% | 66% | 63% | 60% | 58% | |
|---|
| Mar 2026 | 100% | 81% | 73% | 68% | 64% | 61% | | |
|---|
| Apr 2026 | 100% | 74% | 63% | 57% | 54% | | | |
|---|
| May 2026 | 100% | 71% | 60% | 55% | | | | |
|---|
| Jun 2026 | 100% | 76% | 66% | | | | | |
|---|
| Jul 2026 | 100% | 79% | | | | | | |
|---|
| Aug 2026 | 100% | | | | | | | |
|---|
Sign in to view
This Pro source is available to active Rails UI Full Access subscribers.
Bar list
Use a bar list for ranked rows: label, inline bar, value. Bars scale against the largest row, which makes the top item easy to read.
| Amelia Allport | $12,840.00 |
|---|
| Tara Vossenkemper | $9,910.00 |
|---|
| Jamie Gammon | $6,480.00 |
|---|
| Priya Raman | $4,120.00 |
|---|
| Otto Lindqvist | $2,260.00 |
|---|
Sign in to view
This Pro source is available to active Rails UI Full Access subscribers.
Tracker
Use a tracker for status over time. One mark per period answers "was this healthy?" better than a line chart that sits at 100% until something breaks.
Sign in to view
This Pro source is available to active Rails UI Full Access subscribers.
The summary defaults to the share of monitored periods marked up. Pass a string to say something else, or summary: false for none.
Trackers do not create ApexCharts instances, so you can put several in a dashboard header.
Sparkline table
Use a sparkline table when each row needs value, movement, and shape.
| Solo | | $18,450.00 | +14.6% |
|---|
| Team | | $12,380.00 | +4.0% |
|---|
| Enterprise | | $8,940.00 | -6.9% |
|---|
| Legacy | | $2,110.00 | +0.5% |
|---|
Sign in to view
This Pro source is available to active Rails UI Full Access subscribers.
The trends use inline SVG, so twenty rows do not create twenty ApexCharts instances.
Leave change: out and the helper compares the first and last history points. If there is not enough history, it omits the delta.
Timeline
Use timelines for records with start and end times: deploys, incidents, trials, subscriptions. Pass Time or Date objects; the helper converts them for ApexCharts.
Data for chart rui-chart-cfdfac98| Category | Timeline |
|---|
| web | 1786352400000, 1786353600000 |
| api | 1786354800000, 1786361400000 |
| worker | 1786366800000, 1786367700000 |
| cdn | 1786356000000, 1786365900000 |
| 5 | 1786350600000, 1786377600000 |
Sign in to view
This Pro source is available to active Rails UI Full Access subscribers.
Rows with the same label share one lane. Use name: when two events share a lane, so each span has its own tooltip title. status: sets color; unknown status values use the default color.
Items with no start time are skipped because they cannot be placed on the axis.
Funnel
Use a funnel for ordered stages and drop-off. One hue gets darker down the stages so order stays clear.
Data for chart rui-chart-fe7ea149| Stage | Count | From previous | Of total |
|---|
| Visited pricing | 12400 | 100.0% | 100.0% |
| Started trial | 3180 | 25.6% | 25.6% |
| Activated | 1640 | 51.6% | 13.2% |
| Subscribed | 720 | 43.9% | 5.8% |
Sign in to view
This Pro source is available to active Rails UI Full Access subscribers.
Treemap
Use a treemap for part-to-whole data with more categories than a pie can carry. The helper sorts largest first so size and shade move together.
Data for chart rui-chart-aeea848b| Category | Value |
|---|
| Salaries | 128400 |
| Infrastructure | 42800 |
| Marketing | 31200 |
| Tooling | 18600 |
| Travel | 9400 |
| Office | 6100 |
Sign in to view
This Pro source is available to active Rails UI Full Access subscribers.
Candlestick
Use candlesticks for open, high, low, and close data. The helper accepts Rails-shaped hashes or records, keeps volume alongside each candle, and lets overlays ride on the same time axis.
Data for chart rui-chart-0a2abe45| Category | Price | Volume | 20-day avg |
|---|
| 2026-08-12 | 118.0, 126.0, 115.0, 124.0 | 24000 | 121 |
| 2026-08-13 | 124.0, 129.0, 121.0, 122.0 | 31000 | 123 |
| 2026-08-14 | 122.0, 131.0, 120.0, 130.0 | 36000 | 126 |
| 2026-08-15 | 130.0, 136.0, 128.0, 134.0 | 42000 | 129 |
| 2026-08-16 | 134.0, 138.0, 129.0, 131.0 | 38000 | 131 |
| 2026-08-17 | 131.0, 141.0, 130.0, 139.0 | 45000 | 134 |
Sign in to view
This Pro source is available to active Rails UI Full Access subscribers.
Box plot
Use a box plot when the spread matters more than a single average. Pass raw samples and the helper computes min, Q1, median, Q3, and max; pass precomputed values when your database already did the work.
Data for chart rui-chart-d77f0f8b| Category | Response time |
|---|
| Web | 86.0, 118.0, 141.0, 210.0, 310.0 |
| API | 94.0, 138.0, 168.0, 238.0, 420.0 |
| Worker | 180.0, 275.0, 360.0, 475.0, 760.0 |
Sign in to view
This Pro source is available to active Rails UI Full Access subscribers.
Heatmap
Use a heatmap for repeated categories across time, channels, or segments. A nested hash becomes rows and columns, so the view can stay close to the grouped data.
Data for chart rui-chart-0b888952| Category | Mon | Tue | Wed | Thu | Fri |
|---|
| 8 AM | 18 | 24 | 21 | 28 | 26 |
| 12 PM | 44 | 52 | 48 | 58 | 54 |
| 4 PM | 38 | 41 | 46 | 49 | 35 |
Sign in to view
This Pro source is available to active Rails UI Full Access subscribers.
Forecast
Use forecasts when you need actuals, a projected line, and the uncertainty band together. The band renders as a range area while the actual and forecast series stay readable as lines.
Data for chart rui-chart-d5ec216f| Category | Range | Actual | Forecast |
|---|
| Aug 1 | 18696.66, 19297.39 | 17200 | 18997.02 |
| Aug 2 | 18927.59, 19777.15 | 17640 | 19352.37 |
| Aug 3 | 19187.47, 20227.97 | 18020 | 19707.72 |
| Aug 4 | 19462.33, 20663.8 | 18360 | 20063.07 |
Sign in to view
This Pro source is available to active Rails UI Full Access subscribers.
Annotations
Use annotations for deploys, incidents, and targets on the same chart. They use colors outside the series palette so markers do not read as another data series.
Data for chart rui-chart-313877a6| Category | Value |
|---|
| Aug 7 | 42 |
| Aug 8 | 38 |
| Aug 9 | 21 |
| Aug 10 | 57 |
| Aug 11 | 61 |
| Aug 12 | 55 |
| Aug 13 | 64 |
Sign in to view
This Pro source is available to active Rails UI Full Access subscribers.
Gap-filled time series
A GROUP BY query returns buckets with rows. TimeSeries fills the missing buckets so quiet days remain visible as zeroes instead of disappearing between two dates.
Data for chart rui-chart-ff854c24| Category | Value |
|---|
| Aug 7 | 42 |
| Aug 8 | 38 |
| Aug 9 | 0 |
| Aug 10 | 0 |
| Aug 11 | 61 |
| Aug 12 | 55 |
| Aug 13 | 64 |
Sign in to view
This Pro source is available to active Rails UI Full Access subscribers.
Export
Wrap a chart to add PNG and CSV buttons. CSV reads from the hidden data table, so export uses the same values as the accessible fallback.
Data for chart rui-chart-86f942c4| Category | Value |
|---|
| Jan | 32000 |
| Feb | 35000 |
| Mar | 42000 |
| Apr | 38000 |
| May | 48290 |
| Jun | 52000 |
Sign in to view
This Pro source is available to active Rails UI Full Access subscribers.
The PNG exporter paints the chart surface first. A dark-mode chart exported on transparency can disappear on a white background.
Live updates
Broadcast new data and the chart updates in place. Replacing the whole node disconnects the controller and redraws from zero; updating data avoids flicker.
Give the live chart a stable id. The wrapper holds that id; the inner chart id can change on render.
Data for chart rui-chart-6ce16695| Category | Value |
|---|
| 10:00 | 18 |
| 10:05 | 24 |
| 10:10 | 21 |
| 10:15 | 32 |
| 10:20 | 28 |
| 10:25 | 37 |
| 10:30 | 34 |
| 10:35 | 41 |
Updated just now
Sign in to view
This Pro source is available to active Rails UI Full Access subscribers.
Pass the same type: and options used on the original chart. The stream helper builds the next series through the same options builder.
The live controller listens for a railsui-chart:data DOM event. Turbo Streams are one way to send it; ActionCable, polling, or your own JavaScript can dispatch the same event.