Why do we need to apply shadows to SVG?

  1. Shadows are a common design feature that can help elements such as icons stand out. They can be permanent or can be used in different spaces (e.g. :hover, :focusor :active) to show interaction with users.
  2. Shadows happen in real life, so they can be used on screens to breathe your life into the elements and add a touch of realism to the design.

Since we make lists, there are two primary ways to apply shadows to SVG:

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

Yes, both come with filters! And yes, both CSS and SVG have their own filter types. But there is also some intersection between these. For example, CSS filter may refer to SVG <filter>; i.e. if we are working with an internal SVG, for example instead of SVG used as a background image for CSS.

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

However, if you use the SVG icon font, there are always them text-shadow. It really works. But let’s focus on the first two because they fit most use cases.

Shadows with CSS filters

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

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

This applies a shadow that starts at 3 pixels horizontally, 5 pixels down, with a 2-pixel blur, and is 40% black. Here are some examples:

Workbench

Chrome Firefox IE Edge Safari
18 * 35 No 79 6 *

Mobile phone / tablet

Android Chrome Android Firefox Android iOS Safari
91 89 4.4 * 6.0-6.1 *

Invoke the SVG filter inside the CSS filter

Assume that the HTML code has an SVG filter:

<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 the SVG filter ID instead of the values ​​we saw earlier:

svg {
  filter: url(#shadow);
}

This filter is taken from HTML and referenced in the CSS that uses it.

Using SVG filter primitives

You may be wondering how we got that SVG <filter> work. If you want to make a shadow with an SVG filter, we use a filter primitive. An SVG filter primitive is an element that takes some sort of image or graphic as input and then outputs the image or graphic when it is called. They work in a way like filters in a graphical editing application, but as code, and can only be used inside SVG <filter> element.

Is lots of different filter primitives in SVG. Our goal is <feDropShadow>. I’ll let you guess what to do just by looking at the name.

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

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

… We can achieve the same <feDropShadow> SVG filter primitive. There are three key features that are worth calling out because they help define the appearance of the shadow:

  • dx – This changes the position of the shadow on the x-axis.
  • dy – This changes the position of the shadow along the y-axis.
  • stdDeviation – This defines the standard deviation for the shadow blur function. We can use other features such as flood-color to set the color of the shadow and flood-opacity to set the shadow opacity.

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

Using SVG filters

SVG filters are very effective. We just looked <feDropShadow>, which is very useful, of course, but they can do so much more (including effects like Photoshop), and the range of products we only get in the shadows is wide. Let’s look at some, like colored shadows and recessed shadows.

Take the SVG annotations of 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 need a <filter> element to make these effects. This must be <svg> HTML element. A <filter> element is never rendered directly in the browser – it is only used as a reference filter attribute in SVG or url() In CSS.

Here is a syntax that displays the SVG filter and applies it to the 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>

filter the element is meant to hold filter primitives as a child. It is a container for a series of filter functions that are combined to create filter effects.

These filter primitives perform one or more inputs of a single basic graphical function (e.g., Blur, Move, Fill, Combine, or Distort). They are like building blocks where each SVG filter can be used in conjunction with others to create an effect. <feGaussianBlur> is a popular filter primitive used to increase inaccuracy.

Suppose we configure the following SVG filter <feGaussianBlur>:

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

When applied to an element, this filter creates a Gaussian blur which obscures element a 1px radius on the x-axis but no blur on the y-axis. Here is the result without effect:

It is possible to use several primitives within one filter. This creates interesting effects, but you need to make the different primitives aware of each other. Bence Szabó on crazy cool set of designs he created like this.

When combining multiple filter primitives, the first primitive uses the original image (SourceGraphic) as a graphical input. All subsequent primitives use the effect of the filter before it as input. And so on. But we can get into that by using flexibility in, in2 and result attributes with primitive elements. Steven Bradley has an excellent script for filter primitives which dates back to 2016, but remains true.

Today we can use 17 primitives:

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

Please note fe prefix for everyone. It means filter effect. Understanding SVG filters is challenging. An effect like an inner shadow requires a varied syntax that is difficult to understand without a thorough understanding of mathematics and color theory. (Rob O’Learyn “Deepening Shadows” is a good place to start.)

Instead of driving everything out of this rabbit hole, we’re going to work with some ready-made filters. Fortunately, there are a lot of ready-to-use SVG filters around.

Enter the shadows

To use a filter effect in the Twitter logo, we must report it in an SVG source document with a unique ID to refer to <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 and each performs its own mission. But together, they achieve a solid shadow.

Now that we have created this internal shadow filter, we can apply it to SVG. We have already seen how it can be used through CSS. A bit like:

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

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

We can also use SVG <filter> directly in SVG syntax filter attribute. It is 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 a few shadow examples from Oleg Solomka:

Note that the basic shadows here are probably a little more complex than they should be. For example, a colored shadow can still be made <feDropShadow> Like:

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

But the embossed effect is pretty great as a filter!

Also note that you may 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>

The first line says: this SVG shouldn’t be done at all – it’s just stuff we’re going to use later. <defs> the tag says something similar: we only specify these things for later use. That way, we don’t have to repeat ourselves by writing things over and over again. We refer by filter ID and also by symbols, maybe like:

<svg>
  <use xlink:href="https://css-tricks.com/adding-shadows-to-svg-icons-with-css-and-svg-filters/#my-icon" />
</svg>

SVG filters have extensive support (even in Internet Explorer and Edge!), And their performance is very fast.

Workbench

Chrome Firefox IE Edge Safari
8 3 10 12 6

Mobile phone / tablet

Android Chrome Android Firefox Android iOS Safari
91 89 4.4 6.0-6.1

Wrap things up

Last comparison:

  • CSS filters are easier to use, but they are much more limited. I don’t think it’s possible to add an embedded shadow drop-shadow() for example.
  • SVG filters are much more robust, but also much more complex and demanding <filter> in some HTML code.
  • They both have great browser support and work well in all modern browsers, although SVG filters have the (surprisingly) deepest browser support.

In this article, we’ve seen why and how to apply shadow to SVG icons, each with examples. Have you done this, but did you do it differently than what we were watching? Have you tried to make a shadow effect that is impossible to find? Share, please!

LEAVE A REPLY

Please enter your comment!
Please enter your name here