Filters & Effects

Blur, shadows, and color effects with SVG filters

Filter basics

An SVG <filter> is a composable image-processing pipeline. You define it once in <defs>, give it an id, and attach it to any element with filter="url(#id)". The browser renders the target element into an offscreen bitmap, passes that bitmap through each filter primitive in order, and composites the final result back into the page.

Primitives are the individual operations — blur, offset, color adjustment, compositing — and they are connected by naming their output with result and referencing that name in the next primitive's in attribute. Two special built-in names are always available: SourceGraphic (the original rendered element, full color) and SourceAlpha (just the alpha/transparency channel of the element, rendered black).

The filter also has a filter region — a bounding box that limits where filter output can appear. By default it extends 10% beyond each edge of the filtered element (x="-10%" y="-10%" width="120%" height="120%"). If a drop shadow or glow extends past that region, it will be clipped; expand x/y/width/height on the <filter> element to give it more room.

<!-- Minimal filter skeleton --> <defs> <filter id="my-filter" x="-20%" y="-20%" width="140%" height="140%"> <!-- primitives go here --> <feGaussianBlur in="SourceGraphic" stdDeviation="3" result="blurred" /> <!-- next primitive can use in="blurred" --> </filter> </defs> <rect filter="url(#my-filter)" ... />

Building a drop shadow from primitives

A drop shadow looks like a copy of the shape's silhouette, offset and blurred, placed beneath the original. Building it by hand from primitives reveals exactly how the filter pipeline works — and it teaches you the compositing model you need for every other advanced effect.

The recipe has three steps:

  1. feGaussianBlur on SourceAlpha — produces a soft-edged silhouette in black.
  2. feOffset — shifts the blurred silhouette to the desired shadow position.
  3. feMerge — composites the shadow layer under the original SourceGraphic so the shape appears on top of its shadow.

Drop shadow built from primitives

<svg viewBox="0 0 220 120" width="280" height="152" role="img" aria-label="An orange-to-red gradient rectangle with a soft drop shadow"> <defs> <linearGradient id="sf-grad" x1="0" y1="0" x2="1" y2="0"> <stop offset="0%" stop-color="#f0a500" /> <stop offset="100%" stop-color="#d6452c" /> </linearGradient> <filter id="drop-shadow-manual" x="-15%" y="-15%" width="140%" height="140%"> <!-- Step 1: blur the alpha channel to get a soft silhouette --> <feGaussianBlur in="SourceAlpha" stdDeviation="4" result="blur" /> <!-- Step 2: shift the blurred silhouette down and to the right --> <feOffset in="blur" dx="5" dy="5" result="shadow" /> <!-- Step 3: merge shadow under the original graphic --> <feMerge> <feMergeNode in="shadow" /> <feMergeNode in="SourceGraphic" /> </feMerge> </filter> </defs> <rect x="20" y="20" width="160" height="70" rx="8" fill="url(#sf-grad)" filter="url(#drop-shadow-manual)" /> </svg>

The feMerge order is critical: shadow comes first (so it is drawn first, behind everything), then SourceGraphic is drawn on top. Reversing the order would bury the shape under its own shadow. This explicit merge step is why the hand-built approach teaches the compositing model so well — it makes the layering order visible in code.

The <feDropShadow> shorthand

Because drop shadows are so common, SVG provides <feDropShadow> as a single primitive that collapses all three steps above. It accepts stdDeviation (blur amount), dx/dy (offset), flood-color (shadow color), and flood-opacity. The result is identical to the manual approach.

<svg viewBox="0 0 220 120" width="280" height="152" role="img" aria-label="The same gradient rectangle with a drop shadow using the feDropShadow shorthand"> <defs> <linearGradient id="sf-grad2" x1="0" y1="0" x2="1" y2="0"> <stop offset="0%" stop-color="#f0a500" /> <stop offset="100%" stop-color="#d6452c" /> </linearGradient> <filter id="drop-shadow-short" x="-15%" y="-15%" width="140%" height="140%"> <feDropShadow dx="5" dy="5" stdDeviation="4" flood-color="#333" flood-opacity="0.5" /> </filter> </defs> <rect x="20" y="20" width="160" height="70" rx="8" fill="url(#sf-grad2)" filter="url(#drop-shadow-short)" /> </svg>

Note: the CSS filter: drop-shadow(5px 5px 4px rgba(0,0,0,0.5)) function also produces a drop shadow (on any HTML element, not just SVG), but it uses the alpha channel of the entire element including its children — so on an SVG with a transparent background, the CSS version correctly shadows through cut-out areas. Both approaches produce visually similar results for solid shapes.

Glow & color effects

The same building blocks — blur, merge, color matrix — combine in different configurations to produce a wide range of effects. Glow is essentially a drop shadow without an offset: blur the source, merge the blurred copy under the original, and the soft halo of color appears to radiate outward.

Outer glow

For a glow effect, blur SourceGraphic (not SourceAlpha) so the halo inherits the shape's color. Then merge the blurred copy behind the sharp original. The result is a soft color halo that appears to emanate from the shape itself.

<svg viewBox="0 0 180 100" width="240" height="133" role="img" aria-label="A teal rectangle with a soft green outer glow"> <defs> <filter id="glow-effect" x="-30%" y="-30%" width="160%" height="160%"> <feGaussianBlur in="SourceGraphic" stdDeviation="6" result="blurred" /> <feMerge> <feMergeNode in="blurred" /> <feMergeNode in="SourceGraphic" /> </feMerge> </filter> </defs> <rect x="40" y="20" width="100" height="60" rx="10" fill="#0f766e" filter="url(#glow-effect)" /> </svg>

Color effects with feColorMatrix

feColorMatrix is a general-purpose color transformation primitive. It multiplies each pixel's RGBA channels by a 4×5 matrix, letting you shift hues, desaturate, increase contrast, or invert colors — all without touching the shape's original fill value.

The most useful shorthand types are:

  • type="saturate" values="0" — removes all color (grayscale).
  • type="saturate" values="3" — boosts saturation (vivid colors).
  • type="hueRotate" values="120" — rotates hues by 120°. Be careful here: rotating from an orange/red base by large amounts can land in blue/purple territory; use small rotations or values that stay within the allowed palette.

The demo below shows three shapes side by side: the original, a desaturated version, and a saturation-boosted version. All colors stay within the amber/orange/red/green/teal palette.

<svg viewBox="0 0 260 100" width="340" height="131" role="img" aria-label="Three circles showing original color, desaturated, and saturation-boosted versions"> <defs> <filter id="desaturate"> <feColorMatrix type="saturate" values="0" /> </filter> <filter id="supersaturate"> <feColorMatrix type="saturate" values="4" /> </filter> </defs> <!-- Original --> <circle cx="45" cy="50" r="36" fill="#e07b00" /> <!-- Desaturated --> <circle cx="130" cy="50" r="36" fill="#e07b00" filter="url(#desaturate)" /> <!-- Boosted saturation --> <circle cx="215" cy="50" r="36" fill="#e07b00" filter="url(#supersaturate)" /> </svg>

Left to right: the original orange, the fully desaturated gray, and the over-saturated version (nearly red-orange). feColorMatrix operates on each pixel independently — it does not know anything about the shape's structure — so it works equally well on gradients, patterns, images, and text.

Applying filters with CSS

SVG filters defined inside an inline <svg> block in the HTML document are reachable from CSS on any element in that document via filter: url(#id). This lets you apply SVG's powerful filter primitives to ordinary HTML elements — images, headings, buttons — not just SVG shapes.

CSS also provides its own shorthand filter functions that do not require a <filter> definition: blur(), brightness(), contrast(), grayscale(), drop-shadow(), and others. These are equivalent to simple single-primitive SVG filters and are easier to write for common cases.

CSS filter functions on SVG shapes

You can use the CSS filter property directly on SVG elements by applying a class or inline style. This is useful when the filter logic is simple enough to express as a function and you do not want to pollute <defs> with a filter element.

<svg viewBox="0 0 260 100" width="340" height="131" role="img" aria-label="Three shapes demonstrating CSS blur, brightness, and drop-shadow filters"> <!-- CSS blur --> <rect x="10" y="20" width="70" height="60" rx="6" fill="#1f8a4c" style="filter: blur(3px)" /> <!-- CSS brightness boost --> <rect x="100" y="20" width="70" height="60" rx="6" fill="#d6452c" style="filter: brightness(1.6)" /> <!-- CSS drop-shadow function --> <rect x="190" y="20" width="70" height="60" rx="6" fill="#0f766e" style="filter: drop-shadow(4px 4px 5px #333)" /> </svg>

Performance note

SVG filters — whether applied via the filter attribute or the CSS filter property — are rasterization operations. The browser renders the filtered element to an offscreen bitmap, processes it pixel by pixel, and composites the result. This means:

  • Filtered elements are promoted to their own compositor layer, which costs GPU memory.
  • Every time the element changes (animation, scroll, resize), the filter must re-execute — feGaussianBlur with a large stdDeviation is particularly expensive.
  • Applying filters to large areas (background blurs, full-screen effects) can drop frame rates on lower-end devices.
  • Prefer CSS box-shadow (composited by the browser without rasterization) over filter-based shadows for simple cases.
  • Use will-change: filter sparingly — it pre-promotes the element but also increases memory use.

For UI elements that animate, prefer CSS transitions on opacity and transform (which the compositor handles without rasterization) over animated filters. Reserve SVG filter effects for static illustrations or infrequently-updated elements where their expressive power justifies the cost.