The Flip-Flop Technique

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-user-color-scheme="dark"] {
    --color-text: white;
}

Flip-Flop

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

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

@mixin theme-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-alto};
        --color-kaiser: #{$color-dove};
        --color-dove: #{$color-kaiser};
        --color-alto: #{$color-mineshaft};
        --color-white: #{$color-black};
    }
}

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

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

/*:root*/[data-user-color-scheme="dark"] {
    @include theme-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.

🗓 published on
📚 reading length ~250 words
🏷 tagged under
🔗 shorturl repc.co/a52J1


Session IPA Liked: well that was one of the nicest sunsets I've seen from an ... • Aaron Parecki