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
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:
Or maybe an image?
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:
.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 thanstatic
, 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
@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:
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.
.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);
}