Home Blog How to Add Dark Mode to Your Jekyll Site
Tutorial

How to Add Dark Mode to Your Jekyll Site

Add a dark mode toggle to any Jekyll site — using CSS variables, a JavaScript toggle, and localStorage to remember the user's preference.

How to Add Dark Mode to Your Jekyll Site

Dark mode is no longer optional — it’s expected. This guide shows you how to add a proper dark mode toggle to any Jekyll site, with the user’s preference saved across visits.


The Approach

We will use three techniques together:

  1. CSS custom properties (variables) — define colours once, swap them all at once
  2. JavaScript toggle — switch modes on button click
  3. localStorage — remember the user’s choice on return visits
  4. prefers-color-scheme media query — respect the OS-level dark mode preference on first visit

Step 1: Restructure Your CSS with Variables

Instead of hard-coding colours, define them as CSS custom properties:

/* _sass/_variables.scss */

:root {
  --bg-color: #ffffff;
  --text-color: #1a1a1a;
  --text-muted: #6b7280;
  --border-color: #e5e7eb;
  --card-bg: #f9fafb;
  --link-color: #2563eb;
  --heading-color: #111827;
  --code-bg: #f3f4f6;
}

[data-theme="dark"] {
  --bg-color: #0f172a;
  --text-color: #e2e8f0;
  --text-muted: #94a3b8;
  --border-color: #334155;
  --card-bg: #1e293b;
  --link-color: #60a5fa;
  --heading-color: #f1f5f9;
  --code-bg: #1e293b;
}

Now use these variables throughout your stylesheets:

body {
  background-color: var(--bg-color);
  color: var(--text-color);
}

a {
  color: var(--link-color);
}

h1, h2, h3 {
  color: var(--heading-color);
}

When you switch from [data-theme="light"] to [data-theme="dark"] on the <html> element, every colour updates instantly with no additional CSS.


Step 2: Respect the System Preference

Users who have set their OS to dark mode expect your site to default to dark. Use the prefers-color-scheme media query:

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg-color: #0f172a;
    --text-color: #e2e8f0;
    --text-muted: #94a3b8;
    --border-color: #334155;
    --card-bg: #1e293b;
    --link-color: #60a5fa;
    --heading-color: #f1f5f9;
    --code-bg: #1e293b;
  }
}

The :not([data-theme="light"]) selector means: use dark colours unless the user has explicitly chosen light mode via the toggle.


Step 3: Add the Toggle Button

In your header HTML (e.g. _includes/header.html):

<button id="theme-toggle" aria-label="Toggle dark mode" class="theme-toggle">
  <span class="theme-toggle__sun">☀️</span>
  <span class="theme-toggle__moon">🌙</span>
</button>

Style it to show the sun in dark mode and the moon in light mode:

.theme-toggle {
  background: none;
  border: 1px solid var(--border-color);
  border-radius: 50%;
  width: 36px;
  height: 36px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1rem;
  transition: border-color 0.2s;

  &:hover {
    border-color: var(--link-color);
  }
}

/* Show sun in dark mode (to switch to light), moon in light mode (to switch to dark) */
[data-theme="dark"] .theme-toggle__moon { display: none; }
[data-theme="dark"] .theme-toggle__sun { display: inline; }
.theme-toggle__sun { display: none; }
.theme-toggle__moon { display: inline; }

Step 4: Write the JavaScript

Create assets/js/theme-toggle.js:

(function() {
  const STORAGE_KEY = 'theme';
  const DARK = 'dark';
  const LIGHT = 'light';

  function getPreferredTheme() {
    // Check localStorage first
    const stored = localStorage.getItem(STORAGE_KEY);
    if (stored) return stored;

    // Fall back to OS preference
    return window.matchMedia('(prefers-color-scheme: dark)').matches ? DARK : LIGHT;
  }

  function applyTheme(theme) {
    document.documentElement.setAttribute('data-theme', theme);
  }

  // Apply theme immediately (before page renders) to prevent flash
  applyTheme(getPreferredTheme());

  // Set up toggle button after DOM loads
  document.addEventListener('DOMContentLoaded', function() {
    const button = document.getElementById('theme-toggle');
    if (!button) return;

    button.addEventListener('click', function() {
      const current = document.documentElement.getAttribute('data-theme');
      const next = current === DARK ? LIGHT : DARK;
      applyTheme(next);
      localStorage.setItem(STORAGE_KEY, next);
    });
  });
})();

Important: Load this script in <head> to prevent a flash of the wrong theme:


<!-- _includes/head.html -->
<script src="{{ '/assets/js/theme-toggle.js' | relative_url }}"></script>

Loading it in <head> (not deferred) means the theme is applied before the page renders — no white flash before dark mode kicks in.


Step 5: Handle Images in Dark Mode

Light-background images can look jarring in dark mode. Options:

Option A: Reduce opacity for images in dark mode:

[data-theme="dark"] img:not([src*=".svg"]) {
  opacity: 0.9;
}

Option B: Use the <picture> element to serve different images per mode:

<picture>
  <source srcset="/assets/images/logo-dark.png" media="(prefers-color-scheme: dark)">
  <img src="/assets/images/logo-light.png" alt="Logo">
</picture>

Option C: Use SVGs with currentColor for icons and logos — they automatically adapt to the text colour.


Step 6: Dark Mode for Syntax Highlighting

Jekyll uses Rouge or Pygments for syntax highlighting. You need a dark colour scheme.

Add to your _config.yml:

highlighter: rouge

Then download a dark theme for Rouge. The Base16 Ocean Dark theme is a popular choice. Add it to _sass/:

/* _sass/_syntax-dark.scss */
[data-theme="dark"] .highlight {
  background: #2b303b;
  color: #c0c5ce;
}

[data-theme="dark"] .highlight .k  { color: #b48ead; } /* keyword */
[data-theme="dark"] .highlight .s  { color: #a3be8c; } /* string */
[data-theme="dark"] .highlight .c  { color: #65737e; } /* comment */
[data-theme="dark"] .highlight .n  { color: #c0c5ce; } /* name */
[data-theme="dark"] .highlight .o  { color: #8fa1b3; } /* operator */

Testing Your Dark Mode

  1. Open your site, toggle dark mode — all colours should switch cleanly
  2. Refresh the page — dark mode should persist (localStorage is working)
  3. Open DevTools → Application → Local Storage — you should see theme: dark
  4. Set your OS to dark mode and visit the site for the first time in an incognito window — it should default to dark

Dark Mode in Jekyll Themes

Many modern Jekyll themes already include dark mode. If you would rather use a theme that has it built in, check out Chirpy (seamless dark/light toggle) or browse all themes with dark mode support on JekyllHub.

Share LinkedIn