• Designing Beautiful Shadows in CSS
  • Josh W Comeau
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: Hoarfroster

GIF trailer, careful flow, high content!!

If it helps you, remember to pay attention

Welcome to participate in the project and dig out more excellent articles

introduce

In my humble opinion, all of the best websites and web applications have a very obvious, “real” high quality design in terms of design. To be honest, there are a number of factors that determine the quality of design in very complex ways, but shadow is, without a doubt, a very important component.

But when I looked at other sites for a while, I found that many of the shadows were not perfect, but rather monotonous. The entire Internet is wrapped in what I like to call “fuzzy gray boxes” that don’t look like shadows…

In this tutorial, we’ll learn how to turn this typically fake “ugly shadow” into a lively “pretty shadow”, like this one:

⚠️ Intended audience for this article

This article is written for developers who are familiar with the basics of CSS. Some of the knowledge surrounding shadows, such as box-shadow, HSL () colors, and CSS variables should be familiar.

Why do we use shadows?

We’ll cover some CSS tricks soon, I promise, but before we do, I want to take a step back and talk about why shadows exist in CSS and how we can maximize their use.

Shadows imply hierarchy, and deeper shadows imply greater hierarchy. If we use shadows strategically, we can create an illusion of “depth” on our site, after all, different elements on the page actually float above the background at different levels.

Here’s an example (editor’s note: converted to GIF). Drag the Reveal bar to see what I mean:

I want my application to feel like a world in a browser. Shadows, in my case, create such an illusion.

There are also some tactile benefits — by using different shadows on the Header and dialog boxes, we actually create the impression that the dialog box is closer to us than the Header. Our focus is to push the element towards us, so by hierarchical the dialog, we make it easier for the user to see it in the first place. In other words, we can use hierarchy as a direct attention-grabbing tool!

When we use shadows, I apply them with one of the following goals in mind: EITHER I want to make a particular element more noticeable, or I want to make my application feel more accessible and realistic.

In order for me to achieve these goals, we need to look at the big picture, take a holistic view of our application.

Create a unified environment

For a long time, I didn’t really use shadows properly. 😬

When I want to add a shadow to an element, I add a box-shadow attribute, and then… Tinker with the numbers until they seem to fit.

Here’s the problem — by creating a unique shadow for each element, you end up getting confused by the incongruous shadow friends you’ve created. If our goal is to create the illusion of shadows, we should match each shadow, otherwise the result will be nothing more than a blur of borders:

In the real world, a shadow is a description of a light source. The direction of the shadow depends on the position of the light:

Generally speaking, we should have a fixed light source for all elements on the page. Generally, we choose to place the light source in the upper left corner of the page. :

If CSS had a real lighting system, we would be able to define more lighting for a location, which unfortunately CSS does not.

Instead, we can only determine the expression of a shadow by defining a vertical offset. For example, we want a shadow that is 4px from the vertical and 2px from the horizontal.

Here’s the first tip for unifying all shadows: All shadows on a page should follow the same offset ratio. By unifying the offset ratio, all shadows will appear to be from a very distant light source, such as the sun.

Next we’ll talk more about hierarchies — how exactly can we make a shadow seem elevated and closer to the user?

At this point, we should fine-tune the four variables to achieve a unified user experience.

Experience the following example and note how the values change (editor’s note: converted to GIF) :

The first two numbers are related to the horizontal, while the last two jointly determine the vertical. The vertical deviation is always twice the horizontal deviation.

In addition, when the card is promoted, two other changes occur at the same time:

  • The radius of the shadow blur has been increased.
  • The shadows become more transparent.

(FOR authenticity, I actually increased the size of the card at the same time. In practice, this step can be easily skipped!

There are complex mathematical reasons for these changes, but we can focus on making the shadows look like shadows in the real world.

If you are in a good light in the room, put your hands on your desk top (or other near you) on the surface, then slowly lift up your hands, you can notice the shadow what is changing: shadow with you hand farther from the greater deviation (), more fuzzy fuzzy radius (larger), at the same time began to fade away, greater transparency. If you can’t move your hand easily, you can use other reference objects in the room, such as comparing different shadows around you.

Because there are so many shadows around us and we experience them all the time, we don’t have to memorize any rules. We just need to apply our understanding to the design of shadows — although this requires us to calculate the offsets in our minds; We need to start designing HTML elements as entities!

So, to sum up:

  1. Each element on the page should be shaded by a fixed light source.
  2. box-shadowProperties using horizontal and vertical offsets represent the position of the light source. To ensure uniformity, each shadow should have a certain offset ratio.
  3. When an element is closer to the user, the offset should increase, the blur radius should increase, and the opacity should decrease.
  4. We can use our intuition to skip a little bit of the calculation.

tip

The shadow layer

Modern 3D drawing tools, such as Blender, can create realistic shadows and lights using a familiar technique called “light chase” mode.

In ray tracing, countless beams of light are emitted from the Camera and reflect off the surface thousands of times. This is an extremely computationally intensive technique and can take several minutes to form a single image!

Web users have less patience, so box-shadow algorithms are more primitive. He creates a shape from the shape of the element, and then applies basic fuzzy algorithms to it.

As a result, our shadows will never be as real as the photos. But, anyway, we can still use a useful technique called “shadow layering” to make it a little better.

Instead of using a single shadow, we overlay different shadows on an element by using slightly different offsets and radii:

Code Playground

HTML:

<style>
  .traditional.box {
    box-shadow: 0 6px 6px hsl(0deg 0% 0% / 0.3);
  }
  .layered.box {
    box-shadow: 0 1px 1px hsl(0deg 0% 0% / 0.075), 0 2px 2px hsl(0deg 0% 0% /
            0.075), 0 4px 4px hsl(0deg 0% 0% / 0.075), 0 8px 8px hsl(0deg 0%
            0% / 0.075), 0 16px 16px hsl(0deg 0% 0% / 0.075);
  }
</style>

<section class="wrapper">
  <div class="traditional box"></div>
  <div class="layered box"></div>
</section>
Copy the code

CSS:

.wrapper {
  display: flex;
  gap: 32px;
}

.box {
  width: 100px;
  height: 100px;
  border-radius: 8px;
  background-color: white;
}
Copy the code

By layering the shadows, we were able to construct a shadow that was slightly realistic.

This technique is described in Tobias Ahlin’s excellent article as “Smoother and Sharper Shadows with Layered Box-shadow” (using shadow layering to create softer, clearer Shadows). .

Philipp Brumm created a great tool to generate such shadows (shadows.brumm.af) :

⚠️ Trade-off between performance and effectiveness!

Layered shadows are undeniably great, but they also have requirements — performance. If we overlay five times the shadow, our equipment also consumes five times more resources to achieve our effect!

This is nothing for modern devices, but it will significantly slow down rendering on mid – and low-end mobile devices.

Either way, always be sure to test! In my experience, layered shadows don’t significantly affect performance, but I’ve also never tried to use hundreds of shadows on a page at the same time.

Also, animating an element with layered shadows may seem like a bad idea.

Match the shade of color

Having said all that, our shadows are still dull translucent black, as in HSL (0DEg 0% / 0.4). It’s too boring to be true.

When we put black as a shadow color on top of the background color, it actually doesn’t just make the color darker, it also makes it less saturated.

Compare these two box models:

Code Playground

HTML:

<style>
  body {
    background-color: hsl(220deg 100% 80%);
  }
  .black.box {
    background-color: hsl(0deg 0% 0% / 0.25);
  }
  .darker.box {
    background-color: hsl(220deg 100% 72%);
  }
</style>

<section class="wrapper">
  <div class="black box"></div>
  <div class="darker box"></div>
</section>
Copy the code

CSS:

.wrapper {
  display: flex;
  gap: 32px;
}
.box {
  width: 100px;
  height: 100px;
  border-radius: 8px;
}
Copy the code

The box on the left uses a translucent black color, while the box on the right uses the hue and saturation of the background color, but with the brightness turned down. We brought up a more colorful box model!

When we use a darker color, we have a similar effect:

Code Playground

HTML:

<style>
  body {
    background-color: hsl(220deg 100% 80%);
  }

  .black.box {
    --shadow-color: hsl(0deg 0% 0% / 0.25);
  }
  .darker.box {
    --shadow-color: hsl(220deg 100% 55%);
  }
  .box {
    filter: drop-shadow(1px 2px 8px var(--shadow-color));
  }
</style>

<section class="wrapper">
  <div class="black box"></div>
  <div class="darker box"></div>
</section>
Copy the code

CSS:

.wrapper {
  display: flex;
  gap: 32px;
}
.box {
  width: 100px;
  height: 100px;
  border-radius: 8px;
  background: white;
}
Copy the code

As far as I’m concerned, neither of the two shadows I see with my eyes is quite right — there’s no doubt that the left is too saturated, and the right is so saturated that it looks like a glow rather than a shadow…

We can do a few experiments to find colors that look like Princess Goldilocks:

Code Playground

HTML:

<style>
  body {
    background-color: hsl(220deg 100% 80%);
  }
  .black.box {
    --shadow-color: hsl(0deg 0% 0% / 0.5);
  }
  .darker.box {
    --shadow-color: hsl(220deg 100% 50%);
  }
  .goldilocks.box {
    --shadow-color: hsl(220deg 60% 50%);
  }
  .box {
    filter: drop-shadow(1px 2px 8px var(--shadow-color));
  }
</style>

<section class="wrapper">
  <div class="black box">Too grey</div>
  <div class="darker box">Too bright</div>
  <div class="goldilocks box">Just right</div>
</section>
Copy the code

CSS:

.wrapper {
  display: flex;
  flex-direction: column;
  gap: 32px;
}
.box {
  width: 200px;
  height: 100px;
  border-radius: 8px;
  background: white;
  display: grid;
  place-content: center;
}
Copy the code

By using the same hue with lower saturation and brightness, we can create an unusually realistic, non-washed pale desaturated shadow.

⚠️ The relationship between saturation and brightness!

If you are familiar with the HSL color format, you should be able to understand that saturation and brightness affect color independently.

But… Isn’t that a little weird? When we lower the brightness, it seems to have an effect on saturation.

To be honest, if you answer this question, things get complicated… Read on if you are interested (or you can skip the quote).

For example, here are two boxes that have the same saturation percentage (100% saturation), but they look different to us…

Code Playground

HTML:

<style>
  .box.one {
    background-color: hsl(220deg 100% 70%);
  }
  .box.two {
    background-color: hsl(220deg 100% 50%);
  }
</style>

<section class="wrapper">
  <div class="box one"></div>
  <div class="box two"></div>
</section>
Copy the code

CSS:

body {
  background-color: hsl(220deg 100% 80%);
}
.wrapper {
  display: flex;
  gap: 32px;
}
.box {
  width: 100px;
  height: 100px;
  border-radius: 8px;
}
Copy the code

That’s because the level of brightness, just like the amount of paint we add to make it lighter or darker, doesn’t vary. So the saturation is not completely independent of the color.

This can actually be illustrated in two extremes:

  • HSL (0DEG 0% 100%) is pure white, 0% saturation.
  • HSL (0DEG 100% 100%) is also pure white, but the saturation is 100%.

If we adjust the brightness to 95%, there will be a difference, though not a big one:

The same is true for very black colors:

When we are just halfway bright, saturation determines the color completely:

That’s what I think: 50% brightness is the default color for all hues, and at 50% brightness, brightness has no effect on saturation.

When we increase or decrease the brightness from 50%, which is a great dividing line, we reduce the amount of paint used, and the color cannot be completely saturated.

Saturation % is a relative unit based on the amount of paint used to determine brightness.

This is why we should desaturate early in our shadow example, bringing the brightness closer to 50% so that the saturation can construct a wider range of color variations. To make sure that the color looks and feels as we want, we need to adjust the saturation accordingly.

Get all the principles together

We’ve come up with three different tips:

  1. Create a harmonious and unified environment by unifying the location of shadows.
  2. Use the layered shadow technique to create more realistic shadows.
  3. Fine-tune the shadows to prevent the color from becoming desaturated like a washed pigment.

Here is an example of a shadow that combines all three tips:

Code Playground

HTML:

<style>
  .first.box {
    width: 50px;
    height: 50px;
    box-shadow: 0.5 px. 1px 1px hsl(220deg 60% 50% / 0.7);
  }
  .second.box {
    width: 100px;
    height: 100px;
    box-shadow: 1px 2px 2px hsl(220deg 60% 50% / 0.333), 2px 4px 4px hsl(220deg
            60% 50% / 0.333), 3px 6px 6px hsl(220deg 60% 50% / 0.333);
  }
  .third.box {
    width: 150px;
    height: 150px;
    box-shadow: 1px 2px 2px hsl(220deg 60% 50% / 0.2), 2px 4px 4px hsl(220deg
            60% 50% / 0.2), 4px 8px 8px hsl(220deg 60% 50% / 0.2), 8px 16px 16px
        hsl(220deg 60% 50% / 0.2), 16px 32px 32px hsl(220deg 60% 50% / 0.2);
  }
</style>

<section class="wrapper">
  <div class="first box"></div>
  <div class="second box"></div>
  <div class="third box"></div>
</section>
Copy the code

CSS:

body {
  background-color: hsl(220deg 100% 80%);
}
.wrapper {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 32px;
}
.box {
  border-radius: 8px;
  background: white;
  display: grid;
  place-content: center;
}
Copy the code

Incorporate these tips into the design system

We need to customize the shadows for their height and environment. In a world with design systems and a limited number of design principles, this seems counterproductive. Can we really standardize these shadows?

We absolutely can! Although it needs the help of some modern tools.

For example, here is an example of how I use the React, Styled components, and CSS variables to solve this problem:

Code Playground

JSX:

const ELEVATIONS = {
  small: '0.5px 1px 1px HSL (var(--shadow-color) / 0.7).medium: '1px 2px 2px HSL (var(--shadow-color) / 0.333), 2px 4px 4px HSL (--shadow-color) / 0.333), 3px 6px 6px HSL (var(--shadow-color) / 0.333).large: '1px 2px 2px HSL (var(--shadow-color) / 0.2), 2px 4px 2px HSL (var(--shadow-color) / 0.2), 4px 5px 5px HSL (var(--shadow-color) / 0.2), 8px 5px 5px HSL (var(--shadow-color) / 0.2), 16px 32px 32px HSL (var(--shadow-color) / 0.2)};const Wrapper = styled.div` --shadow-color: 0deg 0% 50%; background-color: hsl(0deg 0% 95%); display: flex; flex-direction: column; align-items: center; gap: 16px; padding: 32px; `;
const BlueWrapper = styled(Wrapper)` --shadow-color: 220deg 60% 50%; background-color: hsl(220deg 100% 80%); padding: 32px; `;

const Box = styled.div` border-radius: 8px; background: white; `;
const SubtleBox = styled(Box)`
  width: 50px;
  height: 50px;
  box-shadow: ${ELEVATIONS.small};
`;
const ElevatedBox = styled(Box)`
  width: 100px;
  height: 100px;
  box-shadow: ${ELEVATIONS.large};
`;

render(
  <>
    <Wrapper>
      <SubtleBox />
      <ElevatedBox />
    </Wrapper>
    <BlueWrapper>
      <SubtleBox />
      <ElevatedBox />
    </BlueWrapper>
  </>
);
Copy the code

I constructed a static ELEVATIONS object and defined three levels for it. The color of each shadow is based on a CSS variable called shadow-color.

Every time I change the background color in Wrapper and BlueWrapper, I also change –shadow-color, so that any child elements that use shadows automatically inherit this property.

If CSS variables are new to you, all of this will make your head spin. However, this is just one example — use this as a base to build your desired shadow as you wish!

Extension reading: drop-shadow

Throughout this tutorial, we’ve been using the box-shadow property. Box-shadow is an excellent versatile way to construct shadows, but it’s not the only shadow option we have in CSS. 😮

For example, filter: drop-shadow:

Code Playground

HTML:

<div class="wrapper">
  <div class="box with-box-shadow">box-shadow</div>
  <div class="box with-drop-shadow">drop-shadow</div>
</div>
Copy the code

CSS:

.with-box-shadow {
  box-shadow: 1px 2px 4px hsl(220deg 60% 50%);
}

.with-drop-shadow {
  filter: drop-shadow(1px 2px 4px hsl(220deg 60% 50%));
}

.box {
  width: 150px;
  height: 150px;
  background-color: white;
  border-radius: 8px;
  display: flex;
  justify-content: center;
  align-items: center;
}

.wrapper {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 32px;
  height: 100vh;
}
body {
  padding: 0;
}
Copy the code

The syntax looks almost the same, but the shadows it produces are different. This is because the filter attribute is actually a CSS cousin of an SVG filter. Drop-shadow uses Gaussian blur of SVG, a different blur algorithm than box-shadow.

There are some other important differences between the two, but for now I want to focus on drop-shadow’s superpower: it can outline the shape of an element.

For example, if we use it on an image with transparency, the shadow will only be applied to the opaque part:

This doesn’t just apply to images, but it also applies to HTML elements! See how we can use it to apply shadows to our tooltips:

Code Playground

HTML:

<style>
  .tooltip {
    filter: drop-shadow(1px 2px 8px hsl(220deg 60% 50% / 0.3)) drop-shadow(
        2px 4px 16px hsl(220deg 60% 50% / 0.3))drop-shadow(4px 8px 32px hsl(220deg 60% 50% / 0.3));
  }
</style>

<div class="tooltip" role="tooltip">Words and things</div>
Copy the code

CSS:

.tooltip {
  position: relative;
  background: white;
  padding: 20px 24px;
  border-radius: 1px;
}

/* Create tooltip with clip-path. We've covered this property in detail in the course, and I plan to cover it further in the future! * /
.tooltip::before {
  content: "";
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  margin: auto;
  width: 30px;
  height: 20px;
  background: white;
  clip-path: polygon(0% 0%.50% 100%.100% 0%);
  transform: translateY(calc(+100% - 1px));
}
body {
  padding: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
}
Copy the code

(The change was subtle, after all, we used a soft shadow; You can try reducing the blur radius to see a clearer outline.)

Here’s another quick tip: Unlike box-shadow, Filter is a hardware-accelerated property in Chrome, and most browsers should do the same. This means that it is processed by the GPU rather than the CPU, which can greatly improve performance, especially during animation. But remember to add one more line of will-change: Transform to avoid some of the bugs in Safari.

Although we are going too far off topic, this is enough to show that filter is the one we should adopt! Of course, I plan to continue to expand the Filter attribute in the future and write a more detailed article about it!

I hope this tutorial inspires you to add or fine-tune some of your shadows! To be honest, developers who make shadows at this level are as rare as pandas, and that means most users don’t care about shadows that are so good and realistic. There is no doubt that just by putting a little effort into doing so, our products will definitely make a difference in web design! .

If you find any mistakes in your translation or other areas that need to be improved, you are welcome to the Nuggets Translation Program to revise and PR your translation, and you can also get the corresponding reward points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.