Adding a dark mode button to your Ghost blog

Can you turn your theme dark? Yes, and it might be easy.

A ghost saying 'boo'!
Alwasy adorable Ghosts from the most excellent Aletheia Shade

If you are super lucky, your Ghost theme came with a dark mode slider. You can stop reading now, if so!

If you are slightly less lucky, your Ghost theme is set up to use dark mode, but is just missing a slider to let users pick. That's part 1, below.

Another possibility is that your Ghost theme uses color names consistently and can be made to go dark pretty easily. (Part 2.) And at worst, you'll be chasing down hard-coded colors. What's the situation with your theme? See the table at the end of the post to find out!

Part 1: Add the slider

How to create the slider? Here's my favorite code, which goes into partials/darkmode.hbs


  <div>
  <input type="checkbox" class="dm-checkbox" id="dm-checkbox">
  <label for="dm-checkbox" class="dm-checkbox-label">
    <ico>🌙</ico>
    <ico>☀️</ico>
    <span class="ball"></span>
  </label>
</div>

<script>
const checkbox = document.getElementById("dm-checkbox")
checkbox.addEventListener("change", () => {
  document.body.classList.toggle("dark")
  // note: if your theme needs a different class added, adjust above (change "dark")
  // note: if your theme needs the class added to the HTML element instead, do the line below instead (be sure to uncomment):
  // document.querySelector('html').classList.toggle("dark")
  document.cookie = `darkmode-prefered=${checkbox.checked};path=/`
})

function getCookie(cname) {
  let name = cname + "=";
  let decodedCookie = decodeURIComponent(document.cookie);
  let ca = decodedCookie.split(';');
  for(let i = 0; i <ca.length; i++) {
    let c = ca[i];
    while (c.charAt(0) == ' ') {
      c = c.substring(1);
    }
    if (c.indexOf(name) == 0) {
      return c.substring(name.length, c.length);
    }
  }
  return "none";
}
function checkCookie() {
  let preference = getCookie("darkmode-prefered");
  if (preference != "none") {
    if (preference == "true") { 
        checkbox.checked = true;
        document.body.classList.add("dark")
        // document.querySelector('html').classList.add("dark")
        } else {
        checkbox.checked = false;
        document.body.classList.remove("dark")
        // document.querySelector('html').classList.remove("dark")

        }
  } else {
    if (window.matchMedia("(prefers-color-scheme:dark)").matches) {
        checkbox.checked = true;
        document.body.classList.add("dark")
        // document.querySelector('html').classList.add("dark")

    } else {
        checkbox.checked = false;
        document.body.classList.remove("dark")
        // document.querySelector('html').classList.remove("dark")

    }
    
  }
}
checkCookie()
</script>

You'll want to call this new partial ( {{> darkmode }} ) where you want the darkmode slider to be, probably in the header. (I like it next to the search button, personally.) I'm choosing to inline this code, because I want it to run as early as possible, so that the page doesn't flicker light to dark in a noticeable way.

The code above adds 'dark' to the body of the page, if the user has dark mode turned on. If you're lucky and your theme respects a 'dark' class on the body, you're done!

Update: You'll also need the CSS below:

.dm-checkbox {
    opacity: 0;
    position: absolute;
    width: fit-content;
  }
  
  .dm-checkbox-label {
    background-color: var(--light-gray-color);
    width: 50px;
    height: 26px;
    border-radius: 50px;
    position: relative;
    padding: 5px;
    cursor: pointer;
    display: flex;
    justify-content: space-between;
    align-items: center;
  }
  .home-template .dm-checkbox-label {
    background-color: #1a1a1a;
  }
  
  .dm-checkbox-label .ball {
    background-color: #fff;
    width: 22px;
    height: 22px;
    position: absolute;
    left: 2px;
    top: 2px;
    border-radius: 50%;
    transition: transform 0s linear;
    
  }
  
  .dm-checkbox:checked + .dm-checkbox-label .ball {
    transform: translateX(24px);
  }

Part 2: What if your theme doesn't know about dark mode?

If your theme uses color variables fairly consistently, you can add a dark mode version pretty easily. It'll look something like this (assumes class=dark on the body):

body.dark {
    --primary-text-color: #ccc;
    --secondary-text-color: #aaa;
    --white-color: #000;
    --background-color: var(--white-color);
    --color-white: #000; /* Yes, this theme has white-color and color-white */
    --lighter-gray-color: #1a1a1a;
    --light-gray-color: #666;
    --color-primary-text: #ccc;
    --color-darker-gray: #aaa;
    --darker-gray-color: #aaa;
    --mid-gray-color: #ccc;
    --dark-gray-color: #e6e6e6;
    --darker-gray-color: #f6f6f6;
    --black-color: #fff;

The variables above overwrite the theme's own variables (so you'll have to adjust accordingly). Your theme almost certainly uses different variables, but the big idea is that you set your theme's "white color" to black (#000 or close) and the "black color" to white (#fff or close). And then you change the grays so that dark grey is light gray, and so on.

This goes into one of your css files (be sure to compile, if your theme needs it), or into code injection head, wrapped in <style> </style>.

If your theme is super consistent about using color variables, adding dark mode might be pretty quick. If it turns out that your theme creator has things hard-coded all over the place, you may be playing whack-a-mole to find all the other values that need to change, too.

Addendum: What themes work with dark mode?

Theme Has slider? Needs class added? Other notes?
Casper no add class='dark-mode' to html (not body - see comments) as of 5.64.0. (Sept 2023)
Dawn no, but you can set light/dark/auto add class="theme-dark" to html as of Sept 2023
Solo no add class="has-light-text" to html, AND change the value of --background-color as of Sept 2023
Edition no not set up - see part 2 as of Sept 2023
Source no add class="has-light-text" to html, AND change the value of --background-color as of Oct 2023
Ruby no no, use variables (method used on this page) as of July, 2023
Headline no no, use variables see https://mark.stosberg.com/my-ghost-headline-theme-fork-with-dark-mode/
Tripoli (paid) yes nothing needed! has a dark mode button in the header

I'd love to add more themes, whether free or paid - won't you please add them in the comments below?

Shout-out

Cuong has a set of directions for a code-injection-only dark mode for Casper:

The dark mode toggle for Casper theme without modifying it
The following is a guide to help you create a dark mode toggle for the Casper theme without having to edit it.

That's all for today! Coming next week: Fixing Ghost sites with CLS problems on Google Search Console to avoid "Page Experience" problems.


Hey, before you go... If your finances allow you to keep this tea-drinking ghost and the freelancer behind her supplied with our hot beverage of choice, we'd both appreciate it!