G Gentelella v4.0.0

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 propertiessrc/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.

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.scss hardcodes 480px / 768px / 1024px / 1280px. Changing these is a structural change, not a theme.
  • Sidebar widths--sidebar-w: 252px is a token, but the rail-mode width (76px) is hardcoded in _layout.scss.
  • Animation timings_components.scss uses 200ms / 240ms ease per-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.