The world of post-modern web design is one where the light doesn’t cast many shadows. That doesn’t mean CSS shadows are going away. On the contrary, they’ve become more adaptive. Shadows are an incredibly useful design element. We know they add depth to an otherwise two-dimensional web design, but did you know we can stack, animate, and manipulate them in ways that go beyond that?
I’ve been experimenting with shadows. In this article, I’m going to share several “tricks” I’ve discovered along the way and how they can be used to create interesting effects that have little to do with their primary role of adding depth. We’ll look at an effect that works by stacking layers of shadows that transition on hover. After that, I will show you how to make a shadow of a shadow. Lastly, we’ll play with shadows on text as an alternative to color.
Ready for some fun? Let’s start with an interesting hover effect.
The Introspective Shadow Hover Effect
Most of us are familiar with the inset
keyword. It’s an optional value of the CSS box-shadow
property.
When inset
is specified, the shadow is cast inside the element, directed inward. It’s commonly used to make it look as if an element has been stamped into the surface of the web page. We are going to push that shadow further, both metaphorically and literally, to create an overlay hover effect for image transitions.
Just as we can control the shadow’s blur radius — how far the shadow spreads outward — we can choose to apply no blur at all to the shadow. We can combine that with the fact that inset shadows are painted over an element’s background (unlike default shadows that are cast beneath the element) to create what I call a “veil” that sits on top of an element.
Let’s start with a single div
in the HTML:
<div class="item"></div>
There’s nothing to see yet. So, let’s add some dimensions, a background color, and a border radius to make a green circle.
.item { width: 250px; height: 250px; background: green; border-radius: 50%;
}
This is nothing fancy so far. I merely want to demonstrate that we can essentially cover the green background with a red inset box-shadow
:
.item { width: 250px; height: 250px; background: green; border-radius: 50%; box-shadow: inset 250px 250px 0 red;
}
Now we have a red circle with a green background beneath it. We can remove the red inset shadow on hover to reveal the green background:
.item:hover { box-shadow: none;
}
Since shadows can be layered and are supported by CSS transitions, let’s incorporate that for a more fluid design. First, I’m going to update the HTML a bit by adding a span
inside the .item
:
<div class="item"> <span>The New York Times</span> </div> <!-- more items -->
For the CSS, it’s the same idea as before. We want a circle with an inset shadow and a background:
.item { width: 300px; height: 300px; background-image: url('nytimes.svg'); border-radius: 50%; box-shadow: inset -300px -300px 0 black,
}
The difference so far is that I am using a background-image
instead of a background-color
. They are absolutely interchangeable for the hover effect we’re working on.
Next, I’m going to do two things. First, I’m going to stack more inset shadows inside the .item
. Then I’m changing the text color to white, but only for a moment so the background image shows all the way through.
.item { width: 300px; height: 300px; background-image: url('nytimes.svg'); border-radius: 50%; box-shadow: inset -300px -300px 0 black, inset 300px -300px 0 green, inset -300px 300px 0 blue, inset 300px 300px 0 yellow, 0 0 20px silver; /* standard outset shadow */ color: white; }
Even after we add those four extra shadows, we still are left with only a black circle that says “The New York Times” on it in white. The trick is to remove those shadows on hover, change the color of the text to transparent
, and reveal the logo beneath our stack of inset shadows.
.item:hover { box-shadow: inset 0 0 0 transparent, inset 0 0 0 transparent, inset 0 0 0 transparent, inset 0 0 0 transparent, 0 0 20px silver; /* retain the outset shadow */ color: transparent;
}
That works! But perhaps we should add a little transition in there to smooth it out:
.item { width: 300px; height: 300px; background-image: url('nytimes.svg'); border-radius: 50%; box-shadow: inset -300px -300px 0 black, inset 300px -300px 0 green, inset -300px 300px 0 blue, inset 300px 300px 0 yellow, 0 0 20px silver; /* standard outset shadow */ color: white; transition: box-shadow ease-in-out .6s, color ease-in-out .5s; } .item:hover { box-shadow: inset 0 0 0 transparent, inset 0 0 0 transparent, inset 0 0 0 transparent, inset 0 0 0 transparent, 0 0 20px silver; /* keeping the outset shadow */ color: transparent;
}
The only other thing I think that’s worth calling out is that the outward shadow in the stack is not removed when the .item
is hovered. I only want to remove the inset shadows.
Here’s the final result:
I used CSS variables throughout so you can change the colors of the shadows and the size of the element.
Casting A Shadow Of A Shadow
If we learned anything from that last example, it’s that shadows are visually interesting: they can bend, fade, intersect, and transition. But what about a shadow casting another shadow? Can we create a shadow of an element’s shadow?
This is not the same as stacking layers of shadows as we did earlier. Rather, we will be making a silhouette of a shadow. And because we have a second way to add shadows to elements with the CSS drop-shadow()
filter, we can do exactly that.
A drop-shadow() is a little different than a box-shadow
. Where a box-shadow
casts a shadow along the physical edges of the element’s bounding box, a drop-shadow()
ignores the box and casts a shadow along the element’s shape.
When drop-shadow()
is given to an element with a box-shadow
, the shadow from the box-shadow
will cast a shadow of its own. We can combine these to make interesting effects, like a Venn diagram shape.
.item { box-shadow: 0 0 20px black ; filter: drop-shadow(-30px 0 0 blue);
}
This simple combination of box and drop shadows can lead to interesting designs, like shadows that cast shadows. Let’s start with some HTML that includes the same .item
element we used in the last section. This time, we’ll place two child elements inside it, another div
and an img
:
<div class="item"> <div class="background"></div> <img src="image.jpeg" />
</div> <!-- more items -->
The .item
is merely serving as a container this time. The real work happens on the .background
child element. The image is purely there for decoration. We’re going to set a box-shadow
on the .background
element, then add a stack of three drop-shadow()
layers to it:
/* third circle in the following demo */
.item > .background { box-shadow: 0 0 40px rgb(255 0 0 / .5); filter: drop-shadow(-20px 0 0 rgb(255 0 0 / .5)) drop-shadow(20px 0 0 rgb(255 0 0 / .5)) drop-shadow(20px 0 0 rgb(255 0 0 / .5));
}
We can also use transitions with these effects (as in the middle circle below).
The Textual Shadow
The last effect we’re going to look at involves the CSS text-shadow
property. It’s actually less of a complicated “trick” than it is a demonstration of using and showing just the shadow of a text element for color purposes.
Specifically, I’m talking about transparent text with a shadow on it:
/* second column in the below demo */
p { color: transparent; text-shadow: 1px 1px 0 black;
}
Notice the emoji? Instead of the full-color deal, we normally get, this emoji is more like an icon filled with a solid color. This is one way to make a quick and dirty icon system without drawing them or working with files.
We could have also pulled this off with background-clip: text
to clip around the shape of the emoji or apply a drop-shadow()
. However, that affects the background, limiting where it can be used. Plus, I like the idea of using text-shadow
with text elements since that’s what it’s used for, and emoji are part of the text.
You might think there’s a “gotcha” with underlines. For example, text-shadow
ignores the text decoration of links.
No big deal. If you need to support underlines, we can reach for the CSS text-decoration
and text-underline-offset
properties:
p { color: transparent; text-shadow: 1px 1px 0 black; text-decoration-line: underline; text-decoration color: black; text-underline-offset: 3px;
}
Conclusion
That’s a look at three interesting ways to use CSS shadows as more than that thing you use to add depth. We looked at one way that uses inset shadows to hide the contents of an element’s background for a neat hover effect. Then there was the idea of combining box-shadow
and drop-shadow()
to cast a shadow of another shadow. We capped things off with a quick way to manipulate text and emoji with text-shadow
.
I hope these experiments give you the inspiration to do some CSS shadow experiments of your own. Shadows and gradients are perhaps the two most important CSS features for “drawing” with CSS, like many of the examples you’ll see on Lynn Fisher’s A Single Div project. Shadows have incredible browser support, so the options are plentiful as far as what we can do with them.
(gg, yk)