Charts
Adminator ships Chart.js 4.5 integrated with the design token system. Seven seeded chart examples come pre-themed, and adding a new chart is one seed function plus one canvas element. All charts re-render automatically on theme toggle.
Last updated May 21, 2026
The 2026 redesign wraps real Chart.js 4.5 with a thin layer that:
- Reads CSS variables for colors instead of hard-coded hex
- Re-instantiates every chart when the theme changes
- Lets you declare new charts as
<canvas data-chart-key="...">plus one seed function
The integration lives in src/assets/scripts/2026/charts.js — about 260 lines, including all six demo seeds.
How a chart gets on the page
Two pieces. First, the HTML:
<div class="chart-canvas-wrap">
<canvas data-chart-key="revenue-line"></canvas>
</div>
Then, a seed function in charts.js:
export const SEEDS = {
'revenue-line': (t) => ({
type: 'line',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
datasets: [{
label: 'Revenue',
data: [42, 56, 50, 78, 88, 96],
borderColor: t.primary,
backgroundColor: `${t.primary}20`,
tension: 0.35,
fill: true,
pointRadius: 0,
pointHoverRadius: 5,
borderWidth: 2.5,
}],
},
options: { /* ... */ },
}),
// ...
};
When the page loads, the charts module:
- Reads CSS variables via
tokens()→ returns{ primary, success, danger, text, border, ... } - Calls
Chart.defaults.font.family = 'Inter', etc. — sets shared defaults - Finds every
<canvas data-chart-key="...">element - Looks up the matching seed in
SEEDS, calls it with the tokens - Instantiates a new
Chartwith the returned config
On theme change, a MutationObserver destroys all instances and runs the whole sequence again with the new tokens.
The tokens() function
The bridge between CSS variables and Chart.js:
function tokens() {
const cs = getComputedStyle(document.documentElement);
return {
primary: cs.getPropertyValue('--primary').trim(),
success: cs.getPropertyValue('--success').trim(),
danger: cs.getPropertyValue('--danger').trim(),
warning: cs.getPropertyValue('--warning').trim(),
info: cs.getPropertyValue('--info').trim(),
purple: cs.getPropertyValue('--purple').trim(),
pink: cs.getPropertyValue('--pink').trim(),
orange: cs.getPropertyValue('--orange').trim(),
teal: cs.getPropertyValue('--teal').trim(),
text: cs.getPropertyValue('--t-base').trim(),
muted: cs.getPropertyValue('--t-muted').trim(),
light: cs.getPropertyValue('--t-light').trim(),
border: cs.getPropertyValue('--border').trim(),
soft: cs.getPropertyValue('--border-soft').trim(),
bg: cs.getPropertyValue('--bg-card').trim(),
};
}
If you add a custom palette token in _tokens.scss (see Theming) and want it in your charts, add it here too.
Transparency without rgba()
Chart.js expects hex colors for borderColor but rgba for backgroundColor (so the fill is translucent). The pattern in the seeds:
borderColor: t.primary,
backgroundColor: `${t.primary}20`, // 20 = ~12% opacity in 2-digit hex
The 8-character hex format (#RRGGBBAA) lets you append an alpha without converting to rgba(). Cheat sheet:
10≈ 6%20≈ 12%40≈ 25%80≈ 50%C0≈ 75%FF= 100%
Most modern browsers support this (Chrome 62+, Firefox 49+, Safari 9.1+).
Adding a new chart
Three steps:
1. Add a canvas to your page
<div class="card">
<div class="card-head">
<h3>Active sessions</h3>
</div>
<div class="chart-canvas-wrap">
<canvas data-chart-key="sessions-bar"></canvas>
</div>
</div>
.chart-canvas-wrap gives the canvas a sensible aspect ratio and minimum height — defined in _charts.scss.
2. Add a seed
In charts.js, append to SEEDS:
'weekly-sessions': (t) => ({
type: 'bar',
data: {
labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
datasets: [
{
label: 'Desktop',
data: [124, 168, 192, 184, 210, 156, 142],
backgroundColor: t.primary,
borderRadius: 4,
borderSkipped: false,
},
{
label: 'Mobile',
data: [82, 96, 118, 124, 140, 168, 174],
backgroundColor: t.teal,
borderRadius: 4,
borderSkipped: false,
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: { grid: { display: false }, ticks: { color: t.muted } },
y: { grid: { color: t.soft }, ticks: { color: t.muted } },
},
plugins: {
legend: { display: true },
},
},
}),
3. That’s it
Refresh the page. The canvas picks up the seed by its data-chart-key. Theme toggle works automatically — the init code observes data-theme on <html> via MutationObserver, then destroys and re-instantiates every Chart instance with fresh tokens when it changes.
Re-using a seed across multiple canvases
Seed functions take a single argument — the tokens object — so the same key drives every canvas with that data-chart-key. Two canvases pointing at data-chart-key="revenue-line" will render the same chart twice.
If you need per-instance variation (different data, different color), the simplest pattern is one seed per variant:
'kpi-revenue': (t) => ({ type: 'line', data: { datasets: [{ data: [42, 56, 50, 78], borderColor: t.primary }] }, options: { /* ... */ } }),
'kpi-users': (t) => ({ type: 'line', data: { datasets: [{ data: [12, 14, 18, 22], borderColor: t.success }] }, options: { /* ... */ } }),
This trades a little duplication for clarity — each seed is self-contained, and the init code stays simple. If you have many KPI sparklines and want a factory pattern instead, that’s a fork-and-customize from the existing initCharts() in charts.js.
Chart types included
All six demos live in src/charts.html, plus the monthly line chart on the dashboard. Use them as starting points.
| Chart | Seed key | Use case |
|---|---|---|
| Line + area fill (revenue) | revenue-line | Trend over time, comparison vs. previous period |
| Bar (grouped — channels) | channels-bar | Categorical comparison across channels |
| Doughnut (devices) | devices-doughnut | Percentage breakdown of a small set (3–5 slices) |
| Radar (traffic sources) | sources-radar | Multi-axis comparison |
| Stacked bar (MRR breakdown) | mrr-stacked | Part-to-whole over time |
| Area filled (sessions) | sessions-area | Smooth continuous trends |
| Dashboard monthly line | dashboard-monthly | The featured chart on index.html |
Default styling
applyDefaults() in charts.js sets globally:
Chart.defaults.font.family = "'Inter', system-ui, sans-serif";
Chart.defaults.font.size = 12;
Chart.defaults.color = t.muted;
Chart.defaults.borderColor = t.soft;
Chart.defaults.plugins.legend.position = 'bottom';
Chart.defaults.plugins.legend.labels.usePointStyle = true;
Chart.defaults.plugins.legend.labels.padding = 16;
Chart.defaults.plugins.tooltip.backgroundColor = t.text;
Chart.defaults.plugins.tooltip.titleColor = t.bg;
Chart.defaults.plugins.tooltip.bodyColor = t.bg;
Chart.defaults.plugins.tooltip.cornerRadius = 6;
Chart.defaults.plugins.tooltip.displayColors = false;
This is what gives every chart in the template the same legend style, the same tooltip look, the same grid line color. If you want a different default — say a top-positioned legend — change it here once instead of per-seed.
A note on bundle size
Chart.js plus its registerables (all controllers + scales + elements registered) adds ~180 KB minified to the bundle. Adminator’s webpack config splits it into vendor-chartjs.js so pages without charts can lazy-load it.
If you’re tight on bundle size and only use one or two chart types, you can import just those controllers instead of registerables:
// Smaller bundle — line and bar only
import { Chart, LineController, BarController, LineElement, BarElement,
PointElement, CategoryScale, LinearScale, Tooltip, Legend, Filler } from 'chart.js';
Chart.register(LineController, BarController, LineElement, BarElement,
PointElement, CategoryScale, LinearScale, Tooltip, Legend, Filler);
This drops ~80 KB.