Crafting a Dynamic Dark Mode for Minimal Mistakes
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