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 October 13, 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 updates existing datasets and options with fresh tokens, then calls update('none') without recreating the chart.
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 accepts CSS color strings; an eight-digit hex value adds alpha to a token color. 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 updates existing Chart instances with fresh tokens using update('none').
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.
Loading and theme updates
initCharts() checks for canvas[data-chart-key] before dynamically importing vendor-chartjs.js. Pages without charts download no Chart.js code. The vendor entry registers only line, bar, doughnut, and radar controllers plus their elements, scales, legend, tooltip, and filler. Add registrations there when introducing another chart type.
The minified chart chunk is 180,520 bytes (61,503 gzip), 11.8% smaller than v4.3.0. Production filenames include content hashes.
Theme changes update the existing datasets and options with chart.update('none'). This keeps the chart instance and avoids destroying canvases or replaying animations. Regression tests in tests/charts-runtime.test.js verify that behavior.