The Flip-Flop Technique

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

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;


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

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

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

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.

5 Responses

  1. 2 Likes
  2. 3 Links