{"id":9094,"date":"2026-04-09T09:00:06","date_gmt":"2026-04-09T14:00:06","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=9094"},"modified":"2026-04-09T09:00:07","modified_gmt":"2026-04-09T14:00:07","slug":"svg-filters-guide-getting-started-with-the-basics","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/svg-filters-guide-getting-started-with-the-basics\/","title":{"rendered":"SVG Filters Guide: Getting Started with the Basics"},"content":{"rendered":"\n<p>Visual effects like those produced by SVG <code>&lt;filter&gt;<\/code>s used to live in the <em>\u201chere be dragons\u201d<\/em> zone for me. I&#8217;m more of a tech, not an artist, so I usually steer clear of anything designery like UI, layout, typography, etc. My strength lies in building compact, logical solutions, not creating stunning aesthetics.<\/p>\n\n\n\n<p>That is, until I accidentally fell into it. A problem popped up, and I thought SVG filters might do the trick. One thing led to another, and deeper and deeper down the rabbit hole I went. Luckily, there&#8217;s mathematics involved, which, up to a certain point at least, makes sense to me.<\/p>\n\n\n\n<p>Since I&#8217;ve started posting SVG filter demos, people often ask me how to get started in this area. While I firmly believe the best way is to <em>just start using SVG filters<\/em>, I&#8217;d also like to provide this starter&#8217;s guide, the stuff I wish I had read while getting into all this.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"introduction-to-svg-filters\">Introduction to SVG filters<\/h2>\n\n\n\n<p>All SVG filters must live inside an <code>&lt;svg&gt;<\/code> element. If we use that element <em>only<\/em> for defining filters (and not for rendering graphics as well), then it is functionally the same as a <code>&lt;style&gt;<\/code> element.<\/p>\n\n\n\n<p>So we set its dimensions to 0 and hide it from screen readers.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">svg<\/span> <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">'0'<\/span> <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">'0'<\/span> <span class=\"hljs-attr\">aria-hidden<\/span>=<span class=\"hljs-string\">'true'<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">svg<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In CSS, we take it out of the document flow so it doesn&#8217;t affect the layout.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">svg<\/span><span class=\"hljs-selector-attr\">&#91;aria-hidden=<span class=\"hljs-string\">'true'<\/span>]<\/span><span class=\"hljs-selector-attr\">&#91;height=<span class=\"hljs-string\">'0'<\/span>]<\/span> { <span class=\"hljs-attribute\">position<\/span>: fixed }<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>We do this because collapsing its dimensions to zero via <code>width<\/code> and <code>height<\/code> attributes may not be enough.<\/p>\n\n\n\n<p>Some scenarios may require, for example, setting <code>display: grid<\/code> on the parent of the <code>&lt;svg&gt;<\/code> element. In such a case, the <code>&lt;svg&gt;<\/code> element takes up a grid cell by default. This is less than ideal, but setting <code>position: fixed<\/code> (or any kind of positioning that removes the element from the document flow) on our <code>&lt;svg&gt;<\/code> solves the problem.<\/p>\n\n\n\n<p>We may now drop any number of <code>&lt;filter&gt;<\/code> elements inside this <code>&lt;svg&gt;<\/code> and reference them from the CSS.<\/p>\n\n\n\n<p>Every <code>&lt;filter&gt;<\/code> element needs to have an <code>id<\/code> attribute set.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">filter<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"my-filter\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">filter<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This is used to apply the <code>filter<\/code> on an element that becomes the filter input:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.my-filtered-elem<\/span> { <span class=\"hljs-attribute\">filter<\/span>: <span class=\"hljs-built_in\">url<\/span>(#my-filter) }<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>SVG filters may be chained just like CSS ones:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.my-filtered-elem<\/span> { <span class=\"hljs-attribute\">filter<\/span>: <span class=\"hljs-built_in\">url<\/span>(#my-<span class=\"hljs-number\">1s<\/span>t-filter) <span class=\"hljs-built_in\">url<\/span>(#my-<span class=\"hljs-number\">2<\/span>nd-filter) }<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>They may also be chained with CSS ones:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.my-filtered-elem<\/span> { <span class=\"hljs-attribute\">filter<\/span>: <span class=\"hljs-built_in\">url<\/span>(#my-<span class=\"hljs-number\">1s<\/span>t-filter) <span class=\"hljs-built_in\">blur<\/span>(.<span class=\"hljs-number\">5rem<\/span>) <span class=\"hljs-built_in\">url<\/span>(#my-<span class=\"hljs-number\">2<\/span>nd-filter) }<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>All SVG filters doing something with the RGB channels need to have the <code>color-interpolation-filters<\/code> attribute explicitly set in order to produce consistent results across browsers. This is because the default is inconsistent across browsers. Safari uses the <code>sRGB<\/code> value, while Chrome and Firefox use the <code>linearRGB<\/code> value.<\/p>\n\n\n\n<p><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/SVG\/Attribute\/color-interpolation-filters#usage_notes\">In theory<\/a>, the default is <code>linearRGB<\/code>, so this is a bug in Safari. In practice, what we need in order for our computations to work correctly is <code>sRGB<\/code>. We&#8217;re not going any further into this because I don&#8217;t fully get it myself, but for anyone curious, this is the only <a href=\"https:\/\/discuss.pixls.us\/t\/what-does-linear-rgb-mean\/16584\">explanation<\/a> I&#8217;ve ever read that ever made any sense at all to me. In any case, just know that this is an attribute that almost always needs to be set, together with the <code>id<\/code> attribute.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">svg<\/span> <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">'0'<\/span> <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">'0'<\/span> <span class=\"hljs-attr\">aria-hidden<\/span>=<span class=\"hljs-string\">'true'<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">filter<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">'my-filter'<\/span> <span class=\"hljs-attr\">color-interpolation-filters<\/span>=<span class=\"hljs-string\">'sRGB'<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">filter<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">svg<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>For anyone just starting out and unsure about whether it&#8217;s really needed, it&#8217;s probably best to add it just to be on the safe side. And remember, it&#8217;s best to add it <a href=\"https:\/\/stackoverflow.com\/questions\/56029986\/svg-filter-different-colouring-depending-on-browser\">on the <code>filter<\/code> element itself<\/a>, not on the individual elements inside it.<\/p>\n\n\n\n<p>The <code>&lt;filter&gt;<\/code> element may also get attributes specifying the filter region, which is the area the filter effect is clipped to. Four attributes are used to define the filter region. First, we have the <code>x<\/code> and <code>y<\/code> attributes that give us the top-left corner coordinates of the filter region (where it starts) relative to the filter input&#8217;s bounding box. Then we have <code>width<\/code> and <code>height<\/code> attributes, which are the filter region dimensions.<\/p>\n\n\n\n<p>If we don&#8217;t specify any of these attributes, the browser default is a filter region whose dimensions are <code>120%<\/code> of the filter input&#8217;s bounding box. The extra <code>20%<\/code> gives room for effects like blur or drop\u2011shadow, which often spill outside the bounding box of the filtered element.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"591\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/559669249-f5725d49-cd6c-4471-ae05-67931fd46ecf.png?resize=1024%2C591&#038;ssl=1\" alt=\"Illustration. Shows three situations on three columns. On the first column, there's the initial element that will get a filter applied. Its bounding box boundary is marked by a dashed red line and there are guides highlighting its width and height. On the second column, there's the element with a blur filter applied. Its bounding box remains the same, again highlighted by a dashed red line, but we can see how the blur filter has produced an effect that now gives us non-transparent pixels outside this bounding box. The default filter region, highlighted by a dashed blue line is enough to contain them though. The guides highlighting the filter region dimensions also note that these dimensions are 120% of those of the filter input's bounding box. On the third column, we have a similar situation, this time produced by a drop-shadow filter instead of a blur one.\" class=\"wp-image-9107\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/559669249-f5725d49-cd6c-4471-ae05-67931fd46ecf.png?resize=1024%2C591&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/559669249-f5725d49-cd6c-4471-ae05-67931fd46ecf.png?resize=300%2C173&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/559669249-f5725d49-cd6c-4471-ae05-67931fd46ecf.png?resize=768%2C443&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/559669249-f5725d49-cd6c-4471-ae05-67931fd46ecf.png?w=1296&amp;ssl=1 1296w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><figcaption class=\"wp-element-caption\">why the filter region needs to be bigger than the filter input&#8217;s bounding box<\/figcaption><\/figure>\n\n\n\n<p>Also by default, this <code>120%<\/code> region is positioned symmetrically around the bounding box, so it starts at <code>-10%<\/code> along both axes. This means the filter region goes from <code>-10%<\/code> to <code>110%<\/code> of the input&#8217;s dimensions along both the <em>x<\/em> and <em>y<\/em> axes, so we have a <code>10%<\/code> buffer outside each of the four edges.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"771\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/562080818-ae3a9b93-d126-412f-afca-3c3342a93cc7.png?resize=1024%2C771&#038;ssl=1\" alt=\"Diagram illustrating the concept of a filter region relative to an element's bounding box in a xOy coordinate system. On the diagram, we have the filter input, a coordinate system and the filter region. The filter input is represented by a rectangle with diagonal yellow and white stripes, outlined by a dashed red line. This represents the element the filter is being applied to. The coordinate system is made up of x and y axes originating at the top-left corner of the filter input. The filter region is represented by a larger rectangle surrounding the filter input one, indicated by a dashed blue line. This rectangle starts at 10% of the filter input's width to the left of the y axis (in the negative direction of the x axis) and at 10% of the filter input's height above the x axis (in the negative direction of the y axis). Its dimensions are 120% of those of the filter input.\" class=\"wp-image-9108\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/562080818-ae3a9b93-d126-412f-afca-3c3342a93cc7.png?resize=1024%2C771&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/562080818-ae3a9b93-d126-412f-afca-3c3342a93cc7.png?resize=300%2C226&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/562080818-ae3a9b93-d126-412f-afca-3c3342a93cc7.png?resize=768%2C578&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/562080818-ae3a9b93-d126-412f-afca-3c3342a93cc7.png?w=1296&amp;ssl=1 1296w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><figcaption class=\"wp-element-caption\">the default filter region<\/figcaption><\/figure>\n\n\n\n<p>Just like the values inside some CSS filters (for example <code>saturate()<\/code> or <code>opacity()<\/code>), these attributes can be set to either percentages or decimal values &#8211; <code>width='200%<\/code> and <code>width='2<\/code> produce the exact same result. So do <code>y='-50%<\/code> and <code>y='-.5<\/code>.<\/p>\n\n\n\n<p>There are two common reasons why we may need need to override the default filter region:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The effect spills outside the default bounds. Effects such as extrusion, displacement, blur or drop\u2011shadow may paint pixels beyond the default filter region. Without changing the default filter region, some of the outer pixels get clipped, giving us an awkward-looking result. In this case, we should expand the region to fully contain the effect. However, we should avoid expanding it too much, as that comes with a performance cost.<\/li>\n\n\n\n<li>We want to confine the effect to a smaller region. For example, when working with noise and\/ or displacement like in the case of <a href=\"https:\/\/frontendmasters.com\/blog\/grainy-gradients\/\">grainy gradients<\/a>, we don&#8217;t want the effect to spill outside of the element we apply it on. Ensuring the filter region starts from the top left corner of our element (<code>x<\/code> and <code>y<\/code> are both <code>0<\/code>) and is restricted to its area (both the <code>width<\/code> and the <code>height<\/code> are <code>1<\/code>) prevents the effect from leaking outside.<\/li>\n<\/ul>\n\n\n\n<p>Otherwise, in cases where the defaults are sufficient, we can safely omit these region-defining attributes altogether and keep our markup clean.<\/p>\n\n\n\n<p>So far, we&#8217;ve only talked about <em>relative<\/em> values for the filter region position (specified by the <code>x<\/code> and <code>y<\/code> attributes) and dimensions (specified by the <code>width<\/code> and <code>height<\/code> attributes).<\/p>\n\n\n\n<p>However, we may switch to <em>absolute<\/em> pixel values by changing the value of the <code>filterUnits<\/code> attribute from the <code>objectBoundingBox<\/code> default to <code>userSpaceOnUse<\/code>. In theory.<\/p>\n\n\n\n<p>In practice, I don&#8217;t recall ever doing that given that we normally want things to be responsive on the web and a fixed pixel size filter region wouldn&#8217;t scale with the element the filter is applied on. This would lead to the filter output getting cropped once its responsive input grows above a certain fixed pixel size.<\/p>\n\n\n\n<p>Still, I&#8217;ve made an interactive demo that allows playing with the attributes defining the filter region. When an attribute is greyed out and crossed off, it means it&#8217;s using its default value, so we could safely omit it. But at the same time, for the purpose of this interactive demo, it&#8217;s still present there, as clicking it brings up controls that allow changing its value.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_RNGRVmV\/e6f4f24ef82643f53936eece634aacbc\" src=\"\/\/codepen.io\/anon\/embed\/RNGRVmV\/e6f4f24ef82643f53936eece634aacbc?height=650&amp;theme-id=1&amp;slug-hash=RNGRVmV\/e6f4f24ef82643f53936eece634aacbc&amp;default-tab=result\" height=\"650\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed RNGRVmV\/e6f4f24ef82643f53936eece634aacbc\" title=\"CodePen Embed RNGRVmV\/e6f4f24ef82643f53936eece634aacbc\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>The content of the <code>&lt;filter&gt;<\/code> is intentionally very simple: it just fills the entire filter region with a bright green. The how behind is outside the scope of this introduction to SVG filters, so we&#8217;ll be unpacking it another time.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"inside-the-filter-element-primitives\">Inside the <code>&lt;filter&gt;<\/code> element: primitives!<\/h2>\n\n\n\n<p>Inside the <code>&lt;filter&gt;<\/code> element is where we place the filter primitives &#8211; these are the elements that actually do the work when it comes to creating cool effects.<\/p>\n\n\n\n<p>They are processed in the order they appear, so the output of one primitive is generally used as an input of the next by default. So if we want to see the result only up to a particular stage, we can can simply comment out or temporarily delete the primitives after that.<\/p>\n\n\n\n<p>There are about twenty filter primitives, all prefixed with &#8220;fe&#8221;, which stands for &#8220;<em><strong>f<\/strong><\/em>ilter <em><strong>e<\/strong><\/em>ffect&#8221;. Not &#8220;fi&#8221;, which would stand for &#8220;filter&#8221;. Or &#8220;Fifi&#8221;.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"607\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/560779354-79c1aead-21d7-4c3d-8a31-5ff4727d8f79-1.png?resize=1024%2C607&#038;ssl=1\" alt=\"Screenshot. Shows the same image of Fifi the dog eight times, each time with a different SVG filter applied. We duotone, pixelate this image, turn it into a 3D calendar sheet, slice it diagonally to then offset the slices, bend it, give it a ripped poster effect, create a split text effect over Fifi's shape in the image or add an embossing\/engraving effect.\" class=\"wp-image-9123\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/560779354-79c1aead-21d7-4c3d-8a31-5ff4727d8f79-1.png?resize=1024%2C607&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/560779354-79c1aead-21d7-4c3d-8a31-5ff4727d8f79-1.png?resize=300%2C178&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/560779354-79c1aead-21d7-4c3d-8a31-5ff4727d8f79-1.png?resize=768%2C455&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/560779354-79c1aead-21d7-4c3d-8a31-5ff4727d8f79-1.png?w=1296&amp;ssl=1 1296w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><figcaption class=\"wp-element-caption\">Fifi didn&#8217;t contribute to the naming of anything in the SVG spec&#8230; but an image of Fifi may get filter effects applied!<\/figcaption><\/figure>\n\n\n\n<p>Every filter primitive produces an output, which may be given a name saved in its <code>result<\/code> attribute.<\/p>\n\n\n\n<p>A primitive may take zero, one or two inputs, which we may set via the input attributes <code>in<\/code> (top layer, sometimes referred to as source layer) and <code>in2<\/code> (bottom layer, sometimes referred to as destination layer).<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"261\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/562308674-70f0b576-4c93-4424-a8c4-b44a3aaa0ee1-2.png?resize=1024%2C261&#038;ssl=1\" alt=\"Illustration. Shows primitives as black boxes. Each has an output. The one on the left has no inputs, the one in the middle has one input and the one on the right has two inputs.\" class=\"wp-image-9125\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/562308674-70f0b576-4c93-4424-a8c4-b44a3aaa0ee1-2.png?resize=1024%2C261&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/562308674-70f0b576-4c93-4424-a8c4-b44a3aaa0ee1-2.png?resize=300%2C76&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/562308674-70f0b576-4c93-4424-a8c4-b44a3aaa0ee1-2.png?resize=768%2C196&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/562308674-70f0b576-4c93-4424-a8c4-b44a3aaa0ee1-2.png?w=1296&amp;ssl=1 1296w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><figcaption class=\"wp-element-caption\">SVG filter primitive types, classified based on number of inputs<\/figcaption><\/figure>\n\n\n\n<p>If we don&#8217;t explicitly specify the input atributes for primitives that do take inputs, then these inputs are by default set to either the <code>result<\/code> of the previous filter primitive (whether we&#8217;ve set the <code>result<\/code> attribute on the previous primitive or not, that&#8217;s irrelevant, it still produced an output), or to the filter input (in which case we use the <code>SourceGraphic<\/code> keyword) for the very first primitive, since in that case there is no previous primitive.<\/p>\n\n\n\n<p>In addition to setting primitive inputs to the <code>result<\/code> of an earlier one or to the filter input (<code>SourceGraphic<\/code>) keyword, we may also set them to the filter input&#8217;s alpha map (in which case we use the <code>SourceAlpha<\/code> keyword).<\/p>\n\n\n\n<p>Many tutorials and examples out there seem to imply we need to set the <code>result<\/code>, <code>in<\/code>, <code>in2<\/code> or <code>id<\/code> attributes on every primitive inside the SVG <code>filter<\/code>. We <em><strong>don&#8217;t<\/strong><\/em>.<\/p>\n\n\n\n<p>We only need to set the <code>result<\/code> attribute when we want to use that primitive\u2019s output later in the <code>filter<\/code> and not just for the immediate next primitive.<\/p>\n\n\n\n<p>For example, if we only need the output of the first primitive as an input for the second one, then the first primitive does not need a <code>result<\/code> attribute. However, if we also need to use the output of the first primitive seven primitives later, then the first primitive needs a <code>result<\/code> attribute we can reference later in the <code>filter<\/code>.<\/p>\n\n\n\n<p>Similarly, we only need to set the <code>in<\/code> or <code>in2<\/code> attributes if they require a value different from the default.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"521\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/562309068-da1516f5-d7ad-4b28-97cb-17f1dc5380f5-1.png?resize=1024%2C521&#038;ssl=1\" alt=\"Illustration of a filter with mulrile primitives as a flow chart. The first primitive has no input and its output only goes into the very next single input primitive, so there's no result attribute explicitly set on it. The second primitive only has one input which is the output of the previous primitive, so its in attribute isn't set. Its output only goes into both inputs of the very next primitive, so there's no result attribute explicitly set on it either. The third primitive has two inputs, both of which are the output of the previous one, so there is no in or in2 attribute set here either. Its output is saved as &quot;three&quot; in the result attribute. The fourth primitive has no input and its output only goes into one of the two inputs of the very next primitive, so there's no result attribute explicitly set on it. The fifth primitive has two inputs, the first of which is set to the output of the third primitive, so the in attribute is set to &quot;three&quot;; the second input is the output of the primitive right before, so the in2 attribute isn't set explicitly.\" class=\"wp-image-9115\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/562309068-da1516f5-d7ad-4b28-97cb-17f1dc5380f5-1.png?resize=1024%2C521&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/562309068-da1516f5-d7ad-4b28-97cb-17f1dc5380f5-1.png?resize=300%2C153&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/562309068-da1516f5-d7ad-4b28-97cb-17f1dc5380f5-1.png?resize=768%2C391&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/562309068-da1516f5-d7ad-4b28-97cb-17f1dc5380f5-1.png?w=1296&amp;ssl=1 1296w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><figcaption class=\"wp-element-caption\">illustrating a multi-primitive SVG <code>filter<\/code> structure<\/figcaption><\/figure>\n\n\n\n<p>As for the <code>id<\/code> attribute, the only time it makes sense to set it on a primitive is when we plan on manipulating that primitive from the JS.<\/p>\n\n\n\n<p>A filter primitive may be limited to a rectangle called a primitive subregion. This is defined by the coordinates of its top left corner (set via the <code>x<\/code> and <code>y<\/code> attributes) and its dimensions (set via the <code>width<\/code> and <code>height<\/code> attributes).<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"749\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/562447292-be3e22ea-f8e7-45e3-a432-21b6034e71f1-1.png?resize=1024%2C749&#038;ssl=1\" alt=\"A similar diagram to the SVG filter region one, this time also adding a primitive subregion.\" class=\"wp-image-9116\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/562447292-be3e22ea-f8e7-45e3-a432-21b6034e71f1-1.png?resize=1024%2C749&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/562447292-be3e22ea-f8e7-45e3-a432-21b6034e71f1-1.png?resize=300%2C219&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/562447292-be3e22ea-f8e7-45e3-a432-21b6034e71f1-1.png?resize=768%2C562&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/562447292-be3e22ea-f8e7-45e3-a432-21b6034e71f1-1.png?w=1296&amp;ssl=1 1296w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><figcaption class=\"wp-element-caption\">the result of an SVG filter that stacks a blurred version of the input, clipped to a given subregion, on top of a desaturated version of the same input<\/figcaption><\/figure>\n\n\n\n<p>Length values (offsets or sizes like those defining filter subregions) for all primitives inside an SVG <code>&lt;filter&gt;<\/code> element may be either <em>absolute<\/em> pixel values or <em>relative<\/em> to the filter input bounding box. By default, they are all absolute pixel values, not responsive. However, <em>if<\/em> we change the <code>primitiveUnits<\/code> attribute of the <code>&lt;filter&gt;<\/code> element to <code>objectBoundingBox<\/code>, then all length values of primitive attributes become relative to the filter input&#8217;s bounding box.<\/p>\n\n\n\n<p>There are two things that are important to remember here.<\/p>\n\n\n\n<p>First, the <code>primitiveUnits<\/code> attribute is only set on the <code>&lt;filter&gt;<\/code> element, not on the primitives themselves, and it affects <em>all<\/em> the primitives inside a <code>filter<\/code>.<\/p>\n\n\n\n<p>If we need a primitive with relative units followed by one with absolute units, we need to put them into separate SVG <code>&lt;filter&gt;<\/code> elements and then chain the effects of those filters. In fact, needing different units is one of the two most common reasons for chaining filters, the other one being reusability: when we want the elements having the&nbsp;<code>filter<\/code>&nbsp;applied to get the same effect&nbsp;<em>only<\/em>&nbsp;up to a certain point.<\/p>\n\n\n\n<p>Second, the <code>primitiveUnits<\/code> attribute is different from the <code>filterUnits<\/code> attribute.<\/p>\n\n\n\n<p>The value of the <code>filterUnits<\/code> attribute affects the length-valued attributes set on the <code>&lt;filter&gt;<\/code> element itself (<code>x<\/code>, <code>y<\/code>, <code>width<\/code> and <code>height<\/code> defining the filter region), while the value of the <code>primitiveUnits<\/code> attribute affects the length-valued attributes set on the primitives inside the <code>&lt;filter&gt;<\/code> element.<\/p>\n\n\n\n<p>They also have different defaults. The default value for <code>filterUnits<\/code> is <code>objectBoundingBox<\/code>, while the default value for <code>primitiveUnits<\/code> is <code>userSpaceOnUse<\/code>.<\/p>\n\n\n\n<p>The <code>objectBoundingBox<\/code> and <code>userSpaceOnUse<\/code> are our only options and this feels limiting nowadays \u2014 we&#8217;re only given the choice between values relative to the dimensions of the filter input&#8217;s bounding box and fixed pixel values.<\/p>\n\n\n\n<p>A lot of times, it would be a lot more convenient for these values to be relative to the <code>font-size<\/code> of the element we apply the <code>filter<\/code> on. This <code>font-size<\/code> may in turn depend on the container or viewport dimensions via container query units or viewport units. While there are hackarounds for some particular cases, in many others, this remains a big limitation of SVG filters that we cannot overcome.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"key-takeaways\">Key takeaways<\/h2>\n\n\n\n<p>Here are the most important things to remember:<\/p>\n\n\n\n<ul class=\"wp-block-list ticss-7e25b2b7\">\n<li>SVG filters must live inside an <code>&lt;svg&gt;<\/code> element.<\/li>\n\n\n\n<li>If the <code>&lt;svg&gt;<\/code> element only contains filters, then zero its dimensions, hide it from screen readers and take it out of the document flow.<\/li>\n\n\n\n<li>Inside the <code>&lt;filter&gt;<\/code> element, there are <code>fe<\/code>-prefixed elements (for example <code>&lt;feTile&gt;<\/code> or <code>&lt;feBlend&gt;<\/code>) called primitives. These are responsible for the magic.<\/li>\n\n\n\n<li>The <code>&lt;filter&gt;<\/code> element needs to have an <code>id<\/code> so we can reference it from the CSS.<\/li>\n\n\n\n<li>If the filter is doing something with the RGB channels, explicitly set the <code>color-interpolation-filters<\/code> attribute to <code>sRGB<\/code>.<\/li>\n\n\n\n<li>Other attributes we may want to set on the <code>&lt;filter&gt;<\/code> element:\n<ul class=\"wp-block-list\">\n<li><code>primitiveUnits<\/code> to <code>objectBoundingBox<\/code> if we want the length values specified by the primitives inside the <code>&lt;filter&gt;<\/code> to be relative to the filter input&#8217;s bounding box and not absolute pixel values.<\/li>\n\n\n\n<li>One or more of the <code>x<\/code>, <code>y<\/code>, <code>width<\/code> and <code>height<\/code> attributes if we want to define a filter region different from the default one that extends <code>10%<\/code> in every direction outside the filter input&#8217;s bounding box; these are relative to the dimensions of the filter input&#8217;s bounding box by default.<\/li>\n\n\n\n<li>In the rare case we want our filter effect to be restricted to a box whose position and dimensions are given in pixels, we may set <code>filterUnits<\/code> to <code>objectSpaceOnUse<\/code>.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>The primitives inside the <code>&lt;filter&gt;<\/code> element may take the following generic primitive attributes:\n<ul class=\"wp-block-list\">\n<li><code>x<\/code>, <code>y<\/code>, <code>width<\/code> and <code>height<\/code> specifying the primitive subregion via its top left corner coordinates and dimensions; whether these and other length-valued primitive attributes are absolute pixel lengths or relative to the filter input&#8217;s dimensions is determined by the value of the <code>primitiveUnits<\/code> attribute of their <code>&lt;filter&gt;<\/code> parent.<\/li>\n\n\n\n<li><code>in<\/code> and <code>in2<\/code> specifying the primitive inputs; these may reference the output of another primitive, the filter input (<code>SourceGraphic<\/code>) or the filter input&#8217;s alpha map (<code>SourceAlpha<\/code>).<\/li>\n\n\n\n<li><code>result<\/code>, whose value can be later used to reference the output of this primitive as an input for another primitive at a later time.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Let&#8217;s take a look at what SVG filters are and the basics of how they work. <\/p>\n","protected":false},"author":32,"featured_media":9132,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[7,262,91],"class_list":["post-9094","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-css","tag-filter","tag-svg"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/svg-filters.jpg?fit=2000%2C1200&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/9094","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/users\/32"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=9094"}],"version-history":[{"count":22,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/9094\/revisions"}],"predecessor-version":[{"id":9285,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/9094\/revisions\/9285"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/9132"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=9094"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=9094"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=9094"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}