Skip to content

Commit fad54b4

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"] { ... } The toggle button itself is a newly-introduced SVG file, which means that `script/serve-public.js` has to learn about SVG files. Signed-off-by: Johannes Schindelin <[email protected]>
1 parent 7a62c72 commit fad54b4

File tree

8 files changed

+139
-4
lines changed

8 files changed

+139
-4
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();
@@ -551,6 +552,38 @@ var Downloads = {
551552
},
552553
}
553554

555+
var DarkMode = {
556+
init: function() {
557+
const button = $('#dark-mode-button');
558+
if (!button.length) return;
559+
560+
// Check for dark mode preference at the OS level
561+
const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)").matches;
562+
563+
// Get the user's theme preference from local storage, if it's available
564+
const currentTheme = localStorage.getItem("theme");
565+
566+
if ((prefersDarkScheme && currentTheme !== "light")
567+
|| (!prefersDarkScheme && currentTheme === "dark")) {
568+
button.attr("src", `${baseURLPrefix}images/light-mode.svg`);
569+
}
570+
button.show();
571+
572+
button.click(function(e) {
573+
e.preventDefault();
574+
let theme
575+
if (prefersDarkScheme) {
576+
theme = document.documentElement.dataset.theme === "light" ? "dark" : "light"
577+
} else {
578+
theme = document.documentElement.dataset.theme === "dark" ? "light" : "dark"
579+
}
580+
document.documentElement.dataset.theme = theme
581+
localStorage.setItem("theme", theme);
582+
button.attr("src", `${baseURLPrefix}images/${theme === "dark" ? "light" : "dark"}-mode.svg`);
583+
});
584+
},
585+
}
586+
554587
// Scroll to Top
555588
$('#scrollToTop').removeClass('no-js');
556589
$(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: 5 additions & 3 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) {
65-
@include mode($mode: dark)
66+
@include mode($mode: dark, $theme: ':not([data-theme="light"])')
67+
@include mode($mode: 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" />

script/serve-public.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const mimeTypes = {
1212
"jpeg": "image/jpeg",
1313
"jpg": "image/jpeg",
1414
"png": "image/png",
15+
"svg": "image/svg+xml",
1516
"ico": "image/x-icon",
1617
"js": "text/javascript",
1718
"css": "text/css",

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)