The year was 2018, and we were still trying to cope with IE11 and had to find some ways for certain effects. Things that we take for granted with CSS nowadays were not possible and one of the solutions to create awesome effects on the web was using SVG Filters. Fast forward to 2025, SVG filters are still awesome, but I use them less. In this article, I wanted to revisit them, with some easy-to-use effects we can use today.

Before you get angry and yell at me that SVGs are still relevant, let me tell you that I’m completely aware of this. I see a lot of great people creating amazing things with them, but the truth is that in my day job, I don’t use them that often. It’s kind of a shame and recently I had a project where I found a good use case for them once again, so I thought to myself: “maybe I should revisit some of those basic effects?” This means that I will be keeping things at a basic level. Back in 2018, I didn’t write on this blog, and I used to be quite good at writing those filters by hand, so here is me documenting some of my favorite effects I used back then as I noticed some of those skills got lost over time. By no means am I an expert when it comes to SVG filters, which is exactly why I wanted to write about them.

Creating an SVG filter

In this article, I’ll be focussing on SVG filters that we can call in our CSS. To achieve this, you’ll need an SVG element with a filter defined inside of it. This filter will have a unique ID that we can target through our CSS filter property.

<svg>
  <filter id="my-filter">
      <!-- filter effects -->
  </filter>
</svg>
.some-thing {
   filter: url(#my-filter);
}

Duotones and other color manipulations

Who doesn’t love a good old duotone? To illustrate this, I will be using the same image over and over again. It’s a picture taken the first time I went to Berlin.

The base image: 3 hi-rise buildings in Berlin

Before we get into the SVGs, let’s take a quick look at the built-in filters we have available in CSS.

We can mimic duotones in CSS, for example, the filter property does have a grayscale() filter, so we can turn our image into black and white. We also have a sepia() duotone ready-to-use:

img {
  filter: sepia(100%);
}

By combining sepia and a hue-rotate() filter we can even sort of mimic a colored duotone with CSS:

img {
  filter: sepia(100%) hue-rotate(250deg);
}
The base image: 3 hi-rise buildings in Berlin

Even though this looks kinda great, it still lacks a bit of control, especially If we want to work with brand colors for our clients. This is where SVG filters come in handy.

Color SVG filter part 1: Black and White

Before we go into the coloring part, the first thing we’ll need to do is set our image into black and white. But just this one action already opens up a lot of opportunities.

To manipulate color, the best way to start is with the <feColorMatrix>. This is a real powerhouse and I’ll try to explain the basics as easy as possible. Let’s start by recreating a grayscale:

<svg viewBox="0 0 0 0" width="0" height="0" aria-hidden="true">
  <filter id="grayscale">
    <feColorMatrix type="matrix" values="
        .33 .33 .33 0 0
        .33 .33 .33 0 0
        .33 .33 .33 0 0
        0 0 0 1 0" />
  </filter>
</svg>

The “fe” stands for “Filter Effects”, this is something we’ll see a lot in the rest of this article. So we added the ColorMatrix effect with a bunch of values. The syntax looks a bit overwhelming (with all those values), but trust me, these values do make a lot of sense, and we’ll get there, just a quick example of our result when targeting with CSS:

img {
   filter: url(#grayscale);
}
The base image: 3 hi-rise buildings in Berlin

Let’s dive into those values so that you can play around with them. I on purposely set those values in 4 rows. And this is what they do:

[ R ] : [ r  g  b  a  o ]
[ G ] : [ r  g  b  a  o ]
[ B ] : [ r  g  b  a  o ]
[ A ] : [ a  a  a  a  o ]

Each of the first three rows specifies which color we want to adjust. For example, the first row will let us change the reds, the first three values inside of it are where we declare the new color for all our reds, followed by our alpha transparency and offset.

As an overly simplified example. If I wanted all my reds to turn into greens, all my greens into reds, and keep the blues it would look like this:

<feColorMatrix type="matrix" values="
  0 1 0 0 0
  1 0 0 0 0
  0 0 1 0 0
  0 0 0 1 0" />
The base image: 3 hi-rise buildings in Berlin

So by setting each of those channels to .33 you create a grayscale, just as you would in an RGB color rgb(10 10 10). You want each column to have the same value to create a grayscale image.

This already opens a lot more possibilities compared to our CSS grayscale filter as we can adjust the darkness based on each channel. In this example. I want my blues really light and my reds somewhere in the middle, creating a completely different type of grayscale:

<feColorMatrix type="matrix" values="
  0.5 0.2 0.8 0 0
  0.5 0.2 0.8 0 0
  0.5 0.2 0.8 0 0
  0 0 0 1 0" />
The base image: 3 hi-rise buildings in Berlin

A few things I didn’t mention yet is that the last row of the matrix [A] affects the alpha (transparency) channel. The last column of the matrix provides additive offsets to the color channel, I sometimes use this to darken or lighten my grayscale as well. That opens a whole bunch of other possibilities that I haven’t invested enough time in (yet), but feel free to play around with it to notice its effects.

 <feColorMatrix type="matrix" values="
    .33 .33 .33 0 0
    .33 .33 .33 0 0
    .33 .33 .33 0 0
    1 1 1 0 0" />
The base image: with a duotone purple filter on it

Extra: Creating a NEOPAN-like black and white

As I said before, there are endless possibilities with SVG filters. When I shoot in black & white with my camera, I always try to mimic a Fuji NEOPAN style of film. I just love the contrasts and the dramatic colors. You could emulate these kinds of things. It’s not perfect for every use-case but this is an exaggeration of it:

<svg viewBox="0 0 0 0" width="0" height="0" aria-hidden="true">
  <filter id="neopan">
    <feColorMatrix type="matrix" values="
      0.5 0.44 0.1 0 -.02
      0.5 0.44 0.1 0 -.02
      0.5 0.44 0.1 0 -.02
      0 0 0 1 0" />
    <feComponentTransfer>
      <feFuncR type="linear" slope="1" intercept="-0.02" />
      <feFuncG type="linear" slope="1" intercept="-0.02" />
      <feFuncB type="linear" slope="1" intercept="-0.02" />
    </feComponentTransfer>
  </filter>
</svg>
The base image: 3 hi-rise buildings in Berlin

As you see, I added an extra filter named <feComponentTransfer>. I’ll keep the explanation of this example short:

<feFuncR>, <feFuncG>, and <feFuncB> perform a color component transfer function. This is handy to adjust contrast and brightness after the color transformation we did with <feColorMatrix>.

In short, for each of those RGB color functions we can change the following:

  • Slope: Adjusts the contrast for that channel. > 1 increase contrast, < 1 lowers the contrast

  • Intercept: Brightens the image when > 1, darkens the image when < 1

Note that I’m only going about the “linear” value for the type attribute, there is a lot more to it and the options can vary based on that type. (for reference, the other are: identity | table | discrete | linear | gamma). All these SVG filters probably deserve an article on their own. However, this particular one will be re-used for our duotone.

So now that we’ve gotten this far in all the black & white settings. The next part should be a lot easier:

Color SVG filter part 2: Duotone!

Let’s start by creating our cool duotone colors. First thing we’ll need to do to achieve this effect is to set our image in grayscale once again. If you were following along, that should be rather easy by now.

<svg viewBox="0 0 0 0" width="0" height="0" aria-hidden="true">
  <filter id="duotone-purple">
    <feColorMatrix type="matrix" values=".33 .33 .33 0 0
        .33 .33 .33 0 0
        .33 .33 .33 0 0
        0 0 0 1 0">
    </feColorMatrix>
  </filter>
</svg>

This creates a nice and simple grayscale image to start with as we did before:

The base image: 3 hi-rise buildings in Berlin

Alright, so we’ve got our grayscale image, and now we want to inject some color, turning it into a proper duotone. We’re going to use the <feComponentTransfer> element again, just like in the NEOPAN example, but this time with a twist. This filter effect lets us manipulate the color channels individually after the grayscale conversion.

<feComponentTransfer color-interpolation-filters="sRGB">
  <feFuncR type="table" tableValues=".996078431  .984313725"></feFuncR>
  <feFuncG type="table" tableValues=".125490196  .941176471"></feFuncG>
  <feFuncB type="table" tableValues=".552941176  .478431373"></feFuncB>
</feComponentTransfer>

Remember those <feFuncR>, <feFuncG>, and <feFuncB> elements? They’re back, and this time we’ll be using the type="table" attribute. This is where the magic happens for creating duotone colors.

Instead of linear which we used before to adjust contrast and brightness, table lets us create a lookup table. What this table does, is that for each input value, we provide a corresponding output value.

The tableValues attribute is the key here. It’s a space-separated list of numbers between 0 and 1. In this case, we’re providing two values for each color channel. This means we are defining two points on a graph. The filter interpolates between these points.

But how do we get these colors? It’s easiest explained with an example. Let’s set our duotone in the following two colors:

rgb(224 36 111) and rgb(201 200 44)

As I mentioned before, we are not using RGB color values but a value between 0 and 1. For each of our color channels, we are going to get that value by getting the channel value and divide it by 255. Here is the rundown:

rgb(224 36 111):

  • Red: 224 / 255 = 0.878431373
  • Green: 36 / 255 = 0.141176471
  • Blue: 111 / 255 = 0.435294118

rgb(201 200 44):

  • Red: 201 / 255 = 0.788235294
  • Green: 200 / 255 = 0.784313725
  • Blue: 44 / 255 = 0.17254902

We will follow up by adding those values in this manner:

<feComponentTransfer color-interpolation-filters="sRGB">
  <feFuncR type="table" tableValues="RED1 RED2"></feFuncR>
  <feFuncG type="table" tableValues="GREEN1  GREEN2"></feFuncG>
  <feFuncB type="table" tableValues="BLUE1  BLUE2"></feFuncB>
</feComponentTransfer>

So our final result will be the following:

<feComponentTransfer color-interpolation-filters="sRGB">
  <feFuncR type="table" tableValues="0.878431373 0.788235294"></feFuncR>
  <feFuncG type="table" tableValues="0.141176471 0.784313725"></feFuncG>
  <feFuncB type="table" tableValues="0.435294118 0.17254902"></feFuncB>
</feComponentTransfer>

Here is our full code:

<svg viewBox="0 0 0 0" width="0" height="0" aria-hidden="true">
  <filter id="duotone-purple">
    <feColorMatrix type="matrix" values=".33 .33 .33 0 0
        .33 .33 .33 0 0
        .33 .33 .33 0 0
        0 0 0 1 0">
    </feColorMatrix>
    <feComponentTransfer color-interpolation-filters="sRGB">
      <feFuncR type="table" tableValues="0.878431373 0.788235294"></feFuncR>
      <feFuncG type="table" tableValues="0.141176471 0.784313725"></feFuncG>
      <feFuncB type="table" tableValues="0.435294118 0.17254902"></feFuncB>
    </feComponentTransfer>
  </filter>
 </svg>
The base image: with a duotone purple filter on it

Here is that codepen:

Here is an example with more filters:

Fun fact: This article actually just contains that one image with all the effects, cool huh?

Motion blur

Alright, so we’ve played with colors and duotones. Now, let’s take a look into something a bit different: blur. Blur can be used for a variety of effects, from subtle background softening to creating a sense of depth or movement. Let me tell you: SVG blur has a lot more control than the CSS blur() filter.

.background {
    filter: blur(5px);
}

The blur filter unfortunately only accepts one value, while in SVG filters you can set an X and Y value in which you want a blur to happen. Here is an example of that filter:

<svg width="0" height="0" viewBox="0 0 500 350" aria-hidden="true">
  <filter id="blurX">
    <feGaussianBlur stdDeviation="15 0"></feGaussianBlur>
  </filter>
</svg>

Notice the stdDeviation="15 0" on our GaussianBlur filter effect:

The first value (15) controls the blur radius in the x-axis, while the second value (0) sets the vertical or y–axis blur to 0.

There isn’t that much more to say about this effect, but I did create a demo to illustrate the difference between the two types of blur:

At the end of the article, I have a little something that has the vertical blur as well, stay tuned ;)

Extra info on the SVG horizontal blur demo

I created a bunch of filters in this demo and targeted the background, road, and car.

With a tiny bit of JS, I made those filters change their value, each and every one of those has a different maximum, which I calculated.

I’m also using a CSS variable that sets the animation speed and am using :has() to toggle between the radio buttons (because I’m lazy, do avoid many :has() checks on the root though, as it could cause performance issues, but for this demo, it’s fine…)

I don’t mind giving a complete rundown of this demo if people would be interested. Just give a little shout-out. But to keep this article from getting too bloated, let’s quickly move on.

Noise

Let’s hear some noise for svg!!!
Ok… dad jokes… sorry.

Noise is one of the things that can be created with <feTurbulence>, but it’s just the tip of the iceberg as this filter effect can create so much… From subtle textures to dramatic effects. To create a noise filter, you can use something like this:

<svg width="0" height="0" viewBox="0 0 500 350" aria-hidden="true">
  <filter id="noiseFilter">
    <feTurbulence
       type="fractalNoise"
       baseFrequency="0.85"
       numOctaves="3"
       stitchTiles="stitch" />
  </filter>
</svg>

There are a few attributes inside of this turbulence filter, here is the short explanation of them:

  • type="fractalNoise": specifies the type of noise to generate. fractalNoise creates a more natural, organic-looking noise.
  • baseFrequency="0.85": is the frequency of the noise. Higher values result in smaller, more detailed noise, while lower values create larger, more spread-out noise. Play around with this value to create the desired effect
  • numOctaves="3": determines the number of octaves (dare I say layers?) of noise to combine. More octaves create a more detailed and complex noise pattern
  • stitchTiles="stitch": this attempts to create seamless transitions between the tiles of noise, creating a beautiful repeating pattern

Since I recently started to brush up my drawing skills with a pencil, I created a hamster with CSS based on a drawing and added some noise to it:

This is pretty much all you need to know when it comes to noise, but there are SVG wizards that create amazing stuff with turbulence. And I am far off an SVG wizard. Still, a bit of noise does come in handy from time to time.

Much more…

There are a lot more things we can do with SVG. I brushed up an old demo I created five years ago (that was kept on a USB stick… can you imagine?) to show some of the other effects you could create with SVG. But as I stated before, I wanted to keep this article introductory and I think my current knowledge just isn’t enough to give an accurate description on how to use displacement maps or <feComposite> which combines filter effects into one, as the examples below:

I also spotted this cool collection of SVG text effects on CodePen.

If you want to learn more about displacement maps check this Smashing Magazine article: A Deep Dive Into The Wonderful World Of SVG Displacement Filtering

In conclusion…

So, I wrote this article not as a person who knows all things SVG but as someone who suddenly remembered that in this cool era of new and awesome CSS capabilities, we still have room for some solid SVG tinkering. I don’t use SVG filters on a day-to-day basis, but it’s a shame to lose some of those skills while they can still be very relevant today. It probably depends on the kind of work you do. But hey, this is just a reminder that when you stumble upon a certain effect that would be cool for a client and you have no idea how to achieve it, think SVG, it might just be the solution.

Happy tinkering!

Bonus: hamster parachute

I was creating my demo with the car and thought about doing something with a parachute for vertical motion blur. Then our 6-year-old daughter came into the room and showed her best project management skills. Besides criticism on some of my other demo here were the key asks:

  • Can you make the parachute go up as well?
  • Can you hang something on the parachute?

And so this happened:

 in  html , css