Command Palette

Search for a command to run...

How CSS Filters Work: Every Filter Function, Why Order Matters, and the Layout Bugs They Cause

How CSS Filters Work: Every Filter Function, Why Order Matters, and the Layout Bugs They Cause

T
Toolz Team
|Sep 19, 2026|13 min read
Prefer Toolz.dev on Google

Part of the CSS & Design collection

CSS Filter Generator

Apply blur, brightness, contrast, grayscale, sepia, hue-rotate and more with live sliders and a preview, then copy production-ready CSS filter code.

Use the CSS Filter Generator

The CSS filter property looks like a styling shortcut: one line turns a photo black and white, blurs a background or warms a thumbnail, with no image editor involved. It is that. It also changes how the browser positions and layers the element it is applied to, which is why a filter added for a hover effect can quietly break a dropdown three components away.

This guide covers what each filter function does to pixel values, why the same functions in a different order produce a different image, how filters behave in layout and animation, and where they cost performance. It uses the free CSS filter generator to build the declarations, but every example is plain CSS you can paste anywhere.

TL;DR: The CSS filter property applies graphical effects such as blur, brightness, contrast, grayscale, sepia, saturate, invert, hue-rotate and opacity to an element and everything inside it, in the order the functions are written. Any value other than none also creates a stacking context and becomes the containing block for fixed and absolutely positioned children, which is the usual cause of "my modal is stuck inside a card". The Toolz CSS filter generator builds the declaration with sliders and a live preview, writes only the functions you changed, and runs client-side, free, no signup.

What is the CSS filter property?

The CSS filter property applies image-processing effects to an element before it is drawn on the page. The effect covers the element's whole rendering: its background, borders, text, images and child elements. It is defined in the W3C Filter Effects Module Level 1 and supported in all current browsers; the MDN reference for filter lists compatibility details.

A declaration is a space-separated list of filter functions:

img.hero {
  filter: brightness(105%) contrast(110%) saturate(130%) sepia(45%);
}

The functions run left to right. Each one takes the output of the previous one as its input, which is the key to understanding why order matters.

Filters do not modify the image file. The browser applies them each time it paints, so the original stays untouched and you can change or remove the effect with one line of CSS.

What does each filter function do?

Each function has an identity value that leaves the element unchanged. The Toolz generator uses that to keep output short: any function left at its identity value is omitted from the CSS.

Function What it does Identity Generator slider range
blur() Gaussian blur with the given radius 0px 0 to 20px
brightness() Multiplies each color channel 100% 0 to 200%
contrast() Pushes channels away from or toward mid-gray 100% 0 to 200%
saturate() Increases or decreases color intensity 100% 0 to 200%
grayscale() Blends toward a gray of equal luminance 0% 0 to 100%
sepia() Blends toward a warm brown tone 0% 0 to 100%
invert() Flips each channel toward its opposite 0% 0 to 100%
hue-rotate() Rotates colors around the color wheel 0deg 0 to 360deg
opacity() Makes the element more transparent 100% 0 to 100%

The specification allows values beyond some of these ranges, such as brightness(300%). The sliders cover the ranges that are useful for photo-style adjustments; you can type a larger value into your CSS by hand.

The spec also defines drop-shadow(), which the generator does not include. Unlike box-shadow, drop-shadow() follows the visible shape of transparent images such as PNG icons, rather than the element's rectangular box. For rectangular shadows, the box shadow guide covers the more flexible property.

How do the color filters change pixel values?

Most color filters are simple arithmetic on the red, green and blue channels, each treated as a number from 0 to 1. The Filter Effects specification defines each one as a formula or a 3 by 3 matrix. Worked through on a few colors, they become predictable.

brightness(150%) multiplies every channel by 1.5. A mid-gray of rgb(100, 100, 100) becomes rgb(150, 150, 150). Values that would go past 255 are clipped, which is why high brightness washes out highlights to pure white.

contrast(200%) applies (value - 0.5) × 2 + 0.5 to each channel. A dark gray at 0.25 (about 64) becomes (0.25 - 0.5) × 2 + 0.5 = 0, black. A light gray at 0.75 becomes 1.0, white. Everything spreads away from the middle.

grayscale(100%) replaces each pixel with a gray based on luminance, using weights of 0.2126 for red, 0.7152 for green and 0.0722 for blue. Pure red, rgb(255, 0, 0), becomes 0.2126 × 255 = about 54, so rgb(54, 54, 54). Pure green becomes a much lighter gray of about 182. That is why a grayscale conversion keeps the sense that green is brighter than red.

sepia(100%) mixes channels with warm-weighted coefficients. For red output the formula is 0.393R + 0.769G + 0.189B. White goes in as (1, 1, 1) and comes out as roughly (1.35, 1.20, 0.94), which clips to rgb(255, 255, 239): white becomes a faint cream.

These figures come from applying the specification's formulas directly. On screen, rounding and color management can move a result by a unit or two.

Why does the order of filter functions change the result?

Filters are applied in sequence, so each function works on the previous function's output. When a function removes information, such as grayscale removing color, anything that runs after it has less to work with.

Take pure blue, rgb(0, 0, 255), and apply grayscale and sepia at 100% in both orders.

filter: grayscale(100%) sepia(100%);

  1. Grayscale first: blue has luminance 0.0722, so the pixel becomes a dark gray of 0.0722 on every channel, about rgb(18, 18, 18).
  2. Sepia second: red becomes 0.0722 × (0.393 + 0.769 + 0.189) = 0.0975, green becomes 0.0722 × 1.203 = 0.0869, blue becomes 0.0722 × 0.937 = 0.0677.
  3. Result: about rgb(25, 22, 17), a very dark warm brown.

filter: sepia(100%) grayscale(100%);

  1. Sepia first: red becomes 0.189, green 0.168, blue 0.131, a dark brown.
  2. Grayscale second: luminance is 0.2126 × 0.189 + 0.7152 × 0.168 + 0.0722 × 0.131 = 0.170.
  3. Result: about rgb(43, 43, 43), a neutral gray with no tint at all.

Same two functions, different order, and one result is toned while the other is not. The general rule: put color-removing functions first if you want a later function to tint the result, and last if you want a neutral final image.

The generator always writes functions in one fixed order: blur, brightness, contrast, saturate, grayscale, sepia, invert, hue-rotate, opacity. So grayscale always comes before sepia, which gives the toned version. If you need a different order, copy the CSS and rearrange the functions by hand.

What do the presets produce?

The generator opens on the Vintage preset. These are the exact declarations each preset writes:

Preset Generated CSS
None filter: none;
Grayscale filter: grayscale(100%);
Vintage filter: brightness(105%) contrast(110%) saturate(130%) sepia(45%);
Warm filter: brightness(105%) saturate(140%) sepia(25%);
Cool filter: saturate(120%) hue-rotate(200deg);
Dramatic filter: brightness(90%) contrast(150%) saturate(120%);
Faded filter: brightness(110%) saturate(80%) opacity(80%);
Invert filter: invert(100%);
Blur filter: blur(4px);

Presets are starting points. Click Warm, then drag Blur to 2px, and the output becomes filter: blur(2px) brightness(105%) saturate(140%) sepia(25%);. Click None or Reset and it returns to filter: none;.

The Cool preset is stronger than its name suggests: a 200-degree hue rotation moves oranges toward blue and blues toward orange. It reads as "cool" on warm photos and as a heavy color shift on everything else.

The preview is a fixed sample of a blue, orange and green gradient with a few shapes, not an upload. It shows how each function moves color, contrast and blur, but always test the copied CSS on your real image, because a filter that flatters a gradient can look muddy on a skin tone.

How do you build a filter with the generator?

  1. Open the filter CSS generator. It starts on Vintage.
  2. Pick a preset closest to the look you want, or click None to start from no filters.
  3. Drag the sliders. The preview updates as you move, and the Generated CSS line updates with it.
  4. Click Copy CSS to put the full filter: ...; declaration on your clipboard.
  5. Paste it onto the selector for your image or element, then check it in the real page.

Only changed functions appear in the output, so moving Brightness back to exactly 100% removes brightness() from the line instead of leaving brightness(100%) behind.

Why does a filter break fixed and absolutely positioned elements?

Because any filter other than none makes the element a containing block for its absolutely and fixed positioned descendants, and creates a new stacking context. The Filter Effects specification states both. This is not a browser bug; it is how the property is defined.

In practice, that produces two classic bugs.

A fixed modal that is not fixed. Normally position: fixed places an element relative to the viewport. Inside a filtered ancestor it is placed relative to that ancestor instead:

.card { filter: grayscale(100%); }

/* Meant to cover the whole screen, but now covers only .card */
.card .modal { position: fixed; inset: 0; }

A dropdown hidden under neighbors. The filtered element becomes its own stacking context, so a child with z-index: 1000 can no longer rise above elements outside the card. It is only on top within the card.

The fixes are the same for both. Apply the filter to a narrower element, such as the img inside the card rather than the card itself. Or render the modal or dropdown outside the filtered element, which is what "portal" components in UI frameworks do. The transform property causes the same containing-block behavior, so the CSS transform guide is useful if you meet this bug with both properties in play.

How do filters compare with backdrop-filter and opacity?

Several properties overlap with filter functions, and choosing the right one avoids side effects.

Property Affects Typical use
filter The element and its contents Photo effects, grayscale logos, blur on an image
backdrop-filter Whatever is behind the element Frosted glass panels, blurred overlays
opacity The element and its contents Fading an element in or out
filter: opacity() The element and its contents Transparency as part of a filter chain
mix-blend-mode How the element blends with what is behind it Color overlays, duotone effects

backdrop-filter takes the same functions as filter, but applies them to the content behind the element, which is how glassmorphism panels blur the page underneath. The glassmorphism generator builds those declarations.

For fading an element on its own, plain opacity is the clearer choice. opacity() inside a filter chain is useful when you are already writing a filter and want transparency in the same line.

Can you animate CSS filters?

Yes. filter is animatable, and a common pattern is a grayscale logo that turns to color on hover:

.logo {
  filter: grayscale(100%);
  transition: filter 0.3s ease;
}
.logo:hover {
  filter: none;
}

The browser interpolates between grayscale(100%) and none because none is treated as the matching functions at their identity values. Interpolation works cleanly when both states list the same functions in the same order. If the start and end lists differ in order, the result can jump instead of animating smoothly, so write both states with the same function order.

Do CSS filters hurt performance?

Filters cost rendering time, and blur costs the most. Browsers commonly run filters on the GPU, which makes most color filters cheap. blur() is different: the work grows with both the blur radius and the size of the area, so a large radius on a full-screen element, especially one that animates or sits behind scrolling content, can drop frames on slower devices.

Practical limits:

  • Keep blur radii small on large elements, and avoid animating the radius itself.
  • Blur a smaller element or a scaled-down image rather than a full-page wrapper.
  • For motion, animate transform and opacity, which browsers handle efficiently, and keep the filter static.
  • Test on a mid-range phone, not only a desktop.

Common questions about specific effects

A few effects come up often enough to write out.

Black and white image: filter: grayscale(100%); Values below 100% leave some color, so grayscale(50%) gives a muted look.

Dimmed background image behind text: filter: brightness(60%); on the image element. Check the text contrast afterward rather than guessing; the color contrast checker gives a WCAG ratio.

Rough dark mode for an embedded document or diagram: filter: invert(100%) hue-rotate(180deg); Inverting flips light and dark but also turns hues to their opposites; rotating the hue by 180 degrees brings most colors back toward their original hue. It is an approximation, and photos inside will look wrong, so exclude them.

Toned black and white: filter: grayscale(100%) sepia(60%); as worked through above.

The full set of CSS builders, including text shadow and transforms, is on the CSS tools hub.

Is the generator private?

Yes. The preview and the CSS are generated in your browser with JavaScript. Nothing is uploaded, there is no image to upload in the first place, and the tool keeps working offline once the page has loaded. Copying the CSS counts toward a daily usage limit and records only that a copy happened.

Comments

0 comments

0/2000 characters

No comments yet. Be the first to share your thoughts!