Smoother & sharper shadows with layered box-shadows

Default
Smooth

As light hits an object and a shadow is cast, the shadow can take on a myriad of unique characteristics. If you try to capture the subtleties of a real shadow with box-shadow then, well, you’re pretty much out of luck. The box-shadow CSS property isn’t exactly built to encourage expressiveness. It essentially produces a blurred silhouette of an object—you can change its offset, blur radius, spread, and color, but that’s it. We can’t get anywhere near to expressing the complexities and nuances of shadows in real life.

But with a simple CSS technique, we can expand our range of options. If we use layered box-shadows we can get more fine-grained control over how shadows are rendered:

box-shadow 0 6px 6px rgba(0,0,0,0.2);
Layered box-shadows gradually increasing offset/blur

Look at how square and clumsy the default box-shadow effect (first box) looks compared to the layered box-shadow (second box). We can achieve this effect by creating multiple box-shadows (separating each shadow with a comma), and increasing the offset and blur for every shadow (the box-shadow syntax is X-offset Y-offset blur color):

/* Default box-shadow */
.box {
  box-shadow: 0 3px 3px rgba(0,0,0,0.2);
}

/* Create smoother box-shadows by layering multiple
 * shadows with gradually increasing radius and offset */
.shadow-5 {
  box-shadow: 0 1px 1px rgba(0,0,0,0.12), 
              0 2px 2px rgba(0,0,0,0.12), 
              0 4px 4px rgba(0,0,0,0.12), 
              0 8px 8px rgba(0,0,0,0.12),
              0 16px 16px rgba(0,0,0,0.12);
}

This simple layering technique gives us more control over the rendering of shadows, and with it we can fine-tune sharpness, distance, and spread. You can for example increase or decrease the number of shadows to create a smaller or larger spread. (Note that if you increase the number of layers you’ll have to decrease the alpha value for each layer if you wish to keep the strength somewhat the same.)

Layered box-shadows 4 shadows with 15% alpha
Layered box-shadows 6 shadows with 11% alpha
.shadow-4 {
  box-shadow: 0 1px 1px rgba(0,0,0,0.15), 
              0 2px 2px rgba(0,0,0,0.15), 
              0 4px 4px rgba(0,0,0,0.15), 
              0 8px 8px rgba(0,0,0,0.15);
}

.shadow-6 {
  box-shadow: 0 1px 1px rgba(0,0,0,0.11), 
              0 2px 2px rgba(0,0,0,0.11), 
              0 4px 4px rgba(0,0,0,0.11), 
              0 8px 8px rgba(0,0,0,0.11), 
              0 16px 16px rgba(0,0,0,0.11), 
              0 32px 32px rgba(0,0,0,0.11);
}

Controlling sharpness is as easy as controlling spread, but we can use both the alpha value and the blur value of each layer to change the concentration of depth and the blur radius of the shadow respectively.

The examples above use the same alpha value for all layers, but we can let the alpha value decrease or increase with every layer to create more or less diffuse shadows. For the more concentrated shadow below, the innermost shadow (with the least offset and blur) has the highest alpha value, and it decreases with every layer. The opposite is true for the more diffuse shadow of the second box, where the innermost layer has the lowest alpha value:

Sharp Shadows with decreasing alpha
Diffuse Shadows with increasing alpha
.blog-shadow-sharp {
  box-shadow: 0 1px 1px rgba(0,0,0,0.25), 
              0 2px 2px rgba(0,0,0,0.20), 
              0 4px 4px rgba(0,0,0,0.15), 
              0 8px 8px rgba(0,0,0,0.10),
              0 16px 16px rgba(0,0,0,0.05);
}

.blog-shadow-diffuse {
    box-shadow: 0 1px 1px rgba(0,0,0,0.08), 
                0 2px 2px rgba(0,0,0,0.12), 
                0 4px 4px rgba(0,0,0,0.16), 
                0 8px 8px rgba(0,0,0,0.20);
}

We can also increase the blur in higher incremenents, to increase the spread and create softer, almost dreamy, effects:

Dreamy soft Higher blur increase
.blog-shadow-dreamy {
    box-shadow: 0 1px 2px rgba(0,0,0,0.07), 
                0 2px 4px rgba(0,0,0,0.07), 
                0 4px 8px rgba(0,0,0,0.07), 
                0 8px 16px rgba(0,0,0,0.07),
                0 16px 32px rgba(0,0,0,0.07), 
                0 32px 64px rgba(0,0,0,0.07);
}

Finally, we can control the distance by decoupling the blur radius and Y-offset, and increase the offset in bigger or smaller increments:

Shorter Shadows with smaller distances
Longer Shadows with larger distances
.shadow-shorter {
  box-shadow: 0 1px 1px rgba(0,0,0,0.11), 
              0 2px 2px rgba(0,0,0,0.11), 
              0 4px 4px rgba(0,0,0,0.11), 
              0 6px 8px rgba(0,0,0,0.11),
              0 8px 16px rgba(0,0,0,0.11);
}

.shadow-longer {
  box-shadow: 0 2px 1px rgba(0,0,0,0.09), 
              0 4px 2px rgba(0,0,0,0.09), 
              0 8px 4px rgba(0,0,0,0.09), 
              0 16px 8px rgba(0,0,0,0.09),
              0 32px 16px rgba(0,0,0,0.09);
}

Which combination of all of these techniques to use is of course highly dependent on the context that you’re working in, but with layered shadows we can at the very least gain some more control to help us achieve our desired look and feel.

Further reading