Transparent Layers With Color-Mix

Disclaimer: This article hasn't even gone live and I have realised that it is already out-of-date 😅 I thought I'd come up with a fun way of using a new CSS property that I hadn't previously experimented with and wanted to record the thought process. However, as I've continued working on the original problem I was trying to solve, I've realised that a modal component is best served using the new (ish) <dialog> element, which removes the need for everything I've written here, because you can control the background opacity on a <dialog> using the ::backdrop pseudo-selector 🤦‍♀️ Plus, annoyingly, there is a weird little caveat with that otherwise better solution, which is that ::backdrop never inherits (by design), so you can't actually use custom properties in the way described below. *Sigh* it's never quite as simple, is it?

At any rate, I thought about rewriting the whole article with a different use case but failed to come up with anything that felt realistic, so I'm keeping it as-is. Who knows, it may come in handy someday for some other, yet unknown application, and color-mix is generally just pretty cool. Hope you enjoy it anyway 😉


There are quite a few common UI patterns that call for an element to have different "layers" of content, where one or more of these layers are in some way transparent. The most common example that I come across is a modal. These typically have a foreground element containing the content (text, images, forms and inputs, etc.), and a background "overlay" that covers the rest of the viewport with a semi-transparent layer. This helps focus attention on the content block itself and gives a clear visual indication that the rest of the site is no longer accessible to the user.

As patterns go, this feels intuitively simple. You have a wrapping element positioned across the entire viewport and a child element centred on both axes, then apply a background colour to the wrapper and set the opacity to something like 20-30%. However, opacity inherits in CSS (and can't be undone by making child elements somehow more opaque 😅) so setting opacity on the wrapping parent element also makes the child element transparent, which we don't want.

We can also control opacity on the background colour directly using any colour syntax which supports an alpha channel, e.g. RGBa. That's great for one-off use cases, but within design systems it's likely that your background colour will be tokenised somewhere, and you want to use that token. In my experience, though, these are often defined in a format that isn't compatible with alpha channel editing (e.g. hex codes), or the token has no way to control the degree of opacity you want to apply. For example:

<style>
    --background-grey: #f5f5f5;

    .modal_wrapper {
        background-color: var(--background-grey); <-- no way to hook into this to set opacity
    }
</style>

Luckily, there's a new (ish) CSS property that can help here, because this is where color-mix becomes extremely handy.

CSS Pigments

The color-mix CSS property has surprisingly good adoption across most evergreen browsers (at the time of writing – September 2023 – it's at about 82%[1]). Ostensibly, it's designed to let you combine two colours, and the browser will then calculate the nearest available output within the specified colour space. As I understand it, this is effectively exposing the underlying colour calculations that browsers are making with any colour, which is pretty cool.

For example, if you want to mix red and blue within an sRGB colour space, you can do something like this:

.purple {
    color: color-mix(in srgb, red, blue);
}

And yes, you can turn those input colours into custom properties and reference them from wherever[2]:

.purple {
    --red: #f00;
    --blue: #00f;

    color: color-mix(in srgb, var(--red), var(--blue);
}

Either of these examples will set the targeted text to a pleasing purple colour, but you can do more than simply defining two colours, you can also define the quantity of each colour. For instance, let's say you want a very reddish purple, rather than the current even split. You can define the amount of either colour as a percentage, so if you want four parts of red for every one part of blue, you could set your red variable to 80%:

.purple {
    --red: #f00;
    --blue: #00f;

    color: color-mix(in srgb, var(--red) 80%, var(--blue);
}

This effectively lets you combine hex codes and other digital colour syntaxes in the same way you would mix paints or pigments in the analogue world. Cool! 🎨

Mixing Transparency

What makes this particularly powerful for our use case is that transparent is technically a CSS colour, one that directly affects the alpha channel of an element, and color-mix treats it like any other input.

That means we can create a background colour that accepts any of our design system's colour tokens and adds a dash of transparency. Those tokens don't even need to be defined using a colour format that supports alpha channel manipulation, because by mixing them we ensure that conversion takes place anyway.

So for our modal wrapper, we'd end up with something like this, where we're using pre-defined colours from our design system and modifying them to our specific needs:

<style>
    .wrapper {
        position: absolute;
        inset: 0;
        display: flex;
        width: 100vw;
        height: 100svh;
        background-color: color-mix(in srgb, var(--gray-800) 30%, transparent);
    }

    .modal {
        margin: auto;
    }
</style>

<div class="wrapper">
    <section class="modal">
        ...
    </section>
</div>

No more need for absolutely positioned pseudo elements, weird opacity hacks, or anything else. A simple DOM structure where the wrapping element actually contains the modal content. And a single line of CSS to control the opacity in a modern, performant, and extremely flexible way. CSS is getting really, genuinely awesome 🤘

Explore Other Articles

Newer

A Bookmark Feed

Mastodon makes saving interesting links for later very easy, but getting these into a feed reader or note-taking service seemed impossible until I came across a neat little hosted solution.

Older

Module Mindsets

I'm still enjoying the wombo-combo of Sass and CSS Modules, but my React-ified brain occasionally blanks on how to approach certain problems. The one that catches me out the most: style inheritance.

Further Reading & Sources

Conversation

Want to take part?

Comments are powered by Webmentions; if you know what that means, do your thing 👍

Footnotes

  • <p>The color-mix property enables a lot of interesting functionality when you realise that you can use it to mix transparency into colours, including design tokens.</p>
  • Murray Adcock.
Article permalink

Made By Me, But Made Possible By:

CMS:

Build: Gatsby

Deployment: GitHub

Hosting: Netlify

Connect With Me:

Twitter Twitter

Instagram Instragram

500px 500px

GitHub GitHub

Keep Up To Date:

All Posts RSS feed.

Articles RSS feed.

Journal RSS feed.

Notes RSS feed.