The Flip-Flop Technique

This article, part of the writing collection, was published on and last updated on .

It’s JS Naked Day!

I’m participating in JS Naked Day with the hope of helping to promote the rule of least power. This means that your browsing experience on this website during the 50 hours that make up JS Naked Day should be identical to one where you have disabled JavaScript in your browser.

This is an excellent exercise in making sure there is a clear separation of concerns between HTML for markup, CSS for styling, and JavaScript for interactivity. I highly recommend trying it out and participating yourself!

I recently implemented a colour scheme toggler in the footer of my website, following Andy Bell’s guide, Create a user controlled dark or light mode, and found a wonky but fun alternative solution for styling my dark theme which leverages CSS’s filter property.

Since moving to Eleventy I have put a bit more thought and effort into how I would transition my design from light to dark, and no longer use the filter() technique described below.

I highly recommend you read Andy’s post first! Come back after, this page isn’t going anywhere…


The gist is that we have some JavaScript that hooks onto a number of CSS Variables exposed on our pages, and we’re toggling their values back and forth between a light and dark palette by changing an attribute on the root element:

:root {
	--color-text: black;
}
[data-color-scheme="dark"] {
	--color-text: white;
}

Flip-Flop Permalink

I need to put in some time to work on the colours used in my dark theme, but I wanted an interim solution because I was so excited to implement this, to be honest!

Eventually, I realised that with a handful of filter values, we can come up with a decent-enough inversion of my light-themed styles.

Figure 1

Theme: unaltered

Figure 2

Theme: invert(1)

Inversion complete. We’ve gone from a light background to a dark one. Now we need to fix the hues of our colours—in this case, we want our brown to be blue.

Figure 3

Theme: invert(1) hue-rotate(180deg)

Now we’ve managed to get our blue back, but the Dragon emoji looks completely wrong. This is where the flip-flop technique comes in.

Figure 4

Theme: invert(1) hue-rotate(180deg)
Emoji: invert(1) hue-rotate(180deg)

That’s done it. By applying the same filter again to the emoji, it flip-flops back to its unaltered appearance.

Hue, Saturation, Lightness Permalink

But something’s off. The colours of the emoji in the final example seem less saturated or less vibrant than the unaltered emoji in the first example, which is most noticeable on the yellow hair of the dragon. Interact with this demo to see the unaltered state alongside the fully-filtered state and see for yourself.

Even more confusing to me is that this discrepancy only exists when I look at it using my default light theme—when viewed with my dark theme the unaltered emoji appears just as unsaturated as the final product.

The Code Permalink

Warning! The code below makes pretty heavy-handed use of filter and probably isn’t very performant!

img,
svg,
[role="img"],
embed,
iframe,
object,
video {
	@extend %asset-elements;
}

@mixin color-scheme-dark() {
	@supports (filter: invert(1) hue-rotate(180deg)) {
		&,
		%asset-elements {
			filter: invert(1) hue-rotate(180deg);
		}
	}

	@supports not (filter: invert(1) hue-rotate(180deg)) {
		--color-black: #{$color-white};
		--color-mineshaft: #{$color-yeti};
		--color-kaiser: #{$color-nickel};
		--color-nickel: #{$color-kaiser};
		--color-yeti: #{$color-mineshaft};
		--color-white: #{$color-black};
	}
}

@media (prefers-color-scheme: dark) {
	:root {
		--color-scheme: "dark";
	}

	:root:not([data-color-scheme]) {
		@include color-scheme-dark;
	}
}

/*:root*/[data-color-scheme="dark"] {
	@include color-scheme-dark;
}

Maybe someone knowledgable about colours or filters on the web has an idea of what’s going on here, but I can’t seem to get the emoji to return to its original colour using any combination of filters to try to flip-flop back to its unaltered state.

I’m definitely missing something, but it’s close.

You can also send an anonymous reply (using Quill and Comment Parade).

5 Responses

  1. 2 Likes
  2. 3 Links