Close menu

Mastering box-shadow

Drop shadows are beautiful! Not only do they please visually, they also give content and UI elements a sense of depth and levels, which can be extremely important and useful in differentiating elements.

Box Shadow

CSS3 introduced the box-shadow property which makes creating simple as well as complex shadow effects trivial. For those developers who have been building websites pre-HTML5 & CSS3, it has been a revelation, because before this wonderful feature, drop shadow effects were achieved using a number of different CSS 2.1 tricks consisting of convoluted layouts (including table layouts! Eeek 🤭) and 24-bit png background images!

Syntax

It's somewhat surprising that box-shadow is not a shorthand for more specific settings of the shadow, given how many aspects of the shadow it can actually control! But this is actually a positive! Not only does it mean a shadow can be set in one line, it also makes for less code transferred down the line and terse syntax that's easy to remember.

Let's look at the whole spectrum of the syntax before we look at the different forms of using it.


box-shadow: inset <x-offset> <y-offset> <blur-radius> <spread-radius> <color>;

Simplest Form

In this usage, you simply provide the 2 required length values: x-offset and y-offset. These 2 values are, as you might have guessed, the horizontal and vertical distance from the element's top-left corner, at which the shadow will be drawn.


box-shadow: 10px 10px;

Positive values move the shadow to the right & bottom, while negative values offset it to the left and top.

In this form, the shadow will be sharp edged and fully opaque, using currentcolor for the colour - which is whatever color value is being applied (inherited or explicitly set) to the element.

To understand this better, it helps to compare to IRL shadows; these offsets are like changing the position of a light source along the x or y axis of a plane, above an object which casts a shadow onto a surface.

With Colour

You can also explicitly set the colour of the shadow. When doing this, it must always be the last value supplied:


box-shadow: 10px 10px rgba(0, 0, 255, 0.2);

It can be any valid CSS colour value.

Tip: use rgba values to easily control the shadow's transparency/opacity.

Blur

The shadows we've seen so far have been sharp edged, which is nice if you're using flat design principles or want a very stylised effect, but most of the time we want some of that hypnotic photorealism to shadows.

That's where the blur-radius option is used. It's a css length value which controls the blur applied to the whole shadow. As the shadow blurs, the dissipating edges become translucent.

This must always be the third length value you provide, before any colour value, and it must be positive:

Think of this setting as changing the type of light given off by the light source. The lower the value, the more direct the light source, like a spotlight, the greater the value, the more diffuse the source, like a frosted bulb.


.box__a {
    box-shadow: 10px 10px 5px;
}

.box__b {
    box-shadow: 10px 10px 5px rgba(0, 0, 255, 0.2);
}
box__a
box__b

Applying blur will diffuse the shadow out from the boundaries made by the offset, this means that a blurred shadow may extend outward further than you intended. While reducing your offsets may reduce the distance at which the shadow comes to an end, this may not provide the type of shadow you wanted.

To fine tune issues such as this, you must use the spread value.

Spread

This value determines the 'size' of the shadow cast by the element. By default, this is the same as the size of element casting it.

Think of it as being similar to the distance of the light source from the object which casts the shadow; the closer (larger values) the light source gets to the object, the bigger the shadow.

It's the fourth length value you provide, before any colour value.


.box__a {
  box-shadow: 10px 10px 5px 8px;
}

.box__b {
  box-shadow: 10px 10px 5px 8px rgba(0, 0, 0, 0.5);
}
box__a
box__b

Spread also accepts negative values, which has the effect of making the shadow smaller than the element casting it. This is useful if you want to apply more blur, but reduce how far the blur extends beyond the edges of the element.


.box__a {
  box-shadow: 10px 10px 5px -16px;
}

.box__b {
  box-shadow: 10px 10px 5px -16px rgba(0, 0, 0, 0.5);
}
box__a
box__b

Compound Shadows

One shadow adds massive aesthetic improvements, but box-shadow allows us to create even more complex effects by setting multiple shadows.

What's more, doing so is super simple! Just add comma separated values of the syntax for each shadow:


box-shadow: 5px 5px 5px red,
            10px 10px 5px green,
            15px 15px 5px blue;

This is extremely useful if your design calls for photorealism, for example, if your elements/UI widgets are translucent, you may want to use a light, diffuse shadow in the colour of the element, as well a grey/black shadow.


box-shadow: 4px 4px 16px 2px rgba(255, 119, 5, 0.6),
            8px 8px 40px 10px rgba(0, 0, 0, 0.3);

Order

When specifying multiple shadows, remember that they are 'cast' in a stack and ordered as they are provided; the first in the list is at the top, the next underneath that and so on. Keep this in mind when trying to achieve photorealistic effects. You may have to reorder the shadows in your declaration to get the look you're after.

Inner Shadows

Sometimes you want an inner-shadow instead of a drop shadow, and doing this is super simple too, with the optional inset keyword.

Simply add this keyword at the front of the rule and the shadow is flipped into the inside of the element. All other values behave the same.


box-shadow: inset 4px 4px 16px 2px rgba(0, 0, 0, 0.6);

Box Shadow Means Box

Are you wondering about why it's box-shadow and not drop-shadow?

Remember that CSS uses the box-model for layout; this basically means that to CSS every element is contained inside a box or frame. box-shadow applies a shadow to that box. The name tells us that the shadow it creates is around the element's frame and not it's contents.

Usually, an element's frame is in fact a box, as in rectangular, however an element with border-radius also causes the shadow to use the same radius around the element.


.standard {
    box-shadow: 4px 4px 16px 2px rgba(0, 0, 0, 0.6);
}

.rounded {
    border-radius: 100%;
    box-shadow: 4px 4px 16px 2px rgba(0, 0, 0, 0.6);
}
standard
rounded

CSS Filter: Drop Shadow

So what about actual drop shadows?

Enter CSS filter, and specifically, the drop-shadow function. This allows drop shadows to be applied to images with alpha transparency, such as png, as well as svg.


img {
    filter: drop-shadow(5px 5px 5px #999);
}
scriptuccinojs logo

The syntax for specifying the shadow is very similar to box-shadow, but has a few key differences:

  • Inset and spread are not allowed and not supported.
  • The value must be inside the drop-shadow css function.
  • Multiple shadows are applied as space separated drop-shadow declarations in the filter rule.

Note on Performance

We love box-shadow, but as you might expect, and as has been demonstrated through experiments around the web, it is computationally expensive and so does have an effect on performance, in particular, the scrolling experience (frame rate).

The larger or more complex the shadow, the more processing power needed, this can be particular taxing on lower spec devices, and in all cases, have an energy impact leading to more battery drain.

As we specialise in building high performance sites, we tend to only apply complex shadows on larger devices that are likely to have better processors and bigger batteries, sticking to much simpler single shadows for the smaller smartphone size screen devices, or else eliminating them completely where it doesn't have a big UI/UX effect.

First published: 28/10/2020