How to create dynamic shadows in CSS

Recently I wanted to create a gradient shadow – a shadow that would play nicely with gradient background. As we might suspect it needed a kind of a hack – it is CSS after all. But this hack is quite interesting so I wanted to share my idea with you.

box-shadow

Let’s start with recap of box-shadow then.
This CSS property accepts up to 5 parameters

  1. offset-x
  2. offset-y
  3. blur-radius
  4. spread-radius
  5. color

Two first specifies whether the shadow will be slightly moved to one direction.
Another one is how “blurry” will our blur be.
And the last but one is how far from element the blur will start.

The last one is the most important here – color.
According specification a color can be defined in any of the following ways:

• Using a keyword (such as blue or transparent)
• Using the RGB cubic-coordinate system (via the #-hexadecimal or the rgb() and rgba() functional notations)
• Using the HSL cylindrical-coordinate system (via the hsl() and hsla() functional notations)
MDN web docs

Unfortunately no gradient here 😞

Solution

So how to tackle this?
filter and pseudo-elements come to rescue!

filter

This property allows to apply graphical filters on the element. We can change hue (with hue-rotate), or make the image black-and-white (with grayscale) but the one we want is blur. As the name suggests it applies a Gaussian blur.

Thanks to that great feature we can achieve the shadow like this.

edit on JSFiddle
.dynamic-shadow {
  position: relative;
  width: 300px;
  height: 300px;
  z-index: 1;
  background-size: cover;
  background-image: linear-gradient(135deg, #1e5799 0%, #FF0000 100%);
  background-size: cover;
  border-radius: 5px;
  
  &::before, &::after {
    content: '';
    width: 100%;
    height: 100%;
    position: absolute;
    border-radius: inherit;
    top: 0;
    left: 0;
    opacity: 1;
    z-index: 2;
    background: inherit;
  }
  
  &::before {
    filter: blur(50px);
    opacity: 1;
    z-index: 1;
  }
}

Firstly we create wrapper for a gradient. Then we create ::before and ::after. These two will be the core of whole effect.

::after

This element is to display actual image. We have to create this one due to the way z-index works – we cannot create a descendant that will be underneath the parent that would normally display image.

::before

This one will create the shadow. We give it a desired background and apply filter with value of blur(50px). This will create some nice looking blurred image that imitates shadow.

edit on JSFiddle
.dynamic-shadow {
  position: relative;
  width: 300px;
  height: 300px;
  z-index: 1;
  background-image: linear-gradient(135deg, #1e5799 0%, #FF0000 100%);
  background-size: cover;
  border-radius: 5px;
  
  &::before{
    content: '';
    width: 100%;
    height: 100%;
    position: absolute;
    background: inherit;
    top: 0;
    left: 0;
    opacity: 1;
    z-index: 2;
    border-radius: inherit;
  }

  &::before {
    filter: blur(50px);
    opacity: 1;
    z-index: 1;
  }
}

But there is one more thing…

Obviously it works not only with gradients but also with any image supported by background-image.

edit on JSFiddle
.dynamic-shadow {
  position: relative;
  width: 300px;
  height: 300px;
  z-index: 1;
  margin: 3rem;
  border-radius: 5px;
  background-size: cover;
  
  &::before, &::after {
    content: '';
    width: 100%;
    height: 100%;
    position: absolute;
    background: inherit;
    top: 0;
    left: 0;
    opacity: 1;
    z-index: 2;
    border-radius: inherit;
  }
  
  &::before {
    filter: blur(25px);
    opacity: 1;
    z-index: 1;
  }
}

.img-1 {
  background-image: linear-gradient(135deg, #1e5799 0%, #FF0000 100%);
}
  
.img-2 { 
  background-image: url("https://unsplash.it/300/300?image=1069");
}

And it even works with gifs! So we can create something like those fancy tvs with leds behind that were quite popular some time ago. (NOTE: Chrome doesn’t handle it very well, however on Firefox it works ok).