Theming
Every color, radius, and shadow in Gentelella lives in _tokens.scss as a CSS custom property. Edit one variable, the whole template — including charts, DataTables, and the calendar — restyles instantly. No SCSS recompile needed at runtime.
Last updated May 22, 2026
Gentelella’s design system is a single SCSS file of CSS custom properties — src/scss/v4/_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.
This means:
- Light/dark mode is one attribute swap on
<html>— no class toggling, no CSS recompile - Customizing the brand color is editing a handful of lines
- ECharts, DataTables, and Leaflet all read the same tokens and re-render on theme change
The token file
Open src/scss/v4/_tokens.scss. The DNA comment at the top tells you what to expect:
// Gentelella 2026 v4 — Design tokens
// DNA: Dark sidebar · Teal #1ABB9C · Light #f9fafb
// Font: Inter · 14px base · line-height 1.4286
// Radius: 4/6/8 · Shadows: near-invisible
:root {
/* Sidebar — dark navy by default */
--sidebar-bg: #1a2332;
--sidebar-hover: rgba(255,255,255,0.04);
--sidebar-active: rgba(26,187,156,0.08);
--sidebar-text: #7b8fa3;
--sidebar-text-hover:#c5d0dc;
--sidebar-text-active:#ffffff;
--sidebar-border: rgba(255,255,255,0.06);
--sidebar-w: 252px;
/* Brand — the iconic Gentelella teal */
--primary: #1ABB9C;
--primary-lt: rgba(26,187,156,0.06);
--primary-dk: #169f85;
/* Surfaces */
--body-bg: #f5f7fb;
--bg-surface: #ffffff;
--bg-surface-secondary: #f9fafb;
/* Borders */
--border-color: #e6e7eb;
--border-color-light: #eff0f3;
--border-translucent: rgba(4,32,69,0.08);
/* Text */
--text: #1e2633;
--text-secondary: #626d7d;
--text-muted: #7e8896;
--text-disabled: #c0c7cf;
/* Accent palette — used by charts, tags, avatars, badges */
--blue: #066fd1;
--azure: #4299e1;
--green: #2fb344;
--lime: #74b816;
--yellow: #f59f00;
--orange: #f76707;
--red: #d63939;
--pink: #d6336c;
--purple: #ae3ec9;
--indigo: #4263eb;
--cyan: #17a2b8;
/* Soft variants — 6% alpha versions of the accents, for backgrounds */
--green-lt: rgba(47,179,68,0.06);
--red-lt: rgba(214,57,57,0.06);
--yellow-lt: rgba(245,159,0,0.06);
/* ... */
/* Radii */
--radius: 6px;
--radius-sm: 4px;
--radius-lg: 8px;
}
Below that, dark-mode overrides under :root[data-theme="dark"] redefine the same token names with dark-mode values.
Changing the brand color
The template is built around --primary. Change one variable and the brand color updates across every button, link, focus ring, chart line, sidebar highlight, and badge — instantly.
Say you want an indigo brand. Edit _tokens.scss:
:root {
--primary: #4263eb;
--primary-lt: rgba(66,99,235,0.06);
--primary-dk: #2747c4;
}
Three lines. That’s it.
If you also want the sidebar’s “active link” tint to match (it defaults to the teal-flavored highlight), update that too:
:root {
--sidebar-active: rgba(66,99,235,0.08); /* was rgba(26,187,156,0.08) */
}
For a try-before-you-commit experience, use the live theme generator — pick a color on production/theme.html, watch the whole template restyle, then copy the generated :root block back into _tokens.scss.
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 {
--accent-coral: #FF6B6B;
}
:root[data-theme="dark"] {
--accent-coral: #FF8787;
}
/* _components.scss (or wherever you need it) */
.my-component {
background: var(--accent-coral);
}
If you want the new token in charts, also add it to the tokens() function in src/v4/charts.js:
const tokens = () => {
const cs = getComputedStyle(document.documentElement);
return {
primary: cs.getPropertyValue('--primary').trim(),
accentCoral: cs.getPropertyValue('--accent-coral').trim(), // ← add here
// ...
};
};
The chart factories re-evaluate tokens() on every render. When the theme toggles, the resize-aware re-render path picks up the new value automatically. See the ECharts integration page for the full pattern.
How the theme toggle works
Two pieces of machinery:
1. Pre-paint script — injected by the Vite plugin into every page’s <head>:
<script>
(function () {
try {
var t = localStorage.getItem('theme');
var d = window.matchMedia('(prefers-color-scheme: dark)').matches;
var theme = t || (d ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', theme);
} catch (e) {}
})();
</script>
Runs synchronously before CSS arrives. Dark-mode users see dark styling on the first paint — no flash.
2. Runtime toggle — in src/v4/shell.js, attached to the topbar moon icon:
function toggleTheme() {
const cur = document.documentElement.getAttribute('data-theme') || 'light';
const next = cur === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', next);
try { localStorage.setItem('theme', next); } catch (_e) {}
}
That setAttribute triggers:
- All CSS variables resolve to their dark-mode values immediately
- Charts re-render on the next resize event (the rendered DOM doesn’t change — only the colors via getComputedStyle do)
- DataTables and other components inherit naturally because their CSS is variable-driven
Setting the theme programmatically
If you want to set the theme from elsewhere — say a settings menu — match the pattern:
function setTheme(name /* 'light' | 'dark' */) {
document.documentElement.setAttribute('data-theme', name);
try { localStorage.setItem('theme', name); } catch (_e) {}
}
No event to dispatch, no chart-rebuild call needed — the library integrations all read tokens at render time, so the next chart resize or DataTable repaint picks up the new colors.
Sidebar palette overrides
The sidebar has its own token group prefixed --sidebar-* so you can theme it independently from the rest of the chrome. Common patterns:
Light sidebar (instead of dark navy):
:root {
--sidebar-bg: #ffffff;
--sidebar-text: #626d7d;
--sidebar-text-hover: #1e2633;
--sidebar-text-active: var(--primary);
--sidebar-border: var(--border-color-light);
--sidebar-hover: var(--bg-surface-secondary);
--sidebar-active: var(--primary-lt);
}
Brand-colored sidebar (uses primary color as background):
:root {
--sidebar-bg: var(--primary-dk);
--sidebar-text: rgba(255,255,255,0.6);
--sidebar-text-hover: rgba(255,255,255,0.9);
--sidebar-text-active: #ffffff;
--sidebar-active: rgba(255,255,255,0.08);
}
Both options are wired into the theme generator’s sidebar style picker — visit production/theme.html to preview them live.
Adjusting density
The layout uses Inter at 14px with line-height: 1.4286 — close to but not identical to Tailwind’s defaults. Spacing tokens follow a 4px base scale: --space-4 is 16px (matching Tailwind’s p-4).
To make the UI denser, the simplest path is reducing the body font size in _tokens.scss:
:root {
--font-size-base: 13px; /* was 14px */
}
Everything proportional follows — spacing tokens are mostly rem-based off the body font size. Charts and tables get slightly smaller too because their font-family default is 'Inter' and their sizing uses 1em-relative units in the wrapper SCSS.
What’s deliberately not parameterized
A few things stay as constants because tweaking them has cascading layout implications:
- Breakpoints —
_layout.scsshardcodes 480px / 768px / 1024px / 1280px. Changing these is a structural change, not a theme. - Sidebar widths —
--sidebar-w: 252pxis a token, but the rail-mode width (76px) is hardcoded in_layout.scss. - Animation timings —
_components.scssuses200ms/240ms easeper-component; not abstracted to tokens.
These are deliberately not tokens because the design system doesn’t try to make them safe to change at runtime. If you need to adjust them, edit the partial directly.