> ## Content Index
> Fetch the complete content index at: https://dees.co/llms.txt
> Use this file to discover other available public pages before exploring further.

# Add dark mode to Ghost's Casper theme
- URL: https://dees.co/adding-a-dark-mode-toggle-to-ghosts-casper-theme/
- Published: 2026-09-01T15:27:46.000Z
- Updated: 2026-09-10T13:02:32.000Z
- Description: Restore dark mode to Ghost's Casper theme with Code Injection; ending with a persistent Light/Dark/Auto toggle that survives flashes, private windows, and OS theme changes.
- Author: Garrett Dees
- Tags: Ghost

Casper's built-in dark mode disappeared in Ghost 4.0 and never came back. These recipes restore it, from a one-liner to a full Light/Dark/Auto toggle. Each section is a standalone approach; if you install the toggle in the last section, skip the others.

## Force Dark mode on every page

Add the following to **Settings > Advanced > Code Injection > Site header**:

```HTML
<script>
  document.documentElement.classList.add('dark-mode');
</script>
```

## Set light or dark mode depending on operating system preference

Add the following to **Settings > Advanced > Code Injection > Site header**:

```HTML
<script>
  if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches)
    document.documentElement.classList.add('dark-mode');
</script>
```

## Dynamically update light or dark mode depending on operating system preference

Add the following to **Settings > Advanced > Code Injection > Site header**:

```HTML
<script>
  const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');

  function updateTheme(e) {
    if (e.matches)
      document.documentElement.classList.add('dark-mode');
    else
      document.documentElement.classList.remove('dark-mode');
  }

  updateTheme(prefersDark);
  prefersDark.addEventListener('change', updateTheme);
</script>
```

## Add a toggle switch to pick Light, Dark, or Auto

### Step 1: Add the Logic (Site Header)

This script runs instantly, before the page paints, to prevent a bright "flash" if the user has selected dark mode. The key detail is that the stored preference is read from `localStorage` **synchronously, in the same block** as the class assignment, not deferred to `DOMContentLoaded`. Reading it later would reintroduce exactly the flash this script exists to prevent. The read is also wrapped in `try/catch`, because browsers in private-browsing mode can throw on `localStorage` access. The storage key (`casper-theme-pref`) must match the one written by the footer script in Step 2; if they disagree, returning visitors get a flash before the footer script corrects the theme.

1. Go to **Settings** \> **Advanced** \> **Code Injection**.
2. Paste this code into the **Site Header** block:

```HTML
<style>
  .theme-toggle-container {
    display: inline-flex;
    align-items: center;
    background: #f4f5f6;
    border: 1px solid #e0e0e0;
    padding: 2px;
    border-radius: 20px;
    margin-right: 10px;
    gap: 1px;
    height: 28px;
    vertical-align: middle;
  }

  html.dark-mode .theme-toggle-container {
    background: #202327;
    border-color: #373a40;
  }

  .theme-toggle-btn {
    background: transparent;
    border: none;
    cursor: pointer;
    font-size: 11px;
    padding: 0 8px;
    height: 22px;
    border-radius: 12px;
    color: #747880;
    transition: all 0.2s ease;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .theme-toggle-btn.active {
    background: #ffffff;
    color: #15171a;
    box-shadow: 0 1px 3px rgba(0,0,0,0.1);
  }

  html.dark-mode .theme-toggle-btn.active {
    background: #373a40;
    color: #ffffff;
  }

  .gh-head-actions-list {
    display: flex !important;
    align-items: center !important;
  }
</style>

<script>
  (function () {
    var saved = null;
    try { saved = localStorage.getItem('casper-theme-pref'); } catch (e) {}

    var dark =
      saved === 'dark' ||
      (saved !== 'light' &&
        window.matchMedia &&
        window.matchMedia('(prefers-color-scheme: dark)').matches);

    if (dark) document.documentElement.classList.add('dark-mode');
  })();
</script>

```

### Step 2: Add the Visual Toggle (Site Footer)

This block injects the physical button interface onto your site and hooks up the clicking behavior. Each button carries both a `title` and an `aria-label` so its purpose is available to screen readers and keyboard focus, not just mouse hover. Emoji alone is announced inconsistently by assistive tech and renders differently across platforms. The active button mirrors its state with `aria-pressed`, and the group is exposed as a labelled control group (`role="group"`) for keyboard and screen-reader navigation.

1. Scroll down to the **Site Footer** block in Code Injection.
2. Paste the following HTML and JavaScript:

```HTML
<script>
  document.addEventListener('DOMContentLoaded', () => {
    // 1. Build the HTML block for the toggle element dynamically
    const toggleMarkup = `
      <div class="theme-toggle-container" role="group" aria-label="Theme">
        <button class="theme-toggle-btn" data-theme="light" title="Light Mode" aria-label="Light mode">☀️</button>
        <button class="theme-toggle-btn" data-theme="dark" title="Dark Mode" aria-label="Dark mode">🌙</button>
        <button class="theme-toggle-btn" data-theme="auto" title="Auto (System)" aria-label="Auto (system theme)">💻</button>
      </div>
    `;

    // 2. Locate Casper's native header action layout tree
    const targetMenu = document.querySelector('.gh-head-actions-list') || document.querySelector('.gh-head-actions');

    if (targetMenu) {
      // Injects the capsule right at the beginning of the list, perfectly preceding search
      targetMenu.insertAdjacentHTML('afterbegin', toggleMarkup);
    } else {
      // Safe fallback option if custom headers are toggled
      document.body.insertAdjacentHTML('beforeend', `<div class="theme-toggle-container" style="position:fixed; bottom:20px; right:20px; z-index:9999;">${toggleMarkup}</div>`);
    }

    // 3. Setup core button click handling logic
    const buttons = document.querySelectorAll('.theme-toggle-btn');
    const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');

    var currentTheme = 'auto';
    try { currentTheme = localStorage.getItem('casper-theme-pref') || 'auto'; } catch (e) {}
    applyTheme(currentTheme);
    setActiveButton(currentTheme);

    buttons.forEach(button => {
      button.addEventListener('click', (e) => {
        e.preventDefault();
        const selectedTheme = button.getAttribute('data-theme');
        try { localStorage.setItem('casper-theme-pref', selectedTheme); } catch (err) {}
        applyTheme(selectedTheme);
        setActiveButton(selectedTheme);
      });
    });

    function applyTheme(theme) {
      if (theme === 'dark' || (theme === 'auto' && prefersDarkScheme.matches)) {
        document.documentElement.classList.add('dark-mode');
      } else {
        document.documentElement.classList.remove('dark-mode');
      }
    }

    function setActiveButton(theme) {
      buttons.forEach(btn => {
        if (btn.getAttribute('data-theme') === theme) {
          btn.classList.add('active');
          btn.setAttribute('aria-pressed', 'true');
        } else {
          btn.classList.remove('active');
          btn.setAttribute('aria-pressed', 'false');
        }
      });
    }

    prefersDarkScheme.addEventListener('change', () => {
      var pref = 'auto';
      try { pref = localStorage.getItem('casper-theme-pref') || 'auto'; } catch (e) {}
      if (pref === 'auto') {
        applyTheme('auto');
      }
    });
  });
</script>

```

1. Click **Save**.

*Design note:* the toggle's CSS uses fixed hex values rather than Casper's CSS variables. That's deliberate, since Casper's variable names aren't a stable public API and change between releases. The tradeoff is that the capsule's colors won't track future Casper accent or palette settings.

## Source

[Casper dark theme is gone in Ghost 4.0?Just saw new update and if I check demo there is no dark mode anymore? Why did you guys remove it? It was perfect. Is there any plans to bring it back?![](https://dees.co/content/images/icon/9c651e0e4bcfbcbad5e8473d123ba49b1547dfd9_2_32x32-47707199-a7b8-4417-914e-6479446f1256.ico)Ghost ForumJack\_Oakley![](https://dees.co/content/images/thumbnail/8d4e1be1543b3ed506f105953a0d062b84797e42-30e327e2-487c-4f9d-bc21-c5f58691013a.png)](https://forum.ghost.org/t/casper-dark-theme-is-gone-in-ghost-4-0/21037?ref=dees.co)