Skip to content

Commit a394fe9

Browse files
committed
Add a dark/light mode toggle
In the preceding commit, I added support for a dark mode, heeding the operating system's indicated preference. However, many users will want to be able to switch from/to dark/light mode without switching the rest of their operating system. Guided by the excellent advice provided in https://css-tricks.com/a-complete-guide-to-dark-mode-on-the-web/ and in https://dev.to/ayc0/light-dark-mode-avoid-flickering-on-reload-1567, this commit introduces a button to toggle dark/light mode. To avoid flickering, the idea is to: - store the user preference in the local storage, - using Javascript, in the `<head>` section of the HTML page (so that it is executed _before_ anything is displayed), heed that stored user preference (if any) by setting the `theme` attribute in the `document`'s `dataset`, and finally - use that `theme` via CSS: :root[data-theme="dark"] { ... } Signed-off-by: Johannes Schindelin <[email protected]>
1 parent 7541745 commit a394fe9

File tree

7 files changed

+137
-3
lines changed

7 files changed

+137
-3
lines changed

assets/js/application.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const baseURLPrefix = (() => {
3131

3232
$(document).ready(function() {
3333
BrowserFallbacks.init();
34+
DarkMode.init();
3435
Search.init();
3536
Dropdowns.init();
3637
Forms.init();
@@ -543,6 +544,38 @@ var Downloads = {
543544
},
544545
}
545546

547+
var DarkMode = {
548+
init: function() {
549+
const button = $('#dark-mode-button');
550+
if (!button.length) return;
551+
552+
// Check for dark mode preference at the OS level
553+
const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)").matches;
554+
555+
// Get the user's theme preference from local storage, if it's available
556+
const currentTheme = localStorage.getItem("theme");
557+
558+
if ((prefersDarkScheme && currentTheme !== "light")
559+
|| (!prefersDarkScheme && currentTheme === "dark")) {
560+
button.attr("src", `${baseURLPrefix}images/light-mode.svg`);
561+
}
562+
button.show();
563+
564+
button.click(function(e) {
565+
e.preventDefault();
566+
let theme
567+
if (prefersDarkScheme) {
568+
theme = document.documentElement.dataset.theme === "light" ? "dark" : "light"
569+
} else {
570+
theme = document.documentElement.dataset.theme === "dark" ? "light" : "dark"
571+
}
572+
document.documentElement.dataset.theme = theme
573+
localStorage.setItem("theme", theme);
574+
button.attr("src", `${baseURLPrefix}images/${theme === "dark" ? "light" : "dark"}-mode.svg`);
575+
});
576+
},
577+
}
578+
546579
// Scroll to Top
547580
$('#scrollToTop').removeClass('no-js');
548581
$(window).scroll(function() {

assets/sass/application.scss

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,28 @@ pre {
5151
.d-flex{
5252
display: flex;
5353
}
54+
55+
#dark-mode-button {
56+
display: none;
57+
z-index: 10;
58+
position: absolute;
59+
top: 6px;
60+
right: -50px;
61+
width: 35px;
62+
background-color: transparent;
63+
text-decoration: none;
64+
}
65+
66+
@media (max-width: $default + 100) {
67+
#dark-mode-button {
68+
top: 41px;
69+
right: 5px;
70+
}
71+
}
72+
73+
@media (max-width: $default) {
74+
#dark-mode-button {
75+
top: 5px;
76+
right: 20px;
77+
}
78+
}

assets/sass/dark-mode.css

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@mixin mode($mode: light) {
1+
@mixin mode($mode: light, $theme: "") {
22
// Palette
33
$orange: #f14e32;
44
$blue: #009099;
@@ -31,7 +31,7 @@
3131
$main-bg: #333333;
3232
}
3333

34-
:root {
34+
:root#{$theme} {
3535
--orange: #{$orange};
3636
--orange-darker-5: #{darken($orange, 5%)};
3737
--blue: #{$blue};
@@ -60,7 +60,9 @@
6060
}
6161

6262
@include mode
63+
@include mode($mode: dark, $theme: '[data-theme="dark"]')
6364

6465
@media screen and (prefers-color-scheme: dark) {
6566
@include mode($mode: dark)
67+
@include mode($mode: light, $theme: '[data-theme="light"]')
6668
}

layouts/_default/baseof.html

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,20 @@ <h1>Redirecting&hellip;</h1>
4242
{{ warnf "No section found in %s" (.Page.Path | jsonify) }}
4343
{{ end }}
4444
{{ .Scratch.Set "section" $section }}
45-
4645
<head>
46+
<script type="text/javascript">
47+
// Check for stored user dark/light mode preference, if any
48+
const currentTheme = localStorage.getItem("theme")
49+
if (currentTheme) {
50+
// Check dark mode preference at the OS level
51+
const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)").matches
52+
if ((prefersDarkScheme && currentTheme === "light")
53+
|| (!prefersDarkScheme && currentTheme === "dark")) {
54+
document.documentElement.dataset.theme = currentTheme
55+
}
56+
}
57+
</script>
58+
4759
<meta charset='utf-8'>
4860
<meta content='IE=edge,chrome=1' http-equiv='X-UA-Compatible'>
4961
<meta name="viewport" content="width=device-width, initial-scale=1">

layouts/partials/header.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
document.getElementById('tagline').innerHTML = '--' + tagline;
1515
</script>
1616

17+
<img src="{{ relURL "images/dark-mode.svg" }}" id="dark-mode-button" />
1718
{{ if ne (.Scratch.Get "section") "search" }}
1819
<form id="search" action="{{ relURL "search/results" }}">
1920
<input id="search-text" name="search" placeholder="Type / to search entire site…" autocomplete="off" type="text" />

static/images/dark-mode.svg

Lines changed: 17 additions & 0 deletions
Loading

static/images/light-mode.svg

Lines changed: 44 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)