Free CSS filter pipeline builder
CSS filter generator that respects the order
A CSS filter is a pipeline, not a set of checkboxes: every function is handed the previous one’s output, so blur(4px) brightness(160%) and brightness(160%) blur(4px) are two different pictures. This builder is free and needs no signup — stack up to 12 functions, move the rows, and see the chain rendered forwards and back to front at the same time. Four sample colors are pushed through both directions numerically, so the difference comes back as a ΔE rather than as a claim.
- 100% free
- No signup
- 10 filter functions
- Up to 12 per chain
- Order check in ΔE
photographic range — sky, sun, foliage
Chain — applied top to bottom
- 1saturate
Pushes channels away from luma; over ~180% saturated hues start clipping
- 2contrast
Scales each channel around 0.5, so it clips at both ends
- 3drop-shadow
Shadow of the alpha channel — the silhouette, not the box. No spread, no inset
CSS
filter: saturate(140%) contrast(115%) drop-shadow(0px 6px 10px rgba(15, 23, 42, 0.35));
3 of 12 functions · 1 geometric · 87 characters
Press Ctrl+V anywhere on the page to drop in a filter declaration to load it back into the chain — no need to click the field first.
Order check — the same chain run forwards and backwards
Four sample colors pushed through the color functions in your order and then in the reverse order, clamping after each step exactly as the spec requires. The one blur or drop-shadow in this chain moves pixels rather than recoloring them, so it sits out of the numbers below and shows up in the two previews instead.
| Sample | In your order | Reversed | ΔE76 |
|---|---|---|---|
| #e2725b | #ff6641 | #ff6641 | 0.00 |
| #3b82f6 | #1385ff | #1386ff | 0.20 |
| #22c55e | #00e33d | #00e33d | 0.00 |
| #f5f5f4 | #ffffff | #ffffff | 0.00 |
Largest difference ΔE 0.20 — under 1.0, so these particular functions happen to commute on these samples. Add a contrast or push a brightness past the clipping point and that stops being true.
drop-shadow reads alpha; box-shadow reads the box
The same four values — 0 6px 6px rgba(15, 23, 42, 0.45) — on the same transparent mark, once through the filter and once through the property.
The left mark carries the shadow on its own rectangular box, so the shadow is a rectangle even though the artwork is a ring and a triangle. The right one traces the alpha channel, including the hole in the middle.
Any filter creates a containing block
Both grey boxes hold an identically positioned position: absolute badge at top: 0; left: 0. The right box carries blur(0px), which changes nothing visually and everything positionally.
On the left the badge resolves against the dashed ancestor. On the right the filtered div became the containing block, so the badge snaps into its corner instead. Swap absolute for fixed and the same rule pins a modal to a card rather than to the viewport.
How to build a CSS filter chain
Three steps from an unfiltered element to a declaration whose order you can defend in review.
Choose what the filter will actually run on
The five subjects are not decoration — a chain tuned on the wrong one lies to you. The gradient scene carries a photographic range of sky, sun and foliage and is where brightness and contrast show their clipping. The transparent mark on a checkerboard is the only subject where drop-shadow has anything to trace. The UI card proves that filter runs on the whole subtree, text and button included, not just on a background. The eight-hue bar makes hue-rotate readable. Or load your own file, up to 5 MB, which is read off your disk by the browser and never sent anywhere.
Stack the functions, then drag them into the order you meant
Pick a function from the dropdown and press Add; duplicates are allowed, because two blurs at different points in a chain do different things. Each row gets the sliders that function actually takes — one value for the eight scalar functions, four plus a color for drop-shadow — and the arrows move a row earlier or later. The second preview shows the identical chain applied back to front, so the moment ordering starts to matter you can see it rather than be told about it.
Read the ΔE, then copy the declaration
Under the previews, four sample colors are pushed through the color functions in your order and then in reverse, clamping after every step the way the spec requires, and the two results are compared with a CIE76 ΔE. Anything above 1.0 means the order is load-bearing and a colleague who reshuffles the chain in review will change the output. Copy CSS puts the whole filter declaration on your clipboard; going the other way, paste a filter you already have and the rows rebuild from it.
Technical specifications
| Filter functions | The 10 shorthands of CSS Filter Effects 1 — blur, brightness, contrast, drop-shadow, grayscale, hue-rotate, invert, opacity, saturate, sepia. A url() reference points at an SVG filter, so it is reported back rather than put on a slider |
|---|---|
| Chain length | Up to 12 functions, duplicates allowed; row 1 runs first and hands its result to row 2, and the arrows on each row change that order |
| Color space | Filter Effects 1 pins the shorthand functions to sRGB, while the SVG primitives they are defined in terms of default to linearRGB — the reason a hand-written feColorMatrix rarely matches saturate() exactly |
| Value ranges | blur 0-40px, brightness / contrast / saturate 0-300%, grayscale / sepia / invert / opacity 0-100%, hue-rotate −180° to +180°, drop-shadow offsets ±40px with 0-60px of blur |
| Clamping | Each function's output is clamped to 0-1 per channel before the next one reads it, which is what makes a chain that clips irreversible and the order load-bearing |
| Order check | 4 sample colors run forwards and backwards through the color functions and compared with a CIE76 ΔE; blur and drop-shadow move pixels rather than recolor them, so they sit out of that number and show up in the two previews instead |
| Preview subjects | 5 — a CSS-drawn gradient scene, a transparent SVG mark on a checkerboard, a UI card, an eight-hue bar, and your own image up to 5 MB, which the browser reads from disk into an object URL and never sends anywhere |
| CSS input | Paste any filter or -webkit-filter declaration: px, rem and em for blur, deg, rad, grad and turn for hue-rotate, and bare numbers as ratios, so brightness(1.2) loads as 120% |
Frequently asked questions
Does the order of CSS filter functions matter?
Yes, and usually by more than people expect, because filter is a pipeline in which each function receives the previous one's output. blur(4px) brightness(160%) brightens an image that has already been softened, so the bright halo bleeds outward past the element; brightness(160%) blur(4px) brightens first and then smears highlights that have already clipped at white, which reads flatter. Even two functions whose matrices would commute stop commuting once a channel clips, because every function's result is clamped to the 0-1 range before the next one reads it and a channel pinned at 1 has lost the headroom the next function needed. The order check under the tool runs your chain both ways over four sample colors and reports the CIE76 ΔE between them.
Why did my position: fixed element stop being fixed?
Because an ancestor has a filter on it. Any filter value other than none makes that element a containing block for its absolutely and fixed-positioned descendants, so a fixed modal or a sticky header inside it starts resolving against that element instead of the viewport. What makes this hard to find is that the culprit is often invisible: filter: blur(0px) or filter: grayscale(0), added to nudge an element onto the GPU, changes not one pixel and still moves the containing block. The panel at the bottom of the tool shows the same badge landing in two different places in two boxes that differ only by blur(0px).
What is the difference between filter: drop-shadow and box-shadow?
drop-shadow reads the alpha channel of everything the element paints; box-shadow reads the element's border box. On a transparent PNG, an SVG mark or a clipped shape, drop-shadow traces the artwork itself — holes included — while box-shadow puts a rectangle behind it. The costs are real: drop-shadow takes no spread, no inset and no comma-separated list of layers, and the renderer has to rasterize the whole subtree before it can read the alpha. For a plain rectangular card, box-shadow is both cheaper and more capable, and it does not drag in the containing-block behavior above.
Why does hue-rotate(180deg) not give me the complementary color?
Because hue-rotate is a fixed 3×3 matrix approximation rather than a rotation in a hue-preserving color space. The matrix in the spec is built out of the Rec. 709 luma constants 0.213, 0.715 and 0.072, and it holds neither saturation nor lightness steady, so a saturated orange rotated 180° arrives at a muted blue rather than at its true complement, with any channel that leaves the 0-1 range clipped on the way. Rotating twice by 180° does not reliably return the original color either. When you need an actual complement, derive it from the color, not from the filter.
Can I animate or transition a filter chain?
Only smoothly when both chains list the same functions in the same order. CSS interpolates the filter list item by item: if the shorter list is a prefix of the longer one, the missing entries are filled in with their identity values and the animation runs, but if the function at any position differs the property becomes non-interpolable and snaps from one value to the other. The practical rule is to write every keyframe with the full chain and use identity values — blur(0px), saturate(100%), hue-rotate(0deg) — as placeholders instead of omitting functions. Wide blurs also repaint on every frame, so animating one across a large element is where the frame budget goes.
Why does blur() fade the edges of my element?
Because the blur averages in the transparent area outside the element, so a full-bleed background image blurred in place comes out with soft, washed-out borders. The element's painted region stops at its border box and there is simply nothing beyond it to sample. The fix is to make the blurred layer bigger than the part you show: scale it up by roughly the blur radius on every side, or inset the visible container, and clip the overflow on the parent. This is also why a blurred element visually spills past its own box — the blur is ink, and like any filter output it is not clipped to the border box unless something else clips it.
Should I be using filter or backdrop-filter?
filter changes the element and everything inside it; backdrop-filter changes whatever is painted behind it and leaves the element's own content sharp. If a card's label is going grey along with its background, the effect is on the wrong property. Frosted glass, where the panel stays crisp and the page under it softens, is always backdrop-filter and has its own page here. Screenshots, logos, thumbnails, disabled controls and any decorative imagery are the filter case, which is what this builder is for.
About the filter pipeline and the three things it does behind your back
The filter property takes an ordered list, and CSS Filter Effects 1 defines the ten shorthand functions by compiling each one down to an SVG filter primitive — a color matrix for grayscale, sepia, saturate and hue-rotate, a linear component transfer for brightness, contrast and invert, a Gaussian for blur. Two consequences follow that no slider label mentions. First, each primitive clamps its own result to the 0-1 range before the next one reads it, so once brightness(200%) has pinned a channel at white, the contrast() after it has nothing left to pull back; reordering the pair is not a cosmetic change, it is a different computation. Second, the shorthand functions are pinned to sRGB, while the SVG primitives they are written in terms of default to linearRGB. That single line in the spec is why a hand-written feColorMatrix copied from an SVG rarely matches saturate() on a mid grey, and why porting a filter between the two syntaxes needs a color-interpolation-filters: sRGB on the SVG side.
The second thing the property does quietly is change layout geometry. Any value other than none makes the element a stacking context and a containing block for its absolutely and fixed-positioned descendants — including filter: blur(0px), which alters no pixel whatsoever. A sticky header or a fixed modal that suddenly anchors to a card instead of the viewport is almost always this. The third is drop-shadow, which is the only function here that is not a per-pixel recolor: it reads the alpha channel of the rendered subtree, so it follows a silhouette where box-shadow would draw a rectangle. That distinction is what makes it the right shadow for a shape cut with the clip-path generator and the wrong one for a plain card, where box-shadow’s spread, inset and layer list all still work and the corners follow whatever the border radius generator produced.
Most filter generators ship one slider per function in a fixed alphabetical order and concatenate whatever is non-default. That design cannot express blur(6px) brightness(150%) blur(2px), it silently picks an order for you, and it gives you no way to load a filter that already exists in your codebase. This one treats the chain as the list it is: duplicates allowed, rows reorderable, a reversed preview beside the real one, and a parser that takes a pasted declaration apart again. It also keeps to its own ground — backdrop-filter, which blurs what is behind an element rather than the element itself, belongs to the glassmorphism generator. In a real component the filter is usually the last layer over something else: a color ramp from the gradient generator or its CSS gradient syntax reference, or a control assembled in the CSS button generator whose disabled state is nothing more than grayscale(100%) opacity(45%).
Where your image and your CSS are read
Every number on this page is worked out by JavaScript running in the tab you are reading it in. Nothing you type, paste or open is uploaded, logged or kept, which is also why the tools carry on working after you disconnect from the network.
The image subject is the only file this page touches. Choosing one hands the browser a local object URL, which the preview draws from directly — there is no upload step, no canvas readback and no copy kept once you pick a different file. The pasted declaration is parsed by a function on this page and thrown away as soon as the rows are built.