Theming
Every visual decision in Adminator lives in _tokens.scss as a CSS variable with light and dark variants. Change one token, the whole template updates — including Chart.js, FullCalendar, and the vector map.
Last updated May 21, 2026
Adminator’s design system is a single SCSS file of CSS custom properties — _tokens.scss. Every other partial references those tokens via var(--name) instead of hard-coding hex values. There is no @theme function, no SCSS color manipulation, no Bootstrap variable overrides. Just CSS variables.
This means:
- Light/dark mode is one attribute swap on
<html>— no class toggling, no CSS recompile - Customizing the brand color is editing one line
- Chart.js, FullCalendar, and the world map all read the same tokens and re-render on theme change
The token file
Open src/assets/styles/2026/_tokens.scss. Two top-level blocks: light and dark.
:root[data-theme="light"] {
/* Surfaces */
--bg-body: #F0F4F8;
--bg-card: #FFFFFF;
--bg-sidebar: #FFFFFF;
--bg-hover: #F8FAFC;
--bg-muted: #F1F5F9;
/* Text */
--t-base: #1E293B;
--t-muted: #64748B;
--t-light: #94A3B8;
--t-inverse: #FFFFFF;
/* Borders */
--border: #E4E8EF;
--border-soft: #EEF1F5;
/* Brand */
--primary: #2563EB;
--primary-light: #3B82F6;
--primary-dark: #1D4ED8;
--primary-soft: #EFF6FF;
--primary-ring: rgba(37, 99, 235, 0.18);
/* Semantic */
--success: #10B981; --success-soft: #ECFDF5;
--warning: #F59E0B; --warning-soft: #FFFBEB;
--danger: #EF4444; --danger-soft: #FEF2F2;
--info: #0EA5E9; --info-soft: #F0F9FF;
/* Accent palette (used by charts, tags, avatars) */
--purple: #8B5CF6; --purple-soft: #F5F3FF;
--pink: #EC4899; --pink-soft: #FDF2F8;
--teal: #14B8A6; --teal-soft: #F0FDFA;
--orange: #F97316; --orange-soft: #FFF7ED;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgb(15 23 42 / 0.04);
--shadow-card: 0 1px 3px 0 rgb(15 23 42 / 0.06), 0 1px 2px -1px rgb(15 23 42 / 0.04);
--shadow-lg: 0 10px 15px -3px rgb(15 23 42 / 0.08), 0 4px 6px -4px rgb(15 23 42 / 0.05);
}
:root[data-theme="dark"] {
/* Same token names with dark values */
--bg-body: #0B1120;
--bg-card: #141B2D;
--primary: #60A5FA;
/* ... */
}
The two blocks declare the same token names with different values. CSS picks up whichever block matches the data-theme attribute.
Changing the brand color
The whole template is built around --primary. Change one variable, the brand color updates across every button, link, focus ring, chart line, sidebar highlight, and the FullCalendar accent — instantly.
Say you want a teal brand. Edit _tokens.scss:
:root[data-theme="light"] {
--primary: #14B8A6; /* was #2563EB */
--primary-light: #2DD4BF;
--primary-dark: #0D9488;
--primary-soft: #F0FDFA;
--primary-ring: rgba(20, 184, 166, 0.18);
}
:root[data-theme="dark"] {
--primary: #2DD4BF;
--primary-light: #5EEAD4;
--primary-dark: #14B8A6;
--primary-soft: #0F2A26;
--primary-ring: rgba(45, 212, 191, 0.24);
}
Five tokens (in each theme). That’s it. No grep through 50 SCSS partials.
The accent variants (-light, -dark, -soft, -ring) are used by different components — e.g. -soft is the background for highlighted nav items and is-active states, -ring is the focus ring color. Picking sensible values for each (rather than letting them all be the same shade) gives the template visual depth.
Adding a new token
If you find yourself writing a hex value in a partial, that’s the cue — add a token instead.
/* _tokens.scss */
:root[data-theme="light"] { --accent-coral: #FF6B6B; }
:root[data-theme="dark"] { --accent-coral: #FF8787; }
/* _components.scss */
.my-component {
background: var(--accent-coral);
}
If charts or maps need the new token, also add it to the tokens() function in charts.js / maps.js so they pick it up at render time:
function tokens() {
const cs = getComputedStyle(document.documentElement);
return {
primary: cs.getPropertyValue('--primary').trim(),
accentCoral: cs.getPropertyValue('--accent-coral').trim(), // ← add here
};
}
The library integrations re-evaluate tokens() every time the theme changes — see the Charts and Calendar & Maps pages.
How the theme toggle works
Two pieces:
1. Early-paint script in every page’s <head> sets data-theme from localStorage (or prefers-color-scheme) before any CSS arrives. This prevents the flash of light styling that dark-mode users would otherwise see while CSS loads.
<script>
(function () {
var saved = localStorage.getItem('dash26-theme');
var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.setAttribute('data-theme', saved || (prefersDark ? 'dark' : 'light'));
})();
</script>
2. Runtime toggle in init.js flips the attribute and writes to localStorage:
function initThemeToggle() {
const root = document.documentElement;
const toggle = document.getElementById('themeToggle');
toggle.addEventListener('click', () => {
const next = root.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
root.setAttribute('data-theme', next);
localStorage.setItem('dash26-theme', next);
});
}
That setAttribute triggers:
- All CSS variables resolve to their dark-mode values
- A
MutationObserverincharts.js/calendar.js/maps.jsfires their rebuild functions, which read fresh tokens and re-instantiate Chart instances etc.
Toggling theme programmatically
If you want to set the theme from elsewhere — say a settings menu — replicate the toggle pattern:
function setTheme(name /* 'light' | 'dark' */) {
document.documentElement.setAttribute('data-theme', name);
localStorage.setItem('dash26-theme', name);
}
The library integrations will pick up the change automatically via their MutationObservers — you don’t need to call any chart-rebuild function manually.
Common customizations
Soften the corners. Adminator uses border-radius: 12px for cards and 8px for buttons. To match a softer brand, find the rules in _components.scss and _ui.scss and adjust. Most components use a small set of values (6px, 8px, 10px, 12px) — change them globally with a find-and-replace.
Adjust spacing density. The layout uses an 8px base unit — most spacing is 8px, 12px, 16px, 24px. To make the UI denser, find the gap/padding/margin declarations in _shell.scss and _components.scss and reduce by ~25%.
Replace the font. Inter / Inter Tight / JetBrains Mono load from Google Fonts in _base.scss. Replace the @import url(...) line with your fonts of choice and update the font-family declarations in _base.scss and _tokens.scss (if added).
What’s not in tokens
A few things deliberately not parameterized as tokens because they’re constants for the design:
- Breakpoints — hard-coded in
_responsive.scssat 480px / 720px / 1100px / 1200px. Changing these is a structural change, not a theme. - Sidebar widths —
248pxdesktop,72pxrail-mode,280pxmobile drawer. Hard-coded in_shell.scss. - Z-index scale — 5/10/20/50/90/100. Documented inline in
_shell.scss. - Animation timings — most are
200ms easeor240ms cubic-bezier(...). Hard-coded per-component.
These are deliberately not tokens because tweaking them has cascading layout implications that the design system doesn’t try to abstract away.