Colored shadows using mix-blend-mode

Author Jesse Breneman Published on October 10, 2019

I recently stumbled across this post by Tobias Ahlin on layering box shadows to achieve a smoother result. This was not a new concept to me, but I was super impressed with the post and ended up showing it to Dust, a designer I've worked with for years (who actually has been layering shadows for a long time). From there the discussion changed into talking about how shadows look better when they are color tailored to match their surroundings, which made me curious: Is it possible to automatically color tint a shadow on an element to match its surroundings? Turns out that yes, it is, although there are some caveats.

A demo

Check out my box shadow. 👀

As you can see, we've got a nice green tinted box-shadow, which is cool, but not that exciting, right? That's not that hard to do, just play around with a color picker until you find a nice looking color that works with your background color! You're right, but this eliminates having to do that completely, it automatically will give you a shadow that is background aware.

Laziness aside (seriously, I'm not doing that for every colored background!), this gets even cooler when you consider things like gradients and images as backgrounds. Let's see what happens when I plug the gradient used for the header into this:

Check out my box shadow. 👀

Or maybe an image?

Check out my box shadow. 👀

Photo from Unsplash, courtesy of Nick Perez

Pretty cool, right? Best of all, it's super easy to use! Assuming you have an element with a class of .cool-box-shadow, here's all it takes:

css
.cool-box-shadow {
    position: relative;
}

.cool-box-shadow::before {
    content: '';
    position: absolute;
    width: 100%;
    height: 100%;
    left: 0;
    top: 0;
    box-shadow: 0 1px 1px rgba(0, 0, 0, 0.6), 0 2px 2px rgba(0, 0, 0, 0.6),
        0 4px 4px rgba(0, 0, 0, 0.6), 0 8px 8px rgba(0, 0, 0, 0.6),
        0 16px 16px rgba(0, 0, 0, 0.6);
    mix-blend-mode: soft-light;
}

(I've also included a handy SCSS mixin at the bottom if you're into that!)

Explanation

This effect relies on the mix-blend-mode property, but we need to only apply the blend mode to the shadow. Applying a blend mode blindly to the entire container means all of our content will also be blended, which might look cool but is generally not we're going for. To get around this, we're creating a pseudo-element, setting it to be the same size as it's parent, positioning it so that it's overlaying the parent, and then applying the box shadow to that inside. Because it's a separate element, we can apply the mix-blend-mode to it and have it only apply to our shadow! You'll also notice the position: relative on the parent element, this is necessary to make the pseudo-element size correctly, otherwise the width/height 100% won't work.

soft-light seems to be the best overall blend mode for this effect from my tests, with overlay being an almost identical close second. Depending on the effect you're going for, another mode might work better for you.

Caveats

As with anything pushing the boundaries of CSS, there are a few caveats:

  • New stacking contexts make this not work, or at least not work well. If the element you're applying the shadow to is several levels deeper than your background and one of the elements between has any property that's creating a new context (any position other than static, for instance), this breaks down. From what I can tell, mix-blend-mode is particular about what it looks for when it tries to blend, it appears to only go back to the closest new context and then stop, at least in Chrome.
  • Related (?), I couldn't get this to work with a background color on body. Not sure why. Super weird.
  • Due to how the math works out, this does not show up on pure white backgrounds.
  • It can get a little bold on darker colors. Not a huge deal, but sometimes it's a little much. It doesn't show up on black either, but that's to be expected, it's how a normal box shadow would look too.

Are these caveats too much to bother? Possibly. I think this technique has it's uses, especially in situations where you're using it on images, it generally looks really nice there. I don't think I'll be using this in most normal situations, but I will definitely be thinking about it for more specialized situations that call for more polish.

SCSS mixin

scss
@mixin shadow {
    position: relative;

    &::before {
        content: '';
        position: absolute;
        width: 100%;
        height: 100%;
        left: 0;
        top: 0;
        box-shadow: 0 1px 1px rgba(0, 0, 0, 0.6), 0 2px 2px rgba(0, 0, 0, 0.6),
            0 4px 4px rgba(0, 0, 0, 0.6), 0 8px 8px rgba(0, 0, 0, 0.6),
            0 16px 16px rgba(0, 0, 0, 0.6);
        mix-blend-mode: soft-light;
    }
}

Bonus: Content reflection!

Real life shadows also will include a slight reflection from the actual object they're a shadow of. Can we do that too? I say yes, with another pseudo-element:

Check out my box shadow. 👀

It's pretty subtle, but it adds a little extra color to the top of the shadow.

To accomplish this, I added an ::after that inherits the content element's background color, tries to form itself into a half ellipse at the bottom of the element, and then gets blurred and the opacity knocked down to something subtle.

css
.cool-box-shadow::after {
    content: '';
    position: absolute;
    border-radius: 50% / 100%;
    border-top-left-radius: 0;
    border-top-right-radius: 0;
    width: 96%;
    height: 6px;
    left: 2%;
    top: 100%;
    background-color: inherit;
    opacity: 0.3;
    filter: blur(3px);
}

About the author