Crafting a Dynamic Dark Mode for Minimal Mistakes

2 minute read

Setting up a dark mode that feels “just right” involves a delicate balance between respecting system settings and giving the user manual control. For the Minimal Mistakes theme, which is largely static by nature, this requires a bit of creative plumbing with Sass and JavaScript.

With this setup, the site starts in the user’s preferred system mode, snaps to dark at sunset automatically, but still yields to the user’s manual choice via a simple button click. It’s a clean, modular approach that keeps the project organized while adding a modern touch to a classic theme.

The Core Strategy: CSS Variables

The biggest hurdle is that Jekyll themes usually “bake in” a single skin at build time. To bypass this, we shift from hardcoded hex values to CSS variables. By defining our colors as variables inside a Sass mixin, we create a “single source of truth” that can be toggled on the fly.

Step 1: The CSS Package

We centralize our styles in _sass/custom/dark_mode_toggle.scss. This file contains the instructions for what “dark” looks like.

Example:

@mixin dark-theme-styles {
  --bg-color: #252a34;
  --text-color: #eeeeee;
  --link-color: #00adb5;

  /* Handle specific elements like social icons */
  .social-icons,
  .social-icons a {
    color: inherit !important;
  }
}

html[data-theme="dark"] {
  @include dark-theme-styles;
}

@media (prefers-color-scheme: dark) {
  html:not([data-theme="light"]) {
    @include dark-theme-styles;
  }
}

By using html:not([data-theme="light"]) within the media query, we ensure the system default only kicks in if the user hasn’t explicitly chosen to stay in light mode.

Step 2: The JavaScript Logic

Next, we need a script to handle the “brain” of the operation. Saved at assets/js/dark_mode_toggle.js, this code listens for system changes and manages localStorage so the user’s preference persists across page loads.

const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)');
const savedTheme = localStorage.getItem('theme');

const applyTheme = (theme) => {
  document.documentElement.setAttribute('data-theme', theme);
};

// Initial load logic
if (savedTheme) {
  applyTheme(savedTheme);
} else {
  applyTheme(systemPrefersDark.matches ? 'dark' : 'light');
}

// Manual toggle function
window.switchTheme = function() {
  const current = document.documentElement.getAttribute('data-theme');
  const target = current === 'dark' ? 'light' : 'dark';
  applyTheme(target);
  localStorage.setItem('theme', target);
};

Step 3: Integration and Configuration

To make this feature manageable, we add a toggle in _config.yml: dark_mode_toggle: true.

Finally, we import these assets into our main files. In assets/css/main.scss, we use the Jekyll Sass load path to import our custom styles (remembering to skip the _sass prefix in the path):


@import "custom/dark_mode_toggle";

And in _includes/head/custom.html, we load our script:


<script src="/josephs-blog/assets/js/dark_mode_toggle.js"></script>

Step 4: The Interface

To finish the implementation, you need to place a button in your theme’s navigation bar that calls the switchTheme() function we wrote.

The best place for this in Minimal Mistakes is usually the masthead.html file, so the toggle appears right next to your search icon or navigation links.

Open (or copy to your local project) _includes/masthead.html. Look for the snippet ... of the search button and add this button after it:

<button class="theme-toggle-btn" onclick="switchTheme()">
  <i class="fas fa-moon"></i>
</button>

To make the button look like it belongs in the header, add these styles to your _sass/custom/dark_mode_toggle.scss file:

.theme-toggle-btn {
  background: none;
  border: none;
  cursor: pointer;
  color: inherit;
  font-size: 1.1em;
  padding: 0 10px;
  vertical-align: middle;
  outline: none;

  &:hover {
    opacity: 0.7;
  }
}

/* Update the icon when dark mode is active */
html[data-theme="dark"] .theme-toggle-btn i::before {
  content: "\f185"; /* FontAwesome Sun Icon code */
}

Using FontAwesome, we can make the icon context-aware: it shows a moon in light mode and a sun in dark mode via simple CSS content swapping.

Comments