A Adminator v4.1.4

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 MutationObserver in charts.js / calendar.js / maps.js fires 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.scss at 480px / 720px / 1100px / 1200px. Changing these is a structural change, not a theme.
  • Sidebar widths248px desktop, 72px rail-mode, 280px mobile 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 ease or 240ms 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.