CSS shadow techniques beyond box-shadow: Alternatives and pitfalls

189
141

Anyone who has worked on web design or coding has probably added a shadow at least once. On the other hand, relatively few people may have stopped to think about how many different expressions and technical variations shadows actually offer.

box-shadow is the standard way to add shadows in CSS, but there are many other techniques and approaches as well. Shadow treatment is an important theme not only on the web, but also in design trends that continue to evolve year by year.

For example, long shadows, which were popular a little while ago, and Neumorphism, which has been called one of this year’s trends, are both unique visual styles that make effective use of shadow techniques.

▼ Examples of a long shadow created with CSS (top image) and Neumorphism (bottom image): Many current design trends make use of shadows

In this article, we will explain a variety of techniques and approaches for creating shadows, along with the different kinds of expressions each technique can achieve.

First, understand the basics of box-shadow

The most fundamental CSS property for adding shadows is box-shadow. As the name suggests, box-shadow draws a shadow for an element’s box area (the border and everything inside it).

Review: How to use box-shadow

Let’s start by reviewing the basics of box-shadow.

Basic box-shadow syntax

Even this basic form can produce a wide range of effects.

Basic box-shadow examples

/* 1. Basic box-shadow */
.basic1 {
  box-shadow: 0 10px 25px 0 rgba(0, 0, 0, .5);
}
/* 2. Add an inner shadow with inset. Rounded corners and circles also work */
.basic2 {
  box-shadow: inset 0 10px 25px 0 rgba(0, 0, 0, .5);
}
/* 3. You can freely set the shadow color and opacity */
.basic3 {
  box-shadow: 0 10px 25px 0 rgba(60, 194, 235, 0.5);
}
/* 4. Set blur to 0 to create a border-like effect */
.basic4 {
  box-shadow: 15px 15px 0px 0 rgb(60, 194, 235);
}

Useful even for Material Design: layering multiple shadows

You can also freely stack as many shadows as you like. Let’s look at some examples.

Basic box-shadow examples

/* 1. A smooth shadow made from 6 box-shadow layers */
.layer1 {
  box-shadow:
    0 1.9px 2.5px rgba(0, 0, 0, 0.057),
    0 5px 6.1px rgba(0, 0, 0, 0.076),
    0 10.1px 11.4px rgba(0, 0, 0, 0.086),
    0 19.2px 19.8px rgba(0, 0, 0, 0.092),
    0 38.4px 34.8px rgba(0, 0, 0, 0.1),
    0 101px 74px rgba(0, 0, 0, 0.13);
}
/* 2. Example applying different directions and colors to each shadow */
.layer2 {
  box-shadow:
    -10px 10px 25px rgba(230, 180, 15, 0.9),
    10px -10px 25px rgba(8, 131, 161, 0.9)
}
/* 3. Layering sharp shadows can also create a stacked-paper effect */
.layer3 {
  box-shadow:
    0 20px 0 -10px rgb(198, 224, 231),
    0 40px 0 -20px rgb(105, 171, 209),
    0 60px 0 -30px rgb(27, 115, 165)
}

At first glance, stacking multiple shadows might seem like a special technique. But if you look closely, you will notice it is used frequently even in everyday designs, such as Google’s Material Design.

Example of multiple shadows The image above shows the shadow applied to a Google Drive dialog as displayed in developer tools. It is a subtle shadow, but you can see that it is composed of three box-shadow values in order to look natural.

box-shadow alone can’t reproduce this! Original shadows using pseudo-elements

box-shadow is simple, but precisely because it is simple, there are many things it cannot do. In the image below (left), a light blue box-shadow is cast over a checkered background. Depending on the style, that may be acceptable, but doesn’t the version on the right look more realistic as a shadow?

Example of multiple shadows

Creating realistic shadows with pseudo-elements

box-shadow simply applies a blur to a box of the specified color, so depending on the combination of shadow color and background, it may not look like a natural shadow. To make it look more natural, you can use CSS filter and mix-blend-mode. (Note: filter and mix-blend-mode, which are explained in this section, are not supported in IE.)

.box::after {
  /* Create a box of the same size with a pseudo-element and place it behind using position: absolute */
  content: '';
  display: block;
  position: absolute;
  z-index: -1;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;

  /* ① Fill the pseudo-element box with the shadow color */
  background-color: rgb(42, 159, 226);
  /* ② Blur it with a blur filter */
  filter: blur(15px);
  /* ③ Adjust position and size */
  transform: translateY(10px) scale(1.05);
  /* ④ Composite with multiply */
  mix-blend-mode: multiply;
}

① Fill the pseudo-element with the shadow color, ② blur it with a blur filter, ③ adjust its position and size, and ④ finally composite it using multiply. Compared with box-shadow, which takes only a single line, this is clearly more cumbersome. On the other hand, it gives you the advantage of free customization, such as adjusting the pseudo-element’s shape and size or using different filters.

Let’s look at a few examples.

Using gradients or images as shadows

Unlike the shadow color in box-shadow, the background of a pseudo-element can be anything you like, including gradients and images. By blurring that content into a shadow, you can create memorable shadows that match your site’s color scheme or background image.

Examples of using gradients or images as shadows

For more on mastering gradients, see our previous article, 1歩踏み込んでみる! CSSグラデーションのマニアックな世界.

Try changing the blend mode

For an ordinary realistic shadow, multiply is the best choice, but trying different blend modes can produce much more striking effects.

Examples of changing blend modes

As in the example above, you can use color-dodge to create a glowing effect, or color-burn and overlay to produce vivid shadows. Depending on the combination of background and shadow, a wide range of expressions becomes possible. For more on blend modes, also see our previous article, CSSのブレンドモードが素敵! mix-blend-modeを使いこなそう. It explains the full list of blend modes and where to use them, complete with examples.

Another kind of shadow: drop-shadow

There is one more shadow technique you should not forget: drop-shadow. While box-shadow applies a shadow to the rectangular area surrounding an element, drop-shadow generates a shadow that follows the element’s actual rendered content. That “rendered content” includes everything from bitmap images and SVG to text and child elements. When you want to “apply a shadow to exactly what is visible,” use drop-shadow.

Example of applying a shadow to an image with drop-shadow

It looks a lot like box-shadow, but is it slightly different? How to write drop-shadow

Because drop-shadow is a type of CSS filter, its syntax is slightly different, but you can set parameters similar to box-shadow.

filter: drop-shadow syntax

However, there are several differences you need to be aware of.

  • Even with the same numeric values, the appearance of the shadow does not match box-shadow. This is true even when the shape is just a simple rectangle. That is because the calculation method for the blur amount differs between box-shadow and drop-shadow. With the same value, drop-shadow appears more blurred.
  • The inset keyword available in box-shadow cannot be used with drop-shadow.
  • The spread radius, which can be specified as the fourth numeric value in box-shadow, cannot be used with drop-shadow. In major browsers, specifying it at all—even as 0—causes the entire drop-shadow to be treated as invalid, so you should not specify it.
  • drop-shadow (as a CSS filter) cannot be used in IE. (There were methods using proprietary extensions such as filter: progid:DXImageTransform.Microsoft.DropShadow, but those are beyond the scope of this article.)

The figure below compares box-shadow and drop-shadow with the same parameter values. They look similar, but they are not simple substitutes for one another, so caution is required when a high level of fidelity between design and implementation is important.

Comparison of box-shadow and drop-shadow

If overlapping shadows are causing trouble, solve it with drop-shadow

drop-shadow is handy for images and SVG logos, but that is not its only use case.

The following example is a loading spinner created with CSS Animation. It is animated by rotating eight circles (<span> elements).

Comparison of adding shadows to spinners

Of the two spinners, the one on the left uses box-shadow. There are several issues worth noticing.

  • Each individual circle gets its own shadow, so overlapping circles do not appear connected as a single shape.
  • Because the shadows rotate along with the circles, the shadow angles become inconsistent.

By contrast, the spinner on the right uses drop-shadow applied to the spinner as a whole. By applying a single shadow to the rotated group of multiple <span> elements, you can see that the issues above are avoided.

“I applied box-shadow to elements placed close together, and now the shadows overlap!” In a case like that, drop-shadow may solve the problem.

The dark side of shadows: trouble and workarounds

box-shadow and drop-shadow are easy to use and support a wide range of expressions, but because they involve complex internal rendering calculations, trouble often comes with them. In this section, we will introduce issues that tend to occur especially when shadows are combined with animation, along with key points for avoiding them.

Trouble: shadow animation feels slightly choppy (Safari)

There are many cases where you may want to use box-shadow to create a soft floating effect when an element is hovered. In situations like this, if you animate box-shadow with transition without much thought, the shadow animation may appear slightly choppy, as though the frame rate has dropped.

Comparing shadow animation in Chrome and Safari

This phenomenon mainly occurs when the rendered area of the shadow changes, for example by changing the blur radius of box-shadow. The rendering will not be exactly the same, but if smoothness is more important, you can avoid the issue by keeping the blur radius fixed and animating only the opacity.

/* 1. Change the shadow blur radius with transition */
.box1{
  transition: box-shadow 2s ease-out, transform 2s ease-out;
}
.box1:hover {
  box-shadow: 0 15px 10px 5px rgb(0, 0, 0);
  transform: translateY(-10px);
}

/* 2. Change only the shadow opacity with transition */
.box2{
  transition: box-shadow 2s ease-out, transform 2s ease-out;
  box-shadow: 0 15px 10px 5px rgba(0, 0, 0, 0);
}
.box2:hover {
  box-shadow: 0 15px 10px 5px rgba(0, 0, 0, 1);
  transform: translateY(-10px);
}

Trouble: when a shadow appears on hover, the surrounding area gets clipped (Safari)

Hover-related issues can also occur with drop-shadow.

Comparing shadow animation in Chrome and Safari

In Safari, it appears that when drop-shadow changes, redrawing is performed only inside the element’s box. It is a bit cumbersome, but you can work around the issue by wrapping the element you want to shadow in a box with sufficient padding, and then applying drop-shadow to the outer box.

.ok {
  display: inline-block;
  font-size: 0;
  padding: 100px; /* Give enough padding to the element receiving drop-shadow to create sufficient space for the shadow */
  filter: drop-shadow(0 0px 3px rgba(0, 0, 0, .9));
}
.ok:hover {
  filter: drop-shadow(0 10px 60px rgba(0, 0, 0, .9));
}

Trouble: the shadow disappears only during animation (Safari)

This is another issue that occurs in Safari.

We created a hamburger menu icon by animating three bars (<div>) with CSS Transitions. When a drop-shadow is applied to the icon as a whole, Safari fails to display the shadow only while the animation is in progress.

Comparing shadow animation in Chrome and Safari

This issue occurs when the following conditions overlap:

  1. Multiple child elements are animated with transition: transform
  2. Two or more drop-shadow effects are applied to the parent element

The technique of layering multiple shadows is used frequently in Material Design, so if you casually copy and paste a shadow style, you may accidentally run into exactly these conditions.

Trouble: adding many shadows makes animation heavy (Chrome/Firefox)

Finally, here is a performance issue that occurs in Chrome and Firefox. Let’s apply shadows to 100 <div> elements and rotate them with CSS Animation. We will try the following three shadow approaches:

  1. Apply box-shadow to each element
  2. Apply drop-shadow to each element
  3. Apply a single drop-shadow to all 100 elements together

Since the exact numbers vary by environment, we will only present a summary of tests performed at the time of writing on an iMac (iMac 5K, 27-inch, 2019) using the latest OS and browser versions available then.

Open the demo in a new window

Browser 1. box-shadow on each 2. drop-shadow on each 3. grouped drop-shadow
Chrome(80) Fast Extremely slow Fast
Safari(13.1) Fast Moderate Fast
Firefox(74) Fast Fast Slow

As you can see, the trend differs significantly across browsers. In particular, Chrome’s drop-shadow dropped below 10 fps with 200 elements in the environment above, which is far below a practical level.

drop-shadow is slow in Chrome

As a general tendency, Safari handles animations smoothly once they are offloaded to the GPU, whereas Chrome tends to suffer delays when dragged down by the CPU. That said, these tendencies vary depending on the environment and browser version, so when displaying complex animations, there is no substitute for carefully checking behavior in the main target environments. At this point, using box-shadow is the safer choice for shadows on complex animated elements.

Conclusion

In this article, we introduced a variety of shadow techniques and expressions using CSS filters such as drop-shadow and blur, in addition to the commonly used box-shadow. There are important points to watch out for, especially when combining them with animation, such as browser differences and performance issues. Still, if you use them appropriately and with care, CSS alone can achieve highly sophisticated visual effects. Master shadow techniques and try creating the kind of expression that could become the next trend.

Share on social media
Your shares help us keep the site running.
Post on X
Post to Hatena Bookmark
Share
Copy URL
MATSUMOTO Yuki

Front-end engineer. Transitioned from SI and UX consulting into front-end engineering. Skilled at prototyping new ideas from the planning stage. Loves drawing and coding.

Articles by this staff