Dark mode, ENGAGE! User Preference Media Features in your responsive web app.

The dark theme option has been around in apps for awhile. But now, it's common to offer a dark theme at the OS level. I personally love this feature. I stare at screens all day! Also, I use OLED devices. Naturally I want EVERYTHING to be dark themed when I pick that option. Unfortunately, the majority of the web has not adopted this standard whole-heartedly. ( To be fair, it is kinda new, as of writing this, these features are still in draft. )

This doc will outline solutions to two problems. First, how to architect theming in your CSS / SCSS in an efficient way. Second how to access your theme from JS ( and why you would need to do that)

(I developed this on a Mac using Mojave 10.14 - with Safari Technology Preview. As of November 2018 this will not work with stock Safari.)

What is a Theme?

You can think of a theme kinda like a mod for a video game. A mod doesn't define the gameplay or the characters, its only a tweak. But some mods, even though they are small, will have huge impacts on the overall gameplay. Themes are small conditional tweaks in a style sheet that greatly affect the look of that app.

What are User Preference Media Features?

Your browser will use Media Queries to determine what theme you picked. These are called "User Preference Media Features" (https://drafts.csswg.org/mediaqueries-5/#mf-user-preferences). This standard also exposes other preferences you can set at OS level. Everything in this doc can be applied to any of those preferences.

Theming your Style Sheet

I love SCSS. I love using SCSS Variables! They are so simple! But they have this one drawback, they only work at compile time. My theme of choice can change after the client has loaded the website. Also, at compile time, I don't know what themed-CSS I should serve the client. I don't know their theme selection. So the first problem has two parts: How do I architect my style sheets so that they are responsive to the theme change, and I how do I do that so I don't have to duplicate all of my CSS.

The solution is little easier for me because I am already using SCSS variables everywhere. I have variables like $canvas-background-color and $canvas-grid-color. First step is to convert these to CSS variables. I have to create two media queries, one for light theme and one for dark theme.

(Solution brought to you by Stuff And Nonsense - https://stuffandnonsense.co.uk/blog/redesigning-your-product-and-website-for-dark-mode)

/* Old Variables $canvas-background-color:#5B5B5B; $canvas-grid-color:#FFFFFF; */ :root { --canvas-background-color: #FFFFFF; --canvas-grid-color: #5B5B5B; } @media (prefers-color-scheme: dark) { :root { --canvas-background-color: #5B5B5B; --canvas-grid-color: #FFFFFF; } } canvas{ background-color:var(--canvas-background-color); color:var(--canvas-grid-color); }

After reading that last line of code, you may be wondering, why set the color attribute of a CANVAS? Usually you won't render text in a canvas. I will explain exactly why I do this in the next section.

Why did I do that? Theming with JS.

So I had to do some hacky stuff to get JS to know about the theme change. And not only know about the theme change, but also know what colors changed.

My canvas redraws multiple times a second. I don't want to have to manage colors in JS and in CSS. I really don't want to duplicate anything. So to avoid this, I will get the color from the canvas that has been assigned in CSS.

(I am using Angular with Typescript, so imagine this code living inside a component)

canvas : HTMLElement; draw(){ var backgroundColor = this.getCanvasBackgroundColor(); var gridColor = this.getCanvasGridColor(); this.redrawCanvas(backgroundColor,gridColor); } getCanvasBackgroundColor() { return getCssProperty(this.canvas,'background-color'); } getCanvasGridColor() { return getCssProperty(this.canvas,'color'); } getCssProperty(element,property) { return window.getComputedStyle ? window.getComputedStyle(element, null).getPropertyValue(property) : element.style[property.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); })]; }

(Thank you RSHARPY for getCssProperty - https://stackoverflow.com/a/51137753)

This is a heavy operation to happen each time the frame gets redrawn. I could refactor this to capture the colors at a slower rate than the draw loop rate.

So that's it really. Canvas is kind of a weird thing to get styled, but I had to do it so I could easily match what I darw in canvas and the rest of my themed app.

Last note - Even IF there is an easy way to get the OS theme from JS, you still would need to define color in you JS / TS files. I don't like this one bit. Separation of concerns Y'all, keep colors in CSS / SCSS if possible.