Jim Nielsen’s Blog
Preferences
Theme: This feature requires JavaScript as well as the default site fidelity (see below).
Fidelity:

Controls the level of style and functionality of the site, a lower fidelity meaning less bandwidth, battery, and CPU usage. Learn more.

Applying Multiple Background Colors with CSS

The Problem

I’ve been playing with supporting “dark mode” a lot recently. One of the foundational CSS features I leverage for building dark/light themes is custom properties (a.k.a. CSS variables). I essentially define a number of color variables I plan to use through my UI, then change their values based on whether the client is in “dark mode” or not.

In addition to primary and secondary colors, I often define a number of base neutral colors in rgba. Why rgba? I want to leverage the alpha channel so those neutral colors can blend in with other colors of the site.

Graphic showing and explaining a UI element with rgba colors

What this means is I start with a number of neutral colors for light mode:

@media screen and (prefers-color-scheme: light) {
  :root {
    --color-background: rgba(255,255,255,1); /* white */
    --color-border-dark: rgba(0,0,0,1);      /* black */
    --color-border-medium: rgba(0,0,0,.7);   /* gray */
    --color-border-light: rgba(0,0,0,.3);    /* light gray */
  }
}

And then switching to dark mode is easy: I flip the color definitions to the other end of the spectrum.

@media screen and (prefers-color-scheme: dark) {
  :root {
    --color-background: rgba(0,0,0,1);           /* black */
    --color-border-dark: rgba(255,255,255,1);    /* white */
    --color-border-medium: rgba(255,255,255,.7); /* gray */
    --color-border-light: rgba(255,255,255,.3);  /* light gray */
  }
}

This is rather simplistic for illustration purposes, but you get the idea. The problem is, I often need a solid version of those colors in my UI because I want an element to overlay—and block out—another element.

Screenshot showing the problem where a UI element with a transparent color is trying to overlay—and block out—another UI element

When I write background-color: var(--color-border-light), what I often want is that color as if it was appearing on white (the background color). So the question becomes: could I tell CSS that? Display my specified color as if it were blending into the natural background color? Something like this:

.element {
  /* Put the transparent color on top
     and have it blend into a solid color underneath */
  background-color: var(--color-border-light), var(--color-background);
}

The Solution

Turns out that you can display multiple background colors in CSS and achieve the effect I described above, you just need to leverage gradients to do it. Essentially, you declare two different gradients in CSS—one solid one transparent—that blend into each other.

Screenshot a transparent and a solid UI element overlaying another UI element along with the corresponding CSS values to achieve the effect

What’s neat about this is, given the “dark mode” colors we declared above, when you switch to dark mode this “just works”.

Screenshot showing one UI elements overlaying other UI elements but in dark mode

Here’s some example code:

.element {
  background-image: 
    /* Transparent color on top */
    linear-gradient(
      var(--color-border-light),
      var(--color-border-light)
    ),
    /* Solid color underneath */
    linear-gradient(
      var(--color-background),
      var(--color-background)
    );
}

I’m sure there are other ways you could solve this issue—like by defining and using colors differently—but this worked great for my use case under the paradigm in which I had defined my colors. Maybe you’ll find it handy too.