shadow – CSS-Tricks https://css-tricks.com Tips, Tricks, and Techniques on using Cascading Style Sheets. Fri, 10 Feb 2023 15:13:48 +0000 en-US hourly 1 https://wordpress.org/?v=6.4.3 https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/07/star.png?fit=32%2C32&ssl=1 shadow – CSS-Tricks https://css-tricks.com 32 32 45537868 Different Ways to Get CSS Gradient Shadows https://css-tricks.com/different-ways-to-get-css-gradient-shadows/ https://css-tricks.com/different-ways-to-get-css-gradient-shadows/#respond Fri, 10 Feb 2023 15:13:42 +0000 https://css-tricks.com/?p=376764 It’s a question I hear asked quite often: Is it possible to create shadows from gradients instead of solid colors? There is no specific CSS property that does this (believe me, I’ve looked) and any blog post you find about …


Different Ways to Get CSS Gradient Shadows originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
It’s a question I hear asked quite often: Is it possible to create shadows from gradients instead of solid colors? There is no specific CSS property that does this (believe me, I’ve looked) and any blog post you find about it is basically a lot of CSS tricks to approximate a gradient. We’ll actually cover some of those as we go.

But first… another article about gradient shadows? Really?

Yes, this is yet another post on the topic, but it is different. Together, we’re going to push the limits to get a solution that covers something I haven’t seen anywhere else: transparency. Most of the tricks work if the element has a non-transparent background but what if we have a transparent background? We will explore this case here!

Before we start, let me introduce my gradient shadows generator. All you have to do is to adjust the configuration, and get the code. But follow along because I’m going to help you understand all the logic behind the generated code.

Non-transparent solution

Let’s start with the solution that’ll work for 80% of most cases. The most typical case: you are using an element with a background, and you need to add a gradient shadow to it. No transparency issues to consider there.

The solution is to rely on a pseudo-element where the gradient is defined. You place it behind the actual element and apply a blur filter to it.

.box {
  position: relative;
}
.box::before {
  content: "";
  position: absolute;
  inset: -5px; /* control the spread */
  transform: translate(10px, 8px); /* control the offsets */
  z-index: -1; /* place the element behind */
  background: /* your gradient here */;
  filter: blur(10px); /* control the blur */
}

It looks like a lot of code, and that’s because it is. Here’s how we could have done it with a box-shadow instead if we were using a solid color instead of a gradient.

box-shadow: 10px 8px 10px 5px orange;

That should give you a good idea of what the values in the first snippet are doing. We have X and Y offsets, the blur radius, and the spread distance. Note that we need a negative value for the spread distance that comes from the inset property.

Here’s a demo showing the gradient shadow next to a classic box-shadow:

If you look closely you will notice that both shadows are a little different, especially the blur part. It’s not a surprise because I am pretty sure the filter property’s algorithm works differently than the one for box-shadow. That’s not a big deal since the result is, in the end, quite similar.

This solution is good, but still has a few drawbacks related to the z-index: -1 declaration. Yes, there is “stacking context” happening there!

I applied a transform to the main element, and boom! The shadow is no longer below the element. This is not a bug but the logical result of a stacking context. Don’t worry, I will not start a boring explanation about stacking context (I already did that in a Stack Overflow thread), but I’ll still show you how to work around it.

The first solution that I recommend is to use a 3D transform:

.box {
  position: relative;
  transform-style: preserve-3d;
}
.box::before {
  content: "";
  position: absolute;
  inset: -5px;
  transform: translate3d(10px, 8px, -1px); /* (X, Y, Z) */
  background: /* .. */;
  filter: blur(10px);
}

Instead of using z-index: -1, we will use a negative translation along the Z-axis. We will put everything inside translate3d(). Don’t forget to use transform-style: preserve-3d on the main element; otherwise, the 3D transform won’t take effect.

As far as I know, there is no side effect to this solution… but maybe you see one. If that’s the case, share it in the comment section, and let’s try to find a fix for it!

If for some reason you are unable to use a 3D transform, the other solution is to rely on two pseudo-elements — ::before and ::after. One creates the gradient shadow, and the other reproduces the main background (and other styles you might need). That way, we can easily control the stacking order of both pseudo-elements.

.box {
  position: relative;
  z-index: 0; /* We force a stacking context */
}
/* Creates the shadow */
.box::before {
  content: "";
  position: absolute;
  z-index: -2;
  inset: -5px;
  transform: translate(10px, 8px);
  background: /* .. */;
  filter: blur(10px);
}
/* Reproduces the main element styles */
.box::after {
  content: """;
  position: absolute;
  z-index: -1;
  inset: 0;
  /* Inherit all the decorations defined on the main element */
  background: inherit;
  border: inherit;
  box-shadow: inherit;
}

It’s important to note that we are forcing the main element to create a stacking context by declaring z-index: 0, or any other property that do the same, on it. Also, don’t forget that pseudo-elements consider the padding box of the main element as a reference. So, if the main element has a border, you need to take that into account when defining the pseudo-element styles. You will notice that I am using inset: -2px on ::after to account for the border defined on the main element.

As I said, this solution is probably good enough in a majority of cases where you want a gradient shadow, as long as you don’t need to support transparency. But we are here for the challenge and to push the limits, so even if you don’t need what is coming next, stay with me. You will probably learn new CSS tricks that you can use elsewhere.

Transparent solution

Let’s pick up where we left off on the 3D transform and remove the background from the main element. I will start with a shadow that has both offsets and spread distance equal to 0.

The idea is to find a way to cut or hide everything inside the area of the element (inside the green border) while keeping what is outside. We are going to use clip-path for that. But you might wonder how clip-path can make a cut inside an element.

Indeed, there’s no way to do that, but we can simulate it using a particular polygon pattern:

clip-path: polygon(-100vmax -100vmax,100vmax -100vmax,100vmax 100vmax,-100vmax 100vmax,-100vmax -100vmax,0 0,0 100%,100% 100%,100% 0,0 0)

Tada! We have a gradient shadow that supports transparency. All we did is add a clip-path to the previous code. Here is a figure to illustrate the polygon part.

Showing the clip-path coordinates for the element.

The blue area is the visible part after applying the clip-path. I am only using the blue color to illustrate the concept, but in reality, we will only see the shadow inside that area. As you can see, we have four points defined with a big value (B). My big value is 100vmax, but it can be any big value you want. The idea is to ensure we have enough space for the shadow. We also have four points that are the corners of the pseudo-element.

The arrows illustrate the path that defines the polygon. We start from (-B, -B) until we reach (0,0). In total, we need 10 points. Not eight points because two points are repeated twice in the path ((-B,-B) and (0,0)).

There’s still one more thing left for us to do, and it’s to account for the spread distance and the offsets. The only reason the demo above works is because it is a particular case where the offsets and spread distance are equal to 0.

Let’s define the spread and see what happens. Remember that we use inset with a negative value to do this:

The pseudo-element is now bigger than the main element, so the clip-path cuts more than we need it to. Remember, we always need to cut the part inside the main element (the area inside the green border of the example). We need to adjust the position of the four points inside of clip-path.

.box {
  --s: 10px; /* the spread  */
  position: relative;
}
.box::before {
  inset: calc(-1 * var(--s));
  clip-path: polygon(
    -100vmax -100vmax,
     100vmax -100vmax,
     100vmax 100vmax,
    -100vmax 100vmax,
    -100vmax -100vmax,
    calc(0px  + var(--s)) calc(0px  + var(--s)),
    calc(0px  + var(--s)) calc(100% - var(--s)),
    calc(100% - var(--s)) calc(100% - var(--s)),
    calc(100% - var(--s)) calc(0px  + var(--s)),
    calc(0px  + var(--s)) calc(0px  + var(--s))
  );
}

We’ve defined a CSS variable, --s, for the spread distance and updated the polygon points. I didn’t touch the points where I am using the big value. I only update the points that define the corners of the pseudo-element. I increase all the zero values by --s and decrease the 100% values by --s.

It’s the same logic with the offsets. When we translate the pseudo-element, the shadow is out of alignment, and we need to rectify the polygon again and move the points in the opposite direction.

.box {
  --s: 10px; /* the spread */
  --x: 10px; /* X offset */
  --y: 8px;  /* Y offset */
  position: relative;
}
.box::before {
  inset: calc(-1 * var(--s));
  transform: translate3d(var(--x), var(--y), -1px);
  clip-path: polygon(
    -100vmax -100vmax,
     100vmax -100vmax,
     100vmax 100vmax,
    -100vmax 100vmax,
    -100vmax -100vmax,
    calc(0px  + var(--s) - var(--x)) calc(0px  + var(--s) - var(--y)),
    calc(0px  + var(--s) - var(--x)) calc(100% - var(--s) - var(--y)),
    calc(100% - var(--s) - var(--x)) calc(100% - var(--s) - var(--y)),
    calc(100% - var(--s) - var(--x)) calc(0px  + var(--s) - var(--y)),
    calc(0px  + var(--s) - var(--x)) calc(0px  + var(--s) - var(--y))
  );
}

There are two more variables for the offsets: --x and --y. We use them inside of transform and we also update the clip-path values. We still don’t touch the polygon points with big values, but we offset all the others — we reduce --x from the X coordinates, and --y from the Y coordinates.

Now all we have to do is to update a few variables to control the gradient shadow. And while we are at it, let’s also make the blur radius a variable as well:

Do we still need the 3D transform trick?

It all depends on the border. Don’t forget that the reference for a pseudo-element is the padding box, so if you apply a border to your main element, you will have an overlap. You either keep the 3D transform trick or update the inset value to account for the border.

Here is the previous demo with an updated inset value in place of the 3D transform:

I‘d say this is a more suitable way to go because the spread distance will be more accurate, as it starts from the border-box instead of the padding-box. But you will need to adjust the inset value according to the main element’s border. Sometimes, the border of the element is unknown and you have to use the previous solution.

With the earlier non-transparent solution, it’s possible you will face a stacking context issue. And with the transparent solution, it’s possible you face a border issue instead. Now you have options and ways to work around those issues. The 3D transform trick is my favorite solution because it fixes all the issues (The online generator will consider it as well)

Adding a border radius

If you try adding border-radius to the element when using the non-transparent solution we started with, it is a fairly trivial task. All you need to do is to inherit the same value from the main element, and you are done.

Even if you don’t have a border radius, it’s a good idea to define border-radius: inherit. That accounts for any potential border-radius you might want to add later or a border radius that comes from somewhere else.

It’s a different story when dealing with the transparent solution. Unfortunately, it means finding another solution because clip-path cannot deal with curvatures. That means we won’t be able to cut the area inside the main element.

We will introduce the mask property to the mix.

This part was very tedious, and I struggled to find a general solution that doesn’t rely on magic numbers. I ended up with a very complex solution that uses only one pseudo-element, but the code was a lump of spaghetti that covers only a few particular cases. I don’t think it is worth exploring that route.

I decided to insert an extra element for the sake of simpler code. Here’s the markup:

<div class="box">
  <sh></sh>
</div>

I am using a custom element, <sh>, to avoid any potential conflict with external CSS. I could have used a <div>, but since it’s a common element, it can easily be targeted by another CSS rule coming from somewhere else that can break our code.

The first step is to position the <sh> element and purposely create an overflow:

.box {
  --r: 50px;
  position: relative;
  border-radius: var(--r);
}
.box sh {
  position: absolute;
  inset: -150px;
  border: 150px solid #0000;
  border-radius: calc(150px + var(--r));
}

The code may look a bit strange, but we’ll get to the logic behind it as we go. Next, we create the gradient shadow using a pseudo-element of <sh>.

.box {
  --r: 50px;
  position: relative;
  border-radius: var(--r);
  transform-style: preserve-3d;
}
.box sh {
  position: absolute;
  inset: -150px;
  border: 150px solid #0000;
  border-radius: calc(150px + var(--r));
  transform: translateZ(-1px)
}
.box sh::before {
  content: "";
  position: absolute;
  inset: -5px;
  border-radius: var(--r);
  background: /* Your gradient */;
  filter: blur(10px);
  transform: translate(10px,8px);
}

As you can see, the pseudo-element uses the same code as all the previous examples. The only difference is the 3D transform defined on the <sh> element instead of the pseudo-element. For the moment, we have a gradient shadow without the transparency feature:

Note that the area of the <sh> element is defined with the black outline. Why I am doing this? Because that way, I am able to apply a mask on it to hide the part inside the green area and keep the overflowing part where we need to see the shadow.

I know it’s a bit tricky, but unlike clip-path, the mask property doesn’t account for the area outside an element to show and hide things. That’s why I was obligated to introduce the extra element — to simulate the “outside” area.

Also, note that I am using a combination of border and inset to define that area. This allows me to keep the padding-box of that extra element the same as the main element so that the pseudo-element won’t need additional calculations.

Another useful thing we get from using an extra element is that the element is fixed, and only the pseudo-element is moving (using translate). This will allow me to easily define the mask, which is the last step of this trick.

mask:
  linear-gradient(#000 0 0) content-box,
  linear-gradient(#000 0 0);
mask-composite: exclude;

It’s done! We have our gradient shadow, and it supports border-radius! You probably expected a complex mask value with oodles of gradients, but no! We only need two simple gradients and a mask-composite to complete the magic.

Let’s isolate the <sh> element to understand what is happening there:

.box sh {
  position: absolute;
  inset: -150px;
  border: 150px solid red;
  background: lightblue;
  border-radius: calc(150px + var(--r));
}

Here’s what we get:

Note how the inner radius matches the main element’s border-radius. I have defined a big border (150px) and a border-radius equal to the big border plus the main element’s radius. On the outside, I have a radius equal to 150px + R. On the inside, I have 150px + R - 150px = R.

We must hide the inner (blue) part and make sure the border (red) part is still visible. To do that, I’ve defined two mask layers —One that covers only the content-box area and another that covers the border-box area (the default value). Then I excluded one from another to reveal the border.

mask:
  linear-gradient(#000 0 0) content-box,
  linear-gradient(#000 0 0);
mask-composite: exclude;

I used the same technique to create a border that supports gradients and border-radius. Ana Tudor has also a good article about masking composite that I invite you to read.

Are there any drawbacks to this method?

Yes, this definitely not perfect. The first issue you may face is related to using a border on the main element. This may create a small misalignment in the radii if you don’t account for it. We have this issue in our example, but perhaps you can hardly notice it.

The fix is relatively easy: Add the border’s width for the <sh> element’s inset.

.box {
  --r: 50px;
  border-radius: var(--r);
  border: 2px solid;
}
.box sh {
  position: absolute;
  inset: -152px; /* 150px + 2px */
  border: 150px solid #0000;
  border-radius: calc(150px + var(--r));
}

Another drawback is the big value we’re using for the border (150px in the example). This value should be big enough to contain the shadow but not too big to avoid overflow and scrollbar issues. Luckily, the online generator will calculate the optimal value considering all the parameters.

The last drawback I am aware of is when you’re working with a complex border-radius. For example, if you want a different radius applied to each corner, you must define a variable for each side. It’s not really a drawback, I suppose, but it can make your code a bit tougher to maintain.

.box {
  --r-top: 10px;
  --r-right: 40px;
  --r-bottom: 30px;
  --r-left: 20px;
  border-radius: var(--r-top) var(--r-right) var(--r-bottom) var(--r-left);
}
.box sh {
  border-radius: calc(150px + var(--r-top)) calc(150px + var(--r-right)) calc(150px + var(--r-bottom)) calc(150px + var(--r-left));
}
.box sh:before {
  border-radius: var(--r-top) var(--r-right) var(--r-bottom) var(--r-left);
}

The online generator only considers a uniform radius for the sake of simplicity, but you now know how to modify the code if you want to consider a complex radius configuration.

Wrapping up

We’ve reached the end! The magic behind gradient shadows is no longer a mystery. I tried to cover all the possibilities and any possible issues you might face. If I missed something or you discover any issue, please feel free to report it in the comment section, and I’ll check it out.

Again, a lot of this is likely overkill considering that the de facto solution will cover most of your use cases. Nevertheless, it’s good to know the “why” and “how” behind the trick, and how to overcome its limitations. Plus, we got good exercise playing with CSS clipping and masking.

And, of course, you have the online generator you can reach for anytime you want to avoid the hassle.


Different Ways to Get CSS Gradient Shadows originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/different-ways-to-get-css-gradient-shadows/feed/ 0 376764
How to Make a Folder “Slit” Effect With CSS https://css-tricks.com/how-to-make-a-folder-slit-effect-with-css/ https://css-tricks.com/how-to-make-a-folder-slit-effect-with-css/#comments Wed, 19 Oct 2022 12:56:37 +0000 https://css-tricks.com/?p=374376 When you put something — say a regular sheet of paper — in a manilla folder, a part of that thing might peek out of the folder a little bit. The same sort of thing with a wallet and credit …


How to Make a Folder “Slit” Effect With CSS originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
When you put something — say a regular sheet of paper — in a manilla folder, a part of that thing might peek out of the folder a little bit. The same sort of thing with a wallet and credit cards. The cards poke out just a smidge so you can get a quick glance of which cards you’re carrying.

Credit: Stephen Phillips on Unsplash

I call this sort of thing a “slit”. A slit is where we create the illusion of an opening through which we can tease a visual element peeking out of it. And we can do that in CSS!

The crucial part of the design is the shadow, which is what gives the visual cue of there being a slit. Then there’s the cover for the slit which provides the space for the exhibited element to peek through from under.

Here’s what we’re going to make together:

Let’s begin with creating the shadow

You might be surprised that the shadow in the example is not created with an actual CSS shadow, like box-shadow or a drop-shadow() filter. Instead, the shadow is a separate element in itself, dark and blurred out. This is important in order to make the design more adaptable, both in its default and animated states.

The cover is the other element in the design. The cover is what I call the element that overlaps the shadow. Here’s a figure depicting how the shadow and cover come together.

The shadow is made from a small upright rectangle that has a gradient background. The gradient is darker in the middle. So when the element is blurred, it creates a shadow that’s darker in the middle; hence more dimensional.

Now, the left half of the recreated shadow is covered with a rectangle on top, colored exactly the same as the background of its container.

Both the cover and the shadow are then moved to the left ever so slightly so it appears to be layered

Working on the cover

For the cover to blend with the design’s background, its background color is inherited from its containing element. Alternatively, you can also try to blend the cover to its background using standards like CSS masks and blend modes, depending on your design choices and requirements.

To learn some basics on how these standards might be applied, you can refer to these articles: Sarah Drasner’s “Masking vs. Clipping: When to Use Each” provides an excellent primer on masks. I’ve also written about CSS blend modes in this article where you can brush up on the topic.

In the source code of my example, you’ll notice that I aligned and stacked the elements inside the <main> element using CSS Grid, which is a layout standard I often use in my demos. If you’re recreating a similar design, use a layout method that fits the best for your application to align the different parts of the design. In this case, I’ve set up a single-column grid on the <main> element which allows me to center align the cover, shadow, and image.

What CSS Grid also allows me to do is set all three of those divs so they are all full-width in the <main> container:

main > div {
  grid-area: 1 / 1;
}

This gets everything to stack on top of one another. Normally, we work hard to avoid covering elements with other elements in a grid. But this example relies on it. I’ve given the .slit-cover at width of 50% which naturally reveals the image underneath it. From there, I set a transform on it that moves it 50% in the negative direction, plus the small amount I shifted the shadow earlier (25px) to make sure that is revealed as well.

.slit-cover {
  width: 50%;
  transform: translatex(calc(-50% - 25px));
  /* etc. */
}

And there we have it! A pretty natural-looking slit that mimics something peeking out of a folder, wallet, or whatever.

There are more ways to do this! For one, Flexbox can get elements to line up in a row and align in the center like this. There are lots of ways to get things side-by-side. And maybe you have a way to use the box-shadow property, drop-shadow() filter, or even SVG filters to get the same sort of shadow effect that really sells the illusion.

And you can totally riff on this to get your own look and feel. For example, try swapping the position of the shadow and image. Or play with the color combinations and change the blur() filter value. The shape of the cover and the shadow can also be tweaked — I bet you can create a curved shadow instead of a straight one and share it with us in the comments!


How to Make a Folder “Slit” Effect With CSS originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/how-to-make-a-folder-slit-effect-with-css/feed/ 2 374376
A Serene CSS Dappled Light Effect https://css-tricks.com/css-dappled-light-effect/ https://css-tricks.com/css-dappled-light-effect/#comments Wed, 19 Jan 2022 22:46:39 +0000 https://css-tricks.com/?p=360899 There’s a serene warmth to the early evening sunlight peaking through rustling leaves. Artists use dappled light to create a soft, hypnotic effect.

Bedford Dwellings by Ron Donoughe (2013)

We can create the same sort of dappled light effect in …


A Serene CSS Dappled Light Effect originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
There’s a serene warmth to the early evening sunlight peaking through rustling leaves. Artists use dappled light to create a soft, hypnotic effect.

An oil painting of a tall rectangular orange building with six windows, two by two, and a faint full-width fence in front of it. There is a similar building off in the distance. A tall birch tree is in the foreground with light green and yellow leaves, casting the dappled light effect that is being covered in this article. The shadows cover the green grass between the tree and building, and they extend to the building.
Bedford Dwellings by Ron Donoughe (2013)

We can create the same sort of dappled light effect in web design, using it on photos and illustrations to add that magic touch to what might otherwise be drab walls of content to bring them back to life.

I’ll give you one easy, quick way to add this effect… with just CSS.

Before we get into the code, it’s important to know the composition of dappled light. It’s made up of large spots — circular or elliptical — of light that are intercepted by the shadows cast by the foliage. Basically the light that slips past leaves, branches and so forth. Sometimes the shadows create crisp edges, but are more often blurred since we’re talking about light that passes though many, less defined spaces that diffuse and distort the light as it casts shadows from a further distance than, say, your own stark shadow on a nearby wall from direct sunlight.

Here’s the difference in the appearance of a white wall with and without lit by dappled light:

A side-by-side comparison of the same white brick surface, the left showing the CSS dappled light effect compared to no shadows.
The effect creates splashes of light and shadow.

I’m going to recreate the dappled light effect with both plain text and fun emojis, applying CSS shadows and blends to mimic nature. I’ll cover alternative methods too.

Setting the scene

We’ll use text — letters from the alphabet, special characters, emojis, etc. — to create the shapes of light. And by light, I mean pale, translucent colors. Again, we’re for a dappled light effect rather than something that’s sharp, crisp, or stark.

It’s best to choose characters that are elliptical or oblong in some way — the spots produced by dappled light comes in a variety of shapes. You’ll have to go with your best judgement here to get exactly what you’re going for. Me? I’m using 🍃, 🍂, \ because they are elliptical, oblong, and slanted — a bit of chaos and unpredictability for an otherwise serene effect.

I’m wrapping those in paragraphs that are contained in a .backdrop parent element:

<div class="backdrop">
  <p class="shapes">🍃</p>
  <p class="shapes">🍂</p>
  <p class="shapes">\</p>
</div>

I’m using the parent element as the surface where the dappled light and shadows are cast, applying a background image for its texture. And not only am I giving the surface an explicit width and height, but also setting hidden overflow on it so I’m able to cast shadows that go beyond the surface without revealing them. The objects that cast the dappled light effect are aligned in the middle of the backdrop’s surface, thanks to CSS grid:

.backdrop {
  background: center / cover no-repeat url('image.jpeg');
  width: 400px; height: 240px;
  overflow: hidden;
  display: grid;
}
.backdrop > * {
  grid-area: 1/1;
}

I find that it’s OK if the shapes aren’t aligned exactly on top of one another as long as they overlap in a way that gets the dappled light effect you want. So no pressure to do exactly what I’m doing here to position things in CSS. In fact, I encourage you to try playing with the values to get different patterns of dappled light!

Styling the dappled light in CSS

These are the key properties the emojis should have — transparent color, black semi-transparent background (using the alpha channel in rgba()), blurry white text-shadow with a nice large font-size, and finally, a mix-blend-mode to smooth things out.

.shapes {
  color:  transparent;
  background-color: rgba(0, 0, 0, 0.3); // Use alpha transparency
  text-shadow: 0 0 40px #fff; // Blurry white shadow
  font: bolder 320pt/320pt monospace;
  mix-blend-mode: multiply;
}

mix-blend-mode sets how an element’s colors blend with that of its container element’s content. The multiply value causes the backdrop of an element to show through the element’s light colors and keeps dark colors the same, making for a nicer and more natural dappled light effect.

Refining colors and contrast

I wanted the background-image on the backdrop to be a bit brighter, so I also added filter: brightness(1.6). Another way to do this is with background-blend-mode instead, where all the different backgrounds of an element are blended and, instead of adding the emojis as separate elements, we add them as background images.

Notice that I used a different emoji in that last example as well as floralwhite for some color that’s less intense than pure white for the light. Here’s one of the emoji background images unwrapped:

<svg xmlns='http://www.w3.org/2000/svg'> 
  <foreignObject width='400px' height='240px'> 
    <div xmlns='http://www.w3.org/1999/xhtml' style=
      'font: bolder 720pt/220pt monospace;
       color: transparent;
       text-shadow: 0 0 40px floralwhite;
       background: rgba(0, 0, 0, 0.3);'
    >
      🌾
    </div> 
  </foreignObject> 
</svg>

If you want to use your own images for the shapes, ensure the borders are blurred to create a soft light. The CSS blur() filter can be handy for the same sort of thing. I also used CSS @supports to adjust the shadow blur value for certain browsers as a fallback.

Now let’s circle back to the first example and add a few things:

<div class="backdrop">
  <p class="shapes">🍃</p>
  <p class="shapes">🍂</p>
  <p class="shapes">\</p>
</div>

<p class="content">
  <img width="70px" style="float: left; margin-right: 10px;" src="image.jpeg" alt="">
  Top ten tourists spots for the summer vacation <br><br><i style="font-weight: normal;">Here are the most popular places...</i>
</p>

.backdrop and .shapes are basically the same styles as before. As for the .content, which also sits on top of the .backdrop, I added isolation: isolate to form a new stacking context, excluding the element from the blending as a refining touch.

Animating the light source

I also decided to add a simple CSS animation with @keyframes that get applied to the .backdrop on :hover:

.backdrop:hover > .shapes:nth-of-type(1){
  animation: 2s ease-in-out infinite alternate move;
}
.backdrop:hover > .shapes:nth-of-type(2):hover{
  animation: 4s ease-in-out infinite alternate move-1;
}

@keyframes move {
  from {
    text-indent: -20px;
  }
  to {
    text-indent: 20px;
  }
}
@keyframes move-1 {
  from {
    text-indent: -60px;
  }
  to {
    text-indent: 40px;
  }
}

Animating the text-indent property on the emojis products a super subtle bit of movement — the kind you might expect from clouds moving overhead that change the direction of the light. Just a touch of class, you know.

Wrapping up

There we have it! We drew some inspiration from nature and art to mimic one of those partly cloudy days where the sun shines through trees and bushes, projecting dappled light and shadow spots against a surface. And we did all of it with a small handful of CSS and a few emoji.

The key was how we applied color on the emoji. Using an extra blurry text-shadow in a light color sets the light, and a semi-transparent background-color defines the shadow spots. From there, all we had to do was ensure the backdrop for the light and shadows used a realistic texture with enough contrast to see the dappled light effect in action.


A Serene CSS Dappled Light Effect originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/css-dappled-light-effect/feed/ 8 360899
Scroll Shadows With JavaScript https://css-tricks.com/scroll-shadows-with-javascript/ https://css-tricks.com/scroll-shadows-with-javascript/#comments Tue, 05 Oct 2021 22:40:21 +0000 https://css-tricks.com/?p=352754 Scroll shadows are when you can see a little inset shadow on elements if (and only if) you can scroll in that direction. It’s just good UX. You can actually pull it off in CSS, which I think is …


Scroll Shadows With JavaScript originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Scroll shadows are when you can see a little inset shadow on elements if (and only if) you can scroll in that direction. It’s just good UX. You can actually pull it off in CSS, which I think is amazing and one of the great CSS tricks. Except… it just doesn’t work on iOS Safari. It used to work, and then it broke in iOS 13, along with some other useful CSS things, with no explanation why and has never been fixed but is now fixed, at least as of July 2022 (thanks Ronald!).

Here’s a pure CSS example so you can see it work in all browsers. Screenshots:

I’m bringing this up now because I see Jonnie Hallman is blogging about it again. He mentioned it as an awesome little detail back in May. There are certain interfaces where scroll shadows really extra make sense.

Taking a step back, I thought about the solution that currently worked, using scroll events. If the scroll area has scrolled, show the top and left shadows. If the scroll area isn’t all the way scrolled, show the bottom and right shadows. With this in mind, I tried the simplest, most straight-forward, and least clever approach by putting empty divs at the top, right, bottom, and left of the scroll areas. I called these “edges”, and I observed them using the Intersection Observer API. If any of the edges were not intersecting with the scroll area, I could assume that the edge in question had been scrolled, and I could show the shadow for that edge. Then, once the edge is intersecting, I could assume that the scroll area has reached the edge of the scroll, so I could hide that shadow.

Clever clever. No live demo, unfortunately, but read the post for a few extra details on the implementation.

Other JavaScript-powered examples

I do think if you’re going to do this you should go the IntersectionObserver route though. Would love to see someone port the best of these ideas all together (wink wink).


Scroll Shadows With JavaScript originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/scroll-shadows-with-javascript/feed/ 5 352754
Designing Beautiful Shadows in CSS https://css-tricks.com/designing-beautiful-shadows-in-css/ https://css-tricks.com/designing-beautiful-shadows-in-css/#comments Fri, 17 Sep 2021 22:49:39 +0000 https://css-tricks.com/?p=352082 My favorite kind of blog post is when someone takes a subject that I’ve spent all of five minutes considering and then says—no!—this is an enormous topic worthy of a dissertation. Look at all the things you can do with …


Designing Beautiful Shadows in CSS originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
My favorite kind of blog post is when someone takes a subject that I’ve spent all of five minutes considering and then says—no!—this is an enormous topic worthy of a dissertation. Look at all the things you can do with this tiny CSS property!

I was reminded of this when I spotted this post by Josh Comeau about designing beautiful shadows in CSS:

In my humble opinion, the best websites and web applications have a tangible “real” quality to them. There are lots of factors involved to achieve this quality, but shadows are a critical ingredient.

When I look around the web, though, it’s clear that most shadows aren’t as rich as they could be. The web is covered in fuzzy grey boxes that don’t really look much like shadows.

Josh shows the regular old boring shadow approaches and then explores all the ways to improve and optimize them into shadows with real depth. It all comes down to taking a closer look color and exploring the box-shadow CSS property. And speaking of depth, Rob O’Leary’s “Getting Deep Into Shadows” is another comprehensive look at shadows.

I had also completely forgotten about filter: drop-shadow; which is particularly useful on adding shadows to images that you want to throw onto a page. Great stuff all round.

To Shared LinkPermalink on CSS-Tricks


Designing Beautiful Shadows in CSS originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/designing-beautiful-shadows-in-css/feed/ 1 352082
Adding Shadows to SVG Icons With CSS and SVG Filters https://css-tricks.com/adding-shadows-to-svg-icons-with-css-and-svg-filters/ https://css-tricks.com/adding-shadows-to-svg-icons-with-css-and-svg-filters/#comments Fri, 11 Jun 2021 14:30:29 +0000 https://css-tricks.com/?p=342164 Why would we need to apply shadows to SVG?

  1. Shadows are a common design feature that can help elements, like icons, stand out. They could be persistent, or applied in different states (e.g. :hover, :focus, or :active)


Adding Shadows to SVG Icons With CSS and SVG Filters originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Why would we need to apply shadows to SVG?

  1. Shadows are a common design feature that can help elements, like icons, stand out. They could be persistent, or applied in different states (e.g. :hover, :focus, or :active) to indicate interaction to users.
  2. Shadows happen in real life, so they can be used on screens to breathe some life into your elements and add a touch of realism to a design.

Since we’re making lists, there are two primary ways we can apply shadows to an SVG:

  1. Using the CSS filter() property
  2. Using an SVG <filter>

Yes, both involve filters! And, yes, both CSS and SVG have their own types of filters. But there is some crossover between these as well. For example, a CSS filter can refer to an SVG <filter>; that is, if we’re working with an inline SVG instead of, say, an SVG used as a background image in CSS.

What you can’t use: the CSS box-shadow property. This is commonly used for shadows, but it follows the rectangular outside edge of elements, not the edges of the SVG elements like we want. Here’s Michelle Barker with a clear explanation:

Two flat kitten faces in bright pink showing ears eyes and whiskers. The first kitten has a drop shadow around its box and the second kitten has a drop shadow around its path edges.

If you’re using an SVG icon font, though, there is always text-shadow. That will indeed work. But let’s focus on those first two as they’re in line with a majority of use cases.

Shadows with CSS filters

The trick to applying a shadow directly to SVG via CSS filters is the drop-shadow() function :

svg {
  filter: drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));
}

That will apply a shadow that starts at 3px horizontally, 5px down, with 2px of blur, and is 40% black. Here are some examples of that:

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

ChromeFirefoxIEEdgeSafari
18*35No796*

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
1231244.4*6.0-6.1*

Call an SVG filter inside a CSS filter

Say we have an SVG filter in the HTML:

<svg height="0" width="0">
  
  <filter id='shadow' color-interpolation-filters="sRGB">
    <feDropShadow dx="2" dy="2" stdDeviation="3" flood-opacity="0.5"/>
  </filter>
  
</svg>

We can use a CSS filter to call that SVG filter by ID instead of values we saw earlier:

svg {
  filter: url(#shadow);
}

Now that filter is taken from the HTML and referenced in the CSS, which applies it.

Using SVG filter primitives

You might be wondering how we got that SVG <filter> to work. To make a drop shadow with an SVG filter, we make use of a filter primitive. A filter primitive in SVG is an element that takes some sort of image or graphic as an input, then outputs that image or graphic it when it’s called. They sort of work like filters in a graphic editing application, but in code and can only be used inside an SVG <filter> element.

There are lots of different filter primitives in SVG. The one we’re reaching for is <feDropShadow>. I’ll let you guess what to does just by looking at the name.

So, similar to how we had something like this did this with a CSS filter:

svg {
  filter: drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));
}

…we can accomplish the same with the <feDropShadow> SVG filter primitive. There are three key attributes worth calling out as they help define the appearance of the drop shadow:

  • dx — This shifts the position of the shadow along the x-axis.
  • dy — This shifts the position of the shadow along the y-axis.
  • stdDeviation — This defines the standard deviation for the drop shadow’s blur operation. There are other attributes we can use, such as the flood-color for setting the drop shadow color, and flood-opacity for setting the drop shadow’s opacity.

That example includes three <filter> elements, each with their own <feDropShadow> filter primitives.

Using SVG filters

SVG filters are very powerful. We just looked at <feDropShadow>, which is very useful of course, but there is so much more they can do (including Photoshop-like effects) and the subset of stuff we get just for shadows is extensive. Let’s look at some, like colored shadows and inset shadows.

Let’s take the SVG markup for the Twitter logo as an example :

<svg class="svg-icon" viewBox="0 0 20 20">
  <path fill="#4691f6" d="M18.258,3.266c-0.693,0.405-1.46,0.698-2.277,0.857c-0.653-0.686-1.586-1.115-2.618-1.115c-1.98,0-3.586,1.581-3.586,3.53c0,0.276,0.031,0.545,0.092,0.805C6.888,7.195,4.245,5.79,2.476,3.654C2.167,4.176,1.99,4.781,1.99,5.429c0,1.224,0.633,2.305,1.596,2.938C2.999,8.349,2.445,8.19,1.961,7.925C1.96,7.94,1.96,7.954,1.96,7.97c0,1.71,1.237,3.138,2.877,3.462c-0.301,0.08-0.617,0.123-0.945,0.123c-0.23,0-0.456-0.021-0.674-0.062c0.456,1.402,1.781,2.422,3.35,2.451c-1.228,0.947-2.773,1.512-4.454,1.512c-0.291,0-0.575-0.016-0.855-0.049c1.588,1,3.473,1.586,5.498,1.586c6.598,0,10.205-5.379,10.205-10.045c0-0.153-0.003-0.305-0.01-0.456c0.7-0.499,1.308-1.12,1.789-1.827c-0.644,0.28-1.334,0.469-2.06,0.555C17.422,4.782,17.99,4.091,18.258,3.266" ></path>
</svg>

We’re going to need a <filter> element to do these effects. This needs to be within an <svg> element in the HTML. A <filter> element is never rendered directly in the browser — it is only used as something that can be referenced via the filter attribute in SVG, or the url() function in CSS.

Here is the syntax showing an SVG filter and applying it to a source image :

<svg width="300" height="300" viewBox="0 0 300 300">

  <filter id="myfilters">
    <!-- All filter effects/primitives go in here -->
  </filter>

  <g filter="url(#myfilters)">
    <!-- Filter applies to everything in this group -->
    <path fill="..." d="..." ></path>
  </g>

</svg>

The filter element is meant to hold filter primitives as children. It is a container to a series of filter operations that are combined to create a filter effects.

These filter primitive perform a single fundamental graphical operation (e.g. blurring, moving, filling, combining, or distorting) on one or more inputs. They are like building blocks where each SVG filter can be used to in conjunction with others to create an effect. <feGaussianBlur> is a popular filter primitive used to add a blur effect.

Let’s say we define the following SVG filter with <feGaussianBlur>:

<svg version="1.1" width="0" height="0">
  <filter id="gaussian-blur">
    <feGaussianBlur stdDeviation="1 0" />
  </filter>
</svg>

When applied on an element, this filter creates a Gaussian blur that blurs the element on a 1px radius on the x-axis, but no blurring on the y-axis. Here’s the result, with and without the effect:

It is possible to use multiple primitives inside a single filter. This will create interesting effects, however, you need to make the different primitives aware of each other. Bence Szabó has a crazy cool set of patterns he created this way.

When combining multiple filter primitives, the first primitive uses the original graphic (SourceGraphic) as its graphic input. Any subsequent primitive uses the result of the filter effect before it as its input. And so on. But we can get some flexibility on that with using the in, in2 and result attributes on primitive elements. Steven Bradley has an excellent write-up on filter primitives that dates back to 2016, but still hold true today.

There are 17 primitives we can use today:

  • <feGaussianBlur>
  • <feDropShadow>
  • <feMorphology>
  • <feDisplacementMap>
  • <feBlend>
  • <feColorMatrix>
  • <feConvolveMatrix>
  • <feComponentTransfer>
  • <feSpecularLighting>
  • <feDiffuseLighting>
  • <feFlood>
  • <feTurbulence>
  • <feImage>
  • <feTile>
  • <feOffset>
  • <feComposite>
  • <feMerge>

Notice the fe prefix on all of them. That stands for filter effect. Understanding SVG filters is challenging. An effect like an inset shadow requires a verbose syntax that is difficult to grasp without a thorough understanding of math and color theory. (Rob O’Leary’s “Getting Deep Into Shadows” is a good place to start.)

Rather than running down the rabbit hole of all that, we’re going to work with some pre-made filters. Fortunately, there are a lot of ready-to-use SVG filters around.

Inset shadows

To use filter effect on the Twitter logo, we need to declare it in our “SVG source document” with a unique ID for referencing in our <filter> tag.

<filter id='inset-shadow'>
  <!-- Shadow offset -->
  <feOffset
    dx='0'
    dy='0'
  />

  <!-- Shadow blur -->
  <feGaussianBlur
    stdDeviation='1'
    result='offset-blur'
  />

  <!-- Invert drop shadow to make an inset shadow -->
  <feComposite
    operator='out'
    in='SourceGraphic'
    in2='offset-blur'
    result='inverse'
  />
  
  <!-- Cut color inside shadow -->
  <feFlood
    flood-color='black'
    flood-opacity='.95'
    result='color'
  />
  <feComposite
    operator='in'
    in='color'
    in2='inverse'
    result='shadow'
  />

  <!-- Placing shadow over element -->
  <feComposite
    operator='over'
    in='shadow'
    in2='SourceGraphic'
  />
</filter>

There are four different primitives in there and each one performs a different function. But, taken together, they achieving an inset shadow.

Now that we’ve created this inset shadow filter, we can apply it to our SVG. We’ve already seen how to apply it via CSS. Something like:

.filtered {
  filter: url(#myfilters);
}

/* Or apply only in certain states, like: */
svg:hover, svg:focus {
  filter: url(#myfilters);
} 

We can also apply an SVG <filter> directly within the SVG syntax with the filter attribute. That’s like:

<svg>

  <!-- Apply a single filter -->
  <path d="..." filter="url(#myfilters)" />

  <!-- Or apply to a whole group of elements -->
  <g filter="url(#myfilters)">
    <path d="..." />
    <path d="..." />
  </g>
</svg>

More examples

Here are some more shadow examples from Oleg Solomka:

Note that the basic shadows here are probably a bit more complicated than they need to be. For example, a colored shadow can still be done with <feDropShadow> like:

<feDropShadow dx="-0.8" dy="-0.8" stdDeviation="0"
  flood-color="pink" flood-opacity="0.5"/>

But that embossed effect is pretty great as a filter!

Also note that you might see SVG filters in SVG syntax like this:

<svg height="0" width="0" style="position: absolute; margin-left: -100%;">
  <defs>
    <filter id="my-filters">
      <!-- ... -->
    </filter>

    <symbol id="my-icon">
      <!-- ... -->
    </symbol>
  </defs>
</svg>

On the first line there, that’s saying: this SVG shouldn’t render at all — it’s just stuff that we intend to use later. The <defs> tag says something similar: we’re just defining these things to use later. That way, we don’t have to repeat ourselves by writing things out over and again. We’ll reference the filter by ID, and the symbols as well, perhaps like:

<svg>
  <use xlink:href="#my-icon" />
</svg>

SVG filters have wide support (even in Internet Explorer and Edge!) with very fast performance.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

ChromeFirefoxIEEdgeSafari
8310126

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
1231244.46.0-6.1

Wrapping things up

A final comparison:

  • CSS filters are easier to use, but are much more limited. I don’t think it’s possible to add an inset shadow with the drop-shadow() function, for example.
  • SVG filters are much more robust, but much more complicated as well, and require having the <filter> somewhere in the HTML.
  • They both have great browser support and perform well on all modern browsers, though SVG filters have (surprisingly) the deepest browser support.

In this article, we have seen why and how to apply shadow to SVG icons with examples on each. Have you done this, but did it a different way than anything we looked at? Have you tried to do a shadow effect that you found impossible to pull off? Please share!


Adding Shadows to SVG Icons With CSS and SVG Filters originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/adding-shadows-to-svg-icons-with-css-and-svg-filters/feed/ 11 342164
Creating Colorful, Smart Shadows https://css-tricks.com/creating-colorful-smart-shadows/ https://css-tricks.com/creating-colorful-smart-shadows/#comments Tue, 04 May 2021 14:19:48 +0000 https://css-tricks.com/?p=339742 A bona fide CSS trick from Kirupa Chinnathambi here. To match a colored shadow with the colors in the background-image of an element, you inherit the background in a pseudo-element, kick it behind the original, then blur and filter it. …


Creating Colorful, Smart Shadows originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
A bona fide CSS trick from Kirupa Chinnathambi here. To match a colored shadow with the colors in the background-image of an element, you inherit the background in a pseudo-element, kick it behind the original, then blur and filter it.

.colorfulShadow {
  position: relative;
}

.colorfulShadow::after {
  content: "";
  width: 100%;
  height: 100%;
  position: absolute;
  background: inherit;
  background-position: center center;
  filter: drop-shadow(0px 0px 10px rgba(0, 0, 0, 0.50)) blur(20px);
  z-index: -1;
}

Negative z-index is always a yellow flag for me as that only works if there are no intermediary backgrounds. But the trick holds. There would always be some other way to layer the backgrounds (like a <span> or whatever).

For some reason this made me think of a demo I saw (I can’t remember who to credit!). Emojis had text-shadow on them, which really made them pop. And those shadows could also be colorized to a similar effect.

To Shared LinkPermalink on CSS-Tricks


Creating Colorful, Smart Shadows originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/creating-colorful-smart-shadows/feed/ 1 339742
Getting Deep Into Shadows https://css-tricks.com/getting-deep-into-shadows/ https://css-tricks.com/getting-deep-into-shadows/#comments Mon, 22 Feb 2021 16:00:16 +0000 https://css-tricks.com/?p=334324 Let’s talk shadows in web design. Shadows add texture, perspective, and emphasize the dimensions of objects. In web design, using light and shadow can add physical realism and can be used to make rich, tactile interfaces.

Take the landing page …


Getting Deep Into Shadows originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Let’s talk shadows in web design. Shadows add texture, perspective, and emphasize the dimensions of objects. In web design, using light and shadow can add physical realism and can be used to make rich, tactile interfaces.

Take the landing page below. It is for cycling tours in Iceland. Notice the embellished drop shadow of the cyclist and how it creates the perception that they are flying above not only the content on the page, but the page itself, as though they are “popping” over the screen. It feels dynamic and immediate, which is perfect for the theme of adventure.

A view of a BMX biker from above leaping over a vast area of light brown land with the words free ride along the bottom edge in large, bold, and white block lettering.
Credit: Kate Hahu

Compare that with this next example. It’s a “flat” design, sans shadows. In this case, the bike itself is the focal point. The absence of depth and realism allows the bike to stand out on its own.

Screenshot of a webpage with a light pink background with a white box that contains the site content with a headline that reads "Ride as Much or as Little" in red, an email subscription form, and a large image of a red and white bicycle to the right.
Credit: saravana

You can appreciate the differences between these approaches. Using shadows and depth is a design choice; they should support the theme and the message you want the content to convey.

Light and shadows

As we just saw, depth can enhance content. And what exactly makes a shadow? Light!

It’s impossible to talk about shadow without getting into light. It controls the direction of a shadow as well as how deep or shallow the shadow appears. You can’t have one without the other.

Google’s Material Design design system is a good example of employing light and shadows effectively. You’ve certainly encountered Material Design’s aesthetics because Google employs it on nearly all of its products.

A masonry grid of photos on a mobile screen. The purple header that contains the page title and hamburger menu has a shadow along its bottom edge that separates it from the white background of the photos.
White modal with a list of checkboxes. The modal has an underlying black shadow against the background.
A two-by-three grid of cards on a mobile screen. Each card has a light shallow shadow against a white background.

The design system takes cues from the physical world and expresses interfaces in three-dimensional space using light, surfaces, and cast shadows. Their guidelines on using light and shadows covers this in great detail.

In the Material Design environment, virtual lights illuminate the UI. Key lights create sharper, directional shadows, called key shadows. Ambient light appears from all angles to create diffused, soft shadows, called ambient shadows.

Shadows are a core component of Material Design. Compare that with Apple’s Human Interface Guidelines for macOS, where translucency and blurring is more of a driving factor for evoking depth.

Screenshot of Apple's Reminders app on a desktop. The left column that contains search and navigation is opaque and blends lightly into the desktop background while the solid white right column contains a checkbox list of reminders.
Screenshot of Apple's Maps app. The left column contains the map addresses and different route options with an opaque background that lightly blends in with the desktop background. The right column contains the map and does not blend in with the background.

In this case, light is still an influential factor, as it allows elements to either blend into the desktop, or even into other panels in the UI. Again, it’s is a design choice to employ this in your interface. Either way, you can see how light influences the visual perception of depth.

Light sources and color

Now that we understand the relationship between light and shadows, we ought to dig in a little deeper to see how light affects shadows. We’ve already seen how the strength of light produces shadows at different depths. But there’s a lot to say about the way light affects the direction and color of shadows.

There are two kinds of shadows that occur when a light shines on an object, a drop shadow and a form shadow.

Photo of an orange with light shining on it from the top right. That area is brighter than the left side which is covered in shadow. The ground contains a light reflection of the orange.

Drop shadows

A drop shadow is cast when an object blocks a light source. A drop shadow can vary in tone and value. Color terminology can be dense and confusing, so let’s talk about tone and value for a moment.

Tone is a hue blended with grey. Value describes the overall lightness or darkness of a color. Value is a big deal in painting as it is how the artist translates light and object relationships to color.

Illustration showing the effects of Hue, Tint, Tone, and Shade on red rectangles. Each rectangle is a slightly different shade of red where tint adds white, tone adds gray and shade adds black.

In the web design world, these facets of color are intrinsic to the color picker UI.

Form shadows

A form shadow, on the other hand, is the side of an object facing away from the light source. A form shadow has softer, less defined edges than a drop shadow. Form shadows illustrate the volume and depth of an object.

The appearance of a shadow depends on the direction of light, the intensity of light, and the distance between the object and the surface where the shadow is cast. The stronger the light, the darker and sharper the shadow is. The softer the light, the fainter and softer the shadow is. In some cases, we get two distinct shadows for directional light. The umbra is where light is obstructed and penumbra is where light is cast off.

Two vertically stacked illustrations.The top is a green circle with a yellow light source coming at it from the left and both umbra and penumbra shadows are cast to the right. The bottom illustration is the same green circle and light source, but with a solid black shadow cast to the right.

If a surface is close to an object, the shadow will be sharper. If a surface is further away, the shadow will be fainter. This is not some abstract scientific stuff. This is stuff we encounter every day, whether you realize it or not.

This stuff comes up in just about everything we do, even when writing with a pencil.

Light may also be reflected from sides of an object or another surface. Bright surfaces reflect light, dark surfaces absorb light.

These are the most valuable facets of light to understand for web design. The physics behind light is a complex topic, I have just lightly touched on some of it here. If you’d like to see explicit examples of what shadows are cast based on different light sources, this guide to drawing shadows for comics is instructive.

Positioning light sources

Remember, shadows go hand-in-hand with light, so defining a light source — even though there technically isn’t one — is the way to create impressive shadow effects. The trick is to consistently add shadows relative to the light source. A light source positioned above an element will cast a shadow below the element. Placing a light source to the left of an element will cast a shadow to the right. Placing multiple light sources to the top, bottom, left and right of an element actually casts no shadow at all!

Showing two browser mockups side by side. The left has light shining on it from all four directions showing uniform light and no shadows. The right has a single light source from the top casting a shadow along the bottom edge.

A light source can be projected in any direction you choose. Just make sure it’s used consistently in your design, so the shadow on one element matches other shadows on the page.

Elevation

Shadows can also convey elevation. Once again, Material Design is a good example because it demonstrates how shadows are used to create perceived separation between elements.

Showing a mobile screen flat on a light blue background with header, box, and navigational elements elevated over the screen showing depth.
Credit: Nate Wilson

Inner shadows

Speaking of elevation, the box-shadow property is the only property that can create inner shadows for a sunken effect. So, instead of elevating up, the element appears to be pressed in. That’s thanks to the inset keyword.

That good for something like an effect where clicking a button appears to physically press it.

It’s also possible to “fake” an inner text shadow with a little trickery that’s mostly supported across browsers:

Layering shadows

We’re not limited to a single shadow per element! For example, we can provide a comma-separated list of shadows on the box-shadow property. Why would we want to do that? Smoother shadows, for one.

Interesting effects is another.

Layering shadows can even enhance typography using the text-shadow property.

Just know that layering shadows is a little different for filter: drop-shadow() It’s syntax also takes a list, but it’s space-separated instead of comma-separated.

.box {
  box-shadow: 
    0 2px 2px #555, /* commas */
    0 6px 5px #777,
    0 12px 10px #999
  ;
}

.box {
  filter:
    drop-shadow(0 2px 2px #555) /* spaces */
    drop-shadow(0 6px 5px #777)
    drop-shadow(0 12px 10px #999);
}

Another thing? Shadows stack on top of one another, in the order they are declared where the top shadow is the first one in the list.

Two vertically stacked examples showing a white circle with a yellow and a grey circle behind it and the CSS code snippets that create the effect. On the top, the gray shadow is above the yellow shadow. On the bottom, the yellow shadow is above the gray shadow.

You may have guessed that drop-shadow() works a little differently here. Shadows are added exponentially, i.e. 2^number of shadows - 1.

Here’s how that works:

  • 1 shadow = (2^1 – 1). One shadow is rendered.
  • 2 shadows = (2^2 – 1). Three shadows are rendered.
  • 3 shadows = (2^3 – 1). Seven shadows are rendered.

Or, in code:

.one-shadow {
  filter: drop-shadow(20px 20px 0 grey);
}

.three-shadows {
  filter: 
    drop-shadow(20px 20px 0 grey)
    drop-shadow(40px 0 0 yellow);
}

.seven-shadows {
  filter: 
    drop-shadow(20px 20px 0 grey)
    drop-shadow(40px 0 0 yellow);
    drop-shadow(80px 0 0 red);
}

The <feDropShadow> element works the exact same way for SVGs.

Shadows and accessibility

Here’s something for you to chew on: shadows can help improve accessibility.

Google conducted a study with low-vision participants to better understand how shadows and outlines impact an individual’s ability to identify and interact with a component. They found that using shadows and outlines:

  • increases the ease and speed of finding a component when scanning pages, and
  • improves one’s ability to determine whether or not a component is interactive.

That wasn’t a wide-ranging scientific study or anything, so let’s turn around and see what the W3C says in it’s guidelines for WCAG 2.0 standards:

[…] the designer might darken the background behind the letter, or add a thin black outline (at least one pixel wide) around the letter in order to keep the contrast ratio between the letter and the background above 4.5:1.

That’s talking about light text on a light background. WCAG recommends a contrast ratio that’s at least 4.5:1 between text and images. You can use text shadows to add a stronger contrast between them.

Photo credit: Woody Van der Straeten

Shadows and performance

Before diving into shadows and adding them on all the things, it’s worth calling out that they do affect performance.

For example, filter: drop-shadow is hardware-accelerated by some browsers. A new compositor layer may be created for that element, and offloaded to the GPU. You don’t want to have too many layers, as it takes up limited GPU memory, and will eventually degrade performance. You can assess this in your browser’s DevTools.

Blurring is an expensive operation, so use it sparingly. When you blur something, it mixes the colors from pixels all around the output pixel to generate a blurred result. For example, if your <blur-radius> parameter is 2px, then the filter needs to look at two pixels in every direction around each output pixel to generate the mixed color. This happens for each output pixel, so that means a lot of calculations that grow exponentially. So, shadows with a large blur radius are generally slower to render than other shadows.

Did you know?

Did you know that shadows don’t influence the document layout?

A shadow is the same size as the element it targets. You can modify the size of a box-shadow (through the spread radius parameter), but other properties cannot modify the shadow size.

And did you know that a shadow implicitly has a lower z-index than elements? That’s why shadows sit below other elements.

And what about clipping and masking? If an element with a box-shadow is clipped (with clip-path) or uses a mask (with mask), the shadow isn’t shown. Conversely, if an element with text-shadow or filter: drop-shadow() is clipped, a shadow is shown, as long as it is within the clip region.

Here’s another: We can’t create oblique shadows (with diagonal lines) with shadow properties. That requires creating a shadow element and use a transform:skew() on it.

Oh, and one more: box-shadow follows border-radius. If an element has rounded corners, the shadow is rounded as well. In other words, the shadow mirrors the shape of the box. On the other hand, filter: drop-shadow() can create an irregular shape because it respects transparency and follows the shape of the content.

Showing two of the same card component side-by-side. They are brightly colored with a background gradient that goes from red to gold. The Nike logo is at the top, a title is below it, then a paragraph of white text beneath that. A red show with an exaggerated shadow is on both cards. The cards illustrated the difference between box shadow, which follows the boundaries of the card's edges, and drop shadow, which includes the shape of the shoe outside the card boundary.

Best use cases for different types of shadows

Practically anything on the web can have a shadow and there are multiple CSS properties and functions that create shadows. But choosing the right type of shadow is what makes a shadow effective.

Let’s evaluate the options:

  • box-shadow: This CSS property creates shadows that conform to the elements bounding box. It’s versatile and can be used on anything from cards to buttons to just about anything where the shadow simply needs to follow the element’s box.
  • text-shadow: This is a CSS property that creates shadows specifically for text elements.
  • filter: drop-shadow(): The CSS property here is filter, but what create the shadow is the drop-shadow function it accepts. What makes this type of shadow different from, say box-shadow, is that it follows the rendered shape of any element (including pseudos).
  • <feDropShadow>: This is actually an SVG element, whereas the rest are CSS properties. So, you would use this to create drop shadows directly in SVG markup.

Once you get the hang of the different types of shadows and each one’s unique shadow-creating powers, the possibilities for shadow effects feels endless. From simple drop shadows to floating elements, and even inner shadows, we can create interesting visuals that add extra meaning or value to UI.

The same goes for text shadows.

Shadows in the wild

Shadows are ubiquitous. We’re seeing them used in new and interesting ways all the time.

Have you heard the buzzword “neumorphism” floating around lately? That’s all about shadows. Here’s an implementation by Maria Muñoz:

Yuan Chuan, who makes amazing generative art, calls shadows a secret weapon in UI design:

CSS relies on existing DOM structure in the browser. It’s not possible to generate new elements other than ::before and ::after. Sometimes I really wish CSS had the ability to do so straightforwardly.

Yet, we can partially make up for this by creating various shadows and gradients entirely in CSS.

That’s why having drop-shadow is so exciting. Together with text-shadow and box-shadow we can do a lot more.

Just check out how he uses drop shadows to create intricate patterns.

Yes, that’s pretty crazy. And speaking of crazy, it’s worth mentioning that going too crazy can result in poor performance, so tread carefully.

What about pseudo-elements?

Oh yes, shadow properties are supported by the ::before and ::after pseudo-elements.

Other pseudos that respect shadows? The ::first-letter pseudo-element accepts box-shadow and text-shadow. The ::first-line pseudo-element accepts text-shadow.

Look at how Jhey Tompkins got all creative using box-shadow on pseudo elements to create animated loaders.

Animating shadows

Yes, we can make them move! The properties and function we’ve covered here are totally compatible with CSS animations and transitions. That means we can move shadows, blur shadows, expand/shrink shadows (with box-shadow), and alter the color.

Animating a shadow can provide a user with a cue that an element is interactive, or that an action has taken place. We saw earlier with our button example that an inset shadow showed that the button had been pressed. Another common animation pattern is elevating a card on hover.

If you want to optimize the animation performance, avoid animating box-shadow! It is more performant to animate drop-shadow(). But if you want the smoothest animation, a hack is the best option! Add an ::after pseudo-element with a bigger box-shadow, and animate its opacity instead.

Of course, there is a lot more you can animate. I will leave that exploration up to you!

Wrapping up

Phew, who knew there was so much to something as seemingly “simple” as CSS shadows! There’s the light source and how shadows are cast. The different types of shadows and their color. There’s using shadows for evoking depth, elevating elements and insetting them. There’s the fact that we can layer shadows on top of other shadows. And that we have a selection of CSS properties that we can use for different use cases. Then, there are the accessibility and performance implications that come with them. And, hey, animation is thing! That’s a heckuva lot!

Anyway, hopefully this broad overview gave you something new to chew on, or at the very least, helped you brush up on some concepts.


Getting Deep Into Shadows originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/getting-deep-into-shadows/feed/ 8 334324
Simulating Drop Shadows with the CSS Paint API https://css-tricks.com/simulating-drop-shadows-with-the-css-paint-api/ Tue, 29 Dec 2020 15:16:57 +0000 https://css-tricks.com/?p=331423 Ask a hundred front-end developers, and most, if not all, of them will have used the box-shadow property in their careers. Shadows are enduringly popular, and can add an elegant, subtle effect if used properly. But shadows occupy a strange …


Simulating Drop Shadows with the CSS Paint API originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Ask a hundred front-end developers, and most, if not all, of them will have used the box-shadow property in their careers. Shadows are enduringly popular, and can add an elegant, subtle effect if used properly. But shadows occupy a strange place in the CSS box model. They have no effect on an element’s width and height, and are readily clipped if overflow on a parent (or grandparent) element is hidden.

We can work around this with standard CSS in a few different ways. But, now that some of the CSS Houdini specifications are being implemented in browsers, there are tantalizing new options. The CSS Paint API, for example, allows developers to generate images programmatically at run time. Let’s look at how we can use this to paint a complex shadow within a border image.

A quick primer on Houdini

You may have heard of some newfangled CSS tech hitting the platform with the catchy name of Houdini. Houdini promises to deliver greater access to how the browser paints the page. As MDN states, it is “a set of low-level APIs that exposes parts of the CSS engine, giving developers the power to extend CSS by hooking into the styling and layout process of a browser’s rendering engine.”

The CSS Paint API

The CSS Paint API is one of the first of these APIs to hit browsers. It is a W3C candidate recommendation. This is the stage when specifications start to see implementation. It is currently available for general use in Chrome and Edge, while Safari has it behind a flag and Firefox lists it as “worth prototyping”. There is a polyfill available for unsupported browsers, though it will not run in IE11.

While the CSS Paint API is enabled in Chromium, passing arguments to the paint() function is still behind a flag. You’ll need to enable experimental web platform features for the time being. These examples may not, unfortunately, work in your browser of choice at the moment. Consider them an example of things to come, and not yet ready for production.

The approach

We’re going to generate an image with a shadow, and then use it for a border-image… huh? Well, let’s take a deeper look.

As mentioned above, shadows don’t add any width or height to an element, but spread out from its bounding box. In most cases, this isn’t a problem, but those shadows are vulnerable to clipping. A common workaround is to create some sort of offset with either padding or margin.

What we’re going to do is build the shadow right into the element by painting it in to the border-image area. This has a few key advantages:

  1. border-width adds to the overall element width
  2. Content won’t spill into the border area and overlap the shadow
  3. Padding won’t need any extra width to accommodate the shadow and content
  4. Margins around the element won’t interfere with that element’s siblings

For that aforementioned group of one hundred developers who’ve used box-shadow, it’s likely only a few of them have used border-image. It’s a funky property. Essentially, it takes an image and slices it into nine pieces, then places them in the four corners, sides and (optionally) the center. You can read more about how all this works in Nora Brown’s article.

The CSS Paint API will handle the heavy lifting of generating the image. We’re going to create a module for it that tells it how to layer a series of shadows on top of each other. That image will then get used by border-image.

These are the steps we’ll take:

  1. Set up the HTML and CSS for the element we want to paint in
  2. Create a module that draws the image
  3. Load the module into a paint worklet
  4. Call the worklet in CSS with the new paint() function

Setting up the canvas

You’re going to hear the term canvas a few times here, and in other CSS Paint API resources. If that term sounds familiar, you’re right. The API works in a similar way to the HTML <canvas> element.

First, we have to set up the canvas on which the API will paint. This area will have the same dimensions as the element that calls the paint function. Let’s make a 300×300 div.

<section>
  <div class="foo"></div>
</section>

And the styles:

.foo {
  border: 15px solid #efefef;
  box-sizing: border-box;
  height: 300px;
  width: 300px;
}

Creating the paint class

HTTPS is required for any JavaScript worklet, including paint worklets. You won’t be able to use it at all if you’re serving your content over HTTP.

The second step is to create the module that is loaded into the worklet — a simple file with the registerPaint() function. This function takes two arguments: the name of the worklet and a class that has the painting logic. To stay tidy, we’ll use an anonymous class.

registerPaint(
  "shadow",
  class {}
);

In our case, the class needs two attributes, inputProperties and inputArguments, and a method, paint().

registerPaint(
  "shadow",
  class {
    static get inputProperties() {
      return [];
    }
    static get inputArguments() {
      return [];
    }
    paint(context, size, props, args) {}
  }
);

inputProperties and inputArguments are optional, but necessary to pass data into the class.

Adding input properties

We need to tell the worklet which CSS properties to pull from the target element with inputProperties. It’s a getter that returns an array of strings.

In this array, we list both the custom and standard properties the class needs: --shadow-colors, background-color, and border-top-width. Pay particular attention to how we use non-shorthand properties.

static get inputProperties() {
  return ["--shadow-colors", "background-color", "border-top-width"];
}

For simplicity, we’re assuming here that the border is even on all sides.

Adding arguments

Currently, inputArguments are still behind a flag, hence enabling experimental features. Without them, use inputProperties and custom properties instead.

We also pass arguments to the paint module with inputArguments. At first glance, they may seem superfluous to inputProperties, but there are subtle differences in how the two are used.

When the paint function is called in the stylesheet, inputArguments are explicitly passed in the paint() call. This gives them an advantage over inputProperties, which might be listening for properties that could be modified by other scripts or styles. For example, if you’re using a custom property set on :root that changes, it may filter down and affect the output.

The second important difference for inputArguments, which is not intuitive, is that they are not named. Instead, they are referenced as items in an array within the paint method. When we tell inputArguments what it’s receiving, we are actually giving it the type of the argument.

The shadow class is going to need three arguments: one for X positions, one for Y positions, and one for blurs. We’ll set that up as three space-separated lists of integers.

Anyone who has registered a custom property may recognize the syntax. In our case, the <integer> keyword means any whole number, while + denotes a space-separated list.

static get inputArguments() {
  return ["<integer>+", "<integer>+", "<integer>+"];
}

To use inputProperties in place of inputArguments, you could set custom properties directly on the element and listen for them. Namespacing would be critical to ensure inherited custom properties from elsewhere don’t leak in.

Adding the paint method

Now that we have the inputs, it’s time to set up the paint method.

A key concept for paint() is the context object. It is similar to, and works much like, the HTML <canvas> element context, albeit with a few small differences. Currently, you cannot read pixels back from the canvas (for security reasons), or render text (there’s a brief explanation why in this GitHub thread).

The paint() method has four implicit parameters:

  1. The context object
  2. Geometry (an object with width and height)
  3. Properties (a map from inputProperties)
  4. Arguments (the arguments passed from the stylesheet)
paint(ctx, geom, props, args) {}

Getting the dimensions

The geometry object knows how big the element is, but we need to adjust for the 30 pixels of total border on the X and Y axis:

const width = (geom.width - borderWidth * 2);
const height = (geom.height - borderWidth * 2);

Using properties and arguments

Properties and arguments hold the resolved data from inputProperties and inputArguments. Properties come in as a map-like object, and we can pull values out with get() and getAll():

const borderWidth = props.get("border-top-width").value;
const shadowColors = props.getAll("--shadow-colors");

get() returns a single value, while getAll() returns an array.

--shadow-colors will be a space-separated list of colors which can be pulled into an array. We’ll register this with the browser later so it knows what to expect.

We also need to specify what color to fill the rectangle with. It will use the same background color as the element:

ctx.fillStyle = props.get("background-color").toString();

As mentioned earlier, arguments come into the module as an array, and we reference them by index. They’re of the type CSSStyleValue right now — let’s make it easier to iterate through them:

  1. Convert the CSSStyleValue into a string with its toString() method
  2. Split the result on spaces with a regex
const blurArray = args[2].toString().split(/\s+/);
const xArray = args[0].toString().split(/\s+/);
const yArray = args[1].toString().split(/\s+/);
// e.g. ‘1 2 3’ -> [‘1’, ‘2’, ‘3’]

Drawing the shadows

Now that we have the dimensions and properties, it’s time to draw something! Since we need a shadow for each item in shadowColors, we’ll loop through them. Start with a forEach() loop:

shadowColors.forEach((shadowColor, index) => { 
});

With the index of the array, we’ll grab the matching values from the X, Y, and blur arguments:

shadowColors.forEach((shadowColor, index) => {
  ctx.shadowOffsetX = xArray[index];
  ctx.shadowOffsetY = yArray[index];
  ctx.shadowBlur = blurArray[index];
  ctx.shadowColor = shadowColor.toString();
});

Finally, we’ll use the fillRect() method to draw in the canvas. It takes four arguments: X position, Y position, width, and height. For the position values, we’ll use border-width from inputProperties; this way, the border-image is clipped to contain just the shadow around the rectangle.

shadowColors.forEach((shadowColor, index) => {
  ctx.shadowOffsetX = xArray[index];
  ctx.shadowOffsetY = yArray[index];
  ctx.shadowBlur = blurArray[index];
  ctx.shadowColor = shadowColor.toString();

  ctx.fillRect(borderWidth, borderWidth, width, height);
});

This technique can also be done using a canvas drop-shadow filter and a single rectangle. It’s supported in Chrome, Edge, and Firefox, but not Safari. See a finished example on CodePen.

Almost there! There are just a few more steps to wire things up.

Registering the paint module

We first need to register our module as a paint worklet with the browser. This is done back in our main JavaScript file:

CSS.paintWorklet.addModule("https://codepen.io/steve_fulghum/pen/bGevbzm.js");
https://codepen.io/steve_fulghum/pen/BazexJX

Registering custom properties

Something else we should do, but isn’t strictly necessary, is to tell the browser a little more about our custom properties by registering them.

Registering properties gives them a type. We want the browser to know that --shadow-colors is a list of actual colors, not just a string.

If you need to target browsers that don’t support the Properties and Values API, don’t despair! Custom properties can still be read by the paint module, even if not registered. However, they will be treated as unparsed values, which are effectively strings. You’ll need to add your own parsing logic.

Like addModule(), this is added to the main JavaScript file:

CSS.registerProperty({
  name: "--shadow-colors",
  syntax: "<color>+",
  initialValue: "black",
  inherits: false
});

You can also use @property in your stylesheet to register properties. You can read a brief explanation on MDN.

Applying this to border-image

Our worklet is now registered with the browser, and we can call the paint method in our main CSS file to take the place of an image URL:

border-image-source: paint(shadow, 0 0 0, 8 2 1, 8 5 3) 15;
border-image-slice: 15;

These are unitless values. Since we’re drawing a 1:1 image, they equate to pixels.

Adapting to display ratios

We’re almost done, but there’s one more problem to tackle.

For some of you, things might not look quite as expected. I’ll bet you sprung for the fancy, high DPI monitor, didn’t you? We’ve encountered an issue with the device pixel ratio. The dimensions that have been passed to the paint worklet haven’t been scaled to match.

Rather than go through and scale each value manually, a simple solution is to multiply the border-image-slice value. Here’s how to do it for proper cross-environment display.

First, let’s register a new custom property for CSS that exposes window.devicePixelRatio:

CSS.registerProperty({
  name: "--device-pixel-ratio",
  syntax: "<number>",
  initialValue: window.devicePixelRatio,
  inherits: true
});

Since we’re registering the property, and giving it an initial value, we don’t need to set it on :root because inherit: true passes it down to all elements.

And, last, we’ll multiply our value for border-image-slice with calc():

.foo {
  border-image-slice: calc(15 * var(--device-pixel-ratio));
}

It’s important to note that paint worklets also have access to the devicePixelRatio value by default. You can simply reference it in the class, e.g. console.log(devicePixelRatio).

Finished

Whew! We should now have a properly scaled image being painted in the confines of the border area!

Live demo (best viewed in Chrome and Edge)
Live demo (best viewed in Chrome and Edge)

Bonus: Apply this to a background image

I’d be remiss to not also demonstrate a solution that uses background-image instead of border-image. It’s easy to do with just a few modifications to the module we just wrote.

Since there isn’t a border-width value to use, we’ll make that a custom property:

CSS.registerProperty({
  name: "--shadow-area-width",
  syntax: "<integer>",
  initialValue: "0",
  inherits: false
});

We’ll also have to control the background color with a custom property as well. Since we’re drawing inside the content box, setting an actual background-color will still show behind the background image.

CSS.registerProperty({
  name: "--shadow-rectangle-fill",
  syntax: "<color>",
  initialValue: "#fff",
  inherits: false
});

Then set them on .foo:

.foo {
  --shadow-area-width: 15;
  --shadow-rectangle-fill: #efefef;
}

This time around, paint() gets set on background-image, using the same arguments as we did for border-image:

.foo {
  --shadow-area-width: 15;
  --shadow-rectangle-fill: #efefef;
  background-image: paint(shadow, 0 0 0, 8 2 1, 8 5 3);
}

As expected, this will paint the shadow in the background. However, since background images extend into the padding box, we’ll need to adjust padding so that text doesn’t overlap:

.foo {
  --shadow-area-width: 15;
  --shadow-rectangle-fill: #efefef;
  background-image: paint(shadow, 0 0 0, 8 2 1, 8 5 3);
  padding: 15px;
}

Fallbacks

As we all know, we don’t live in a world where everyone uses the same browser, or has access to the latest and greatest. To make sure they don’t receive a busted layout, let’s consider some fallbacks.

Padding fix

Padding on the parent element will condense the content box to accommodate for shadows that extend from its children.

section.parent {
  padding: 6px; /* size of shadow on child */
}

Margin fix

Margins on child elements can be used for spacing, to keep shadows away from their clipping parents:

div.child {
  margin: 6px; /* size of shadow on self */
}

Combining border-image with a radial gradient

This is a little more off the beaten path than padding or margins, but it’s got great browser support. CSS allows gradients to be used in place of images, so we can use one within a border-image, just like how we did with paint(). This may be a great option as a fallback for the Paint API solution, as long as the design doesn’t require exactly the same shadow:

Gradients can be finicky and tricky to get right, but Geoff Graham has a great article on using them.

div {
  border: 6px solid;
  border-image: radial-gradient(
    white,
    #aaa 0%,
    #fff 80%,
    transparent 100%
  )
  25%;
}

An offset pseudo-element

If you don’t mind some extra markup and CSS positioning, and need an exact shadow, you can also use an inset pseudo-element. Beware the z-index! Depending on the context, it may need to be adjusted.

.foo {
  box-sizing: border-box;
  position: relative;
  width: 300px;
  height: 300px;
  padding: 15px;
}

.foo::before {
  background: #fff;
  bottom: 15px;
  box-shadow: 0px 2px 8px 2px #333;
  content: "";
  display: block;
  left: 15px;
  position: absolute;
  right: 15px;
  top: 15px;
  z-index: -1;
}

Final thoughts

And that, folks, is how you can use the CSS Paint API to paint just the image you need. Is it the first thing to reach for in your next project? Well, that’s for you to decide. Browser support is still forthcoming, but pushing forward.

In all fairness, it may add far more complexity than a simple problem calls for. However, if you’ve got a situation that calls for pixels put right where you want them, the CSS Paint API is a powerful tool to have.

What’s most exciting though, is the opportunity it provides for designers and developers. Drawing shadows is only a small example of what the API can do. With some imagination and ingenuity, all sorts of new designs and interactions are possible.

Further reading


Simulating Drop Shadows with the CSS Paint API originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
331423
Breaking down CSS Box Shadow vs. Drop Shadow https://css-tricks.com/breaking-css-box-shadow-vs-drop-shadow/ https://css-tricks.com/breaking-css-box-shadow-vs-drop-shadow/#comments Fri, 20 Oct 2017 15:57:03 +0000 http://css-tricks.com/?p=261309 Drop shadows. Web designers have loved them for a long time to the extent that we used to fake them with PNG images before CSS Level 3 formally introduced them to the spec as the box-shadow property. I still reach …


Breaking down CSS Box Shadow vs. Drop Shadow originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Drop shadows. Web designers have loved them for a long time to the extent that we used to fake them with PNG images before CSS Level 3 formally introduced them to the spec as the box-shadow property. I still reach for drop shadows often in my work because they add a nice texture in some contexts, like working with largely flat designs.

Not too long after box-shadow was introduced, a working draft for CSS Filters surfaced and, with it, a method for drop-shadow() that looks a lot like box-shadow at first glance. However, the two are different and it’s worth comparing those differences.

For me, the primary difference came to light early on when I started working with box-shadow. Here’s a simple triangle not unlike the one I made back then.

Let’s use this to break down the difference between the two.

Box Shadow

Add a box-shadow on that bad boy and this happens.

It’s annoying, but makes sense. CSS uses a box model, where the element’s edges are bound in the shape of a rectangle. Even in cases where the shape of the element does not appear to be a box, the box is still there and that is was box-shadow is applied to. This was my “ah-ha moment” when understanding the box in box-shadow.

CSS Filter Drop Shadow

CSS Filters are pretty awesome. Take a gander at all the possibilities for adding visual filters on elements and marvel at how CSS suddenly starts doing a lot of things we used to have to mockup in Photoshop.

Filters are not bound to the box model. That means the outline of our triangle is recognized and the transparency around it is ignored so that the intended shape receives the shadow.

Deciding Which Method to Use

The answer is totally up to you. The simple example of a triangle above might make it seem that filter: drop-shadow() is better, but it’s not a fair comparison of the benefits or even the possibilities of both methods. It’s merely an illustration of their different behaviors in a specific context.

Like most things in development, the answer of which method to use depends. Here’s a side-by-side comparison to help distinguish the two and when it might be best to choose one over the other.

 Box ShadowDrop Shadow
SpecificationCSS Backgrounds and Borders Module Level 3Filter Effects Module Level 1
Browser SupportGreatGood
Supports Spread RadiusYes, as an optional fourth valueNo
Blur RadiusCalculation is based on a pixel lengthCalculation is based on the stdDeviation attribute of the SVG filter
Supports inset shadowsYesNo
PerformanceNot hardware acceleratedHardware accelerated in browsers that support it. It’s a heavy lift without it.

Wrapping Up

The difference between box-shadow and filter: drop-shadow() really boils down to the CSS box model. One sees it and the other disregards it. There are other differences that distinguish the two in terms of browser support, performance and such, but the way the two treat the box model is the key difference.

Update: Amelia identified another key difference in the comments where the spread of the radius for drop-shadow() is calculated differently than box-shadow and even that of text-shadow. That means that the spread radius you might specify in box-shadow is not one-to-one with the default spread value for drop-shadow, so the two are not equal replacements of one another in some cases.

Let’s cap this off with a few other great examples illustrating that. Lennart Schoors also has a nice write-up with practical examples using tooltips and icons that we previously called out.


Breaking down CSS Box Shadow vs. Drop Shadow originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/breaking-css-box-shadow-vs-drop-shadow/feed/ 10 261309
Slicing SVG 9 Ways https://css-tricks.com/slicing-svg-9-ways/ Tue, 06 Sep 2016 14:00:49 +0000 http://css-tricks.com/?p=245253 Paul Lewis animated a 3D Card Flip. Straightforward enough there, but this needed a shadow element as well:

The shadow may need to move around or fade, so we need a solution that we can animate with transforms & opacity,


Slicing SVG 9 Ways originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Paul Lewis animated a 3D Card Flip. Straightforward enough there, but this needed a shadow element as well:

The shadow may need to move around or fade, so we need a solution that we can animate with transforms & opacity, since they can be handed off to the GPU. It will need to be separate to the element it’s shadowing, since we will likely want to fade and move it without affecting the other element.

He tried box-shadow and filter: blur(), but both aren’t performant enough.

He tried an SVG element with a blur filter, but SVG alone can’t do 9-slice scaling (necessary to keep the corners from scaling awkwardly).

The winner was using an SVG image but applying it to an element via border-image, which essentially 9-slice scaling.

To Shared LinkPermalink on CSS-Tricks


Slicing SVG 9 Ways originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
245253
Top Shadow https://css-tricks.com/snippets/css/top-shadow/ https://css-tricks.com/snippets/css/top-shadow/#comments Wed, 16 Feb 2011 16:18:55 +0000 http://css-tricks.com/ Shadow along the top edge of the website, like this:

body::before {
  content: "";
  position: fixed;
  top: -10px;
  left: 0;
  width: 100%;
  height: 10px;
  box-shadow: 0px 0 10px rgba(0, 0, 0, 0.8);
  z-index: 100;
}


Top Shadow originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Shadow along the top edge of the website, like this:

body::before {
  content: "";
  position: fixed;
  top: -10px;
  left: 0;
  width: 100%;
  height: 10px;
  box-shadow: 0px 0 10px rgba(0, 0, 0, 0.8);
  z-index: 100;
}

Top Shadow originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/snippets/css/top-shadow/feed/ 33 8529