Skip to content

Commit c12af05

Browse files
authored
feat(thought): added dark mode article (#62)
* feat(thought): added dark mode article * chore: add links to eventual files * fix: add styling for anchors inside lists
1 parent f7b290e commit c12af05

File tree

2 files changed

+234
-2
lines changed

2 files changed

+234
-2
lines changed

assets/styles/styles.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,11 @@ ul:last-child {
5555
padding-bottom: 0;
5656
}
5757

58-
p a {
58+
p a, ul a, ol a {
5959
border-bottom: 1px solid #fff;
6060
}
6161

62-
p a:hover {
62+
p a:hover, ul a:hover, ol a:hover {
6363
color: #ccc;
6464
border-color: #ccc;
6565
}
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
Ever thought "ewww this site is so bright?!" and then searched for the "dark mode" button? I'm one of those people too, and despite my love of bright blocks of colour I decided that implementing a dark mode switch would be a good idea.
2+
3+
Additionally, I hate it when things _default_ to light mode, even though my machine is set to dark mode! So in this little guide, I'll walk you through how I implemented a machine-aware dark mode for this site.
4+
5+
## What's the plan?
6+
7+
As this site is very simple and static (no server-side rendering here, just good ol' fashioned HTML and CSS), I'll need to implement a client-side-only dark mode. This means I'll need to:
8+
9+
- Figure out how I want to "apply" a dark mode across the site using CSS
10+
- Figure out the JavaScript to enable this dark mode
11+
12+
## How to apply dark mode in HTML
13+
14+
This one's quite simple, and I didn't want to muck around too much. As my site is powered by basic CSS (no CSS-in-JS or anything of that ilk), I can just apply a class to the `<body>` element, and then select on that!
15+
16+
So if it's in light mode, the body will just be:
17+
18+
```html
19+
<body>
20+
<h1>Content</h1>
21+
</body>
22+
```
23+
24+
But in dark mode, it'll be:
25+
26+
```html
27+
<body class="dark">
28+
<h1>Content</h1>
29+
</body>
30+
```
31+
32+
That means in CSS I can then do things like:
33+
34+
```css
35+
// light mode is default
36+
body {
37+
background: white;
38+
}
39+
40+
h1 {
41+
color: black;
42+
}
43+
44+
// dark mode has a more specific selector, so will override the light mode
45+
body.dark {
46+
background: black;
47+
}
48+
49+
.dark h1 {
50+
color: white;
51+
}
52+
```
53+
54+
## How to define the dark CSS styles
55+
56+
My site has a single `styles.css` that defines the site's theme, along with a `reset.css` for resetting browser defaults to 0 values and `syntax_highlight.css` for my syntax block styling. All these live in a `styles` folder, and my build process merges them all together and minifies them.
57+
58+
So, my plan is to copy/paste my `styles.css` to `styles_dark.css`, and remove any properties that aren't about colours. It was a slightly tedious process, but only took me 15 minutes to isolate all the colour values in my CSS. Now during development (before I've created the JavaScript), I manually added the `dark` class to my body element, and ran the site in development mode.
59+
60+
Now I've got a file with just the colours, I prepend the `.dark` class selector to the beginning of each of them. Because it's a standard CSS file, I select all instances of `{` using VS Code `CTRL/CMD + D`, then hit the `Home` key to take me to the beginning of each line, and smack in `.dark` - now all my `styles_dark.css` selectors are prefixed with the correct selector - neat!
61+
62+
With my site running in development mode and the body being hard-coded to be `class="dark"`, I just modified all the values until I was happy with the darker theme.
63+
64+
If this sounds like a lot of manual steps, it is! But only because my site is very, _very_ simplistic in its implementation for maximum speed/ease of maintenance.
65+
66+
## How to flip between light and dark mode
67+
68+
### Simple functionality
69+
70+
Now I've decided on adding the `dark` class to my `<body>` tag, I can write some JavaScript to control this functionality. To begin with, I just added a button to the top of my home page (who cares about styling right now), and some simple JavaScript:
71+
72+
```html
73+
<!-- HTML -->
74+
75+
<!-- At the top of my home page -->
76+
<button onclick="switchMode()">Switch dark/light mode</button>
77+
78+
<!-- Just before the closing body tag -->
79+
<script>
80+
var darkMode = false;
81+
function switchMode() {
82+
if (darkMode) {
83+
darkMode = false;
84+
document.body.classList.remove('dark');
85+
} else {
86+
darkMode = true;
87+
document.body.classList.add('dark');
88+
}
89+
}
90+
</script>
91+
```
92+
93+
Hooray! Now the site switches between light and dark mode as expected.
94+
95+
### Remembering preference
96+
97+
Now we're switching between modes, but it doesn't remember my last mode on page refresh! Let's use local storage to remember our value:
98+
99+
```javascript
100+
// retrieve from local storage
101+
var darkMode = window.localStorage.getItem('darkMode') || 'false';
102+
if (darkMode === 'true') {
103+
document.body.classList.add('dark');
104+
}
105+
106+
// enable switching functionality
107+
function switchMode() {
108+
if (darkMode === 'true') {
109+
darkMode = 'false';
110+
document.body.classList.remove('dark');
111+
} else {
112+
darkMode = 'true';
113+
document.body.classList.add('dark');
114+
}
115+
window.localStorage.setItem('darkMode', darkMode);
116+
}
117+
```
118+
119+
Note that we have to use string versions of `true` and `false`, because local storage _always_ stores things as strings. Now our site remembers our preference, defaulting to light mode.
120+
121+
### Honouring system preference
122+
123+
This is great, but if the site is being viewed on a browser/machine with dark mode enabled system-wide, we should honour that too by default:
124+
125+
```javascript
126+
// detect browser setup
127+
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches && !window.localStorage.getItem('darkMode')) {
128+
window.localStorage.setItem('darkMode', 'true');
129+
}
130+
131+
// retrieve from local storage
132+
var darkMode = window.localStorage.getItem('darkMode') || 'false';
133+
if (darkMode === 'true') {
134+
document.body.classList.add('dark');
135+
}
136+
137+
// enable switching functionality
138+
function switchMode() {
139+
if (darkMode === 'true') {
140+
darkMode = 'false';
141+
document.body.classList.remove('dark');
142+
} else {
143+
darkMode = 'true';
144+
document.body.classList.add('dark');
145+
}
146+
window.localStorage.setItem('darkMode', darkMode);
147+
}
148+
```
149+
150+
There are some caveats to this method (it's supported only in modern browsers), but it's good enough to cover 95% of users of the web, and I would suspect almost 100% of viewers of my site (due to the target audience). Again, it will default to light mode if it can't use `window.matchMedia`, so no harm no foul.
151+
152+
## Pretty SVG button
153+
154+
Finally, I updated the styling of my boring-standard button to be something snazzier, using a nice SVG of a filled or hollow sun:
155+
156+
```html
157+
<section class="darkmode">
158+
<button onclick="switchMode()">
159+
<svg class="lightSun" xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" viewBox="0 0 48 48">
160+
<g id="Layer_2" data-name="Layer 2">
161+
<g id="invisible_box" data-name="invisible box">
162+
<rect width="48" height="48" fill="none" />
163+
</g>
164+
<g id="Q3_icons" data-name="Q3 icons">
165+
<g>
166+
<path d="M24,10a2,2,0,0,0,2-2V4a2,2,0,0,0-4,0V8A2,2,0,0,0,24,10Z" />
167+
<path d="M24,38a2,2,0,0,0-2,2v4a2,2,0,0,0,4,0V40A2,2,0,0,0,24,38Z" />
168+
<path d="M36.7,14.1l2.9-2.8a2.3,2.3,0,0,0,0-2.9,2.3,2.3,0,0,0-2.9,0l-2.8,2.9a2,2,0,1,0,2.8,2.8Z" />
169+
<path d="M11.3,33.9,8.4,36.7a2.3,2.3,0,0,0,0,2.9,2.3,2.3,0,0,0,2.9,0l2.8-2.9a2,2,0,1,0-2.8-2.8Z" />
170+
<path d="M44,22H40a2,2,0,0,0,0,4h4a2,2,0,0,0,0-4Z" />
171+
<path d="M10,24a2,2,0,0,0-2-2H4a2,2,0,0,0,0,4H8A2,2,0,0,0,10,24Z" />
172+
<path d="M36.7,33.9a2,2,0,1,0-2.8,2.8l2.8,2.9a2.1,2.1,0,1,0,2.9-2.9Z" />
173+
<path d="M11.3,14.1a2,2,0,0,0,2.8-2.8L11.3,8.4a2.3,2.3,0,0,0-2.9,0,2.3,2.3,0,0,0,0,2.9Z" />
174+
<path d="M24,14A10,10,0,1,0,34,24,10,10,0,0,0,24,14Zm0,16a6,6,0,1,1,6-6A6,6,0,0,1,24,30Z" />
175+
</g>
176+
</g>
177+
</g>
178+
</svg>
179+
<svg class="darkSun" xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" viewBox="0 0 48 48">
180+
<g id="Layer_2" data-name="Layer 2">
181+
<g id="invisible_box" data-name="invisible box">
182+
<rect width="48" height="48" fill="none" />
183+
</g>
184+
<g id="Q3_icons" data-name="Q3 icons">
185+
<g>
186+
<path d="M24,10a2,2,0,0,0,2-2V4a2,2,0,0,0-4,0V8A2,2,0,0,0,24,10Z" />
187+
<path d="M24,38a2,2,0,0,0-2,2v4a2,2,0,0,0,4,0V40A2,2,0,0,0,24,38Z" />
188+
<path d="M36.7,14.1l2.9-2.8a2.3,2.3,0,0,0,0-2.9,2.3,2.3,0,0,0-2.9,0l-2.8,2.9a2,2,0,1,0,2.8,2.8Z" />
189+
<path d="M11.3,33.9,8.4,36.7a2.3,2.3,0,0,0,0,2.9,2.3,2.3,0,0,0,2.9,0l2.8-2.9a2,2,0,1,0-2.8-2.8Z" />
190+
<path d="M44,22H40a2,2,0,0,0,0,4h4a2,2,0,0,0,0-4Z" />
191+
<path d="M10,24a2,2,0,0,0-2-2H4a2,2,0,0,0,0,4H8A2,2,0,0,0,10,24Z" />
192+
<path d="M36.7,33.9a2,2,0,1,0-2.8,2.8l2.8,2.9a2.1,2.1,0,1,0,2.9-2.9Z" />
193+
<path d="M11.3,14.1a2,2,0,0,0,2.8-2.8L11.3,8.4a2.3,2.3,0,0,0-2.9,0,2.3,2.3,0,0,0,0,2.9Z" />
194+
<path d="M24,14A10,10,0,1,0,34,24,10,10,0,0,0,24,14Z" />
195+
</g>
196+
</g>
197+
</g>
198+
</svg>
199+
</button>
200+
</section>
201+
```
202+
203+
And I use CSS to show or hide the appropriate SVG:
204+
205+
```css
206+
// default to showing darkSun (ie, light mode)
207+
.lightSun {
208+
display: none;
209+
}
210+
211+
// show lightSun and hide darkSun in dark mode
212+
.dark .lightSun {
213+
display: block;
214+
fill: #fff;
215+
}
216+
.dark .darkSun {
217+
display: none;
218+
}
219+
```
220+
221+
Now I just add this code to all my pages, popping the JavaScript in my `footer.html` partial, and the SVG button to my `header.html` partial - now it's on every page!
222+
223+
## This isn't perfect
224+
225+
This is by no means a perfect implementation - the biggest issue is that there's a "flash of unstyled content" when the page loads and it runs the JavaScript to detect if you have dark mode on, and _then_ applies the `dark` class to the `<body>` element. However, for this _incredibly basic site_ it happens in ~2ms, so I think it's a trade-off worth making for the utter simplicity of the implementation.
226+
227+
You can check out all the eventual code for this in this website's repository:
228+
- [darkmode.html partial](https://github.com/ripixel/ripixel-website/blob/master/partials/darkmode.html) (for the switching button, included on every page)
229+
- [footer.html partial](https://github.com/ripixel/ripixel-website/blob/master/partials/footer.html#L30) (for the switching JavaScript
230+
- [styles_dark.css](https://github.com/ripixel/ripixel-website/blob/master/assets/styles/styles_dark.css) (the dark mode specific styles)
231+
232+
Go click that sun in the top-right hand corner of the site and let me know what you think!

0 commit comments

Comments
 (0)