CSS Filters
CSS filters apply visual processing effects to elements: blurring, adjusting brightness and contrast, shifting hue, converting to grayscale, and more. Like blend modes, these are effects that traditionally required image editing software or pre-processed assets. Applied in CSS, they work on any element including live content, and they can be animated or toggled with a single property change.
CSS provides two filter properties: filter, which applies effects to an element and its contents, and backdrop-filter, which applies effects to whatever is behind an element. Both use the same set of filter functions.
The filter Property
The filter property accepts one or more filter functions. When multiple functions are listed, they are applied in order from left to right, each one operating on the result of the previous.
/* Single filter */
.element {
filter: grayscale(100%);
}
/* Multiple filters chained */
.element {
filter: brightness(1.2) contrast(1.1) saturate(1.3);
}
Filter Functions
blur() applies a Gaussian blur. The value is a length, typically in pixels. blur(0) is no blur, blur(4px) is a moderate blur, blur(20px) is heavily blurred. Blur is expensive to render, more so than most other filter functions.
.frosted {
filter: blur(8px);
}
brightness() adjusts how light or dark the element appears. A value of 1 is the original. Values above 1 brighten, values below 1 darken. brightness(0) produces black.
.dimmed {
filter: brightness(0.6);
}
.brightened {
filter: brightness(1.4);
}
contrast() adjusts the difference between light and dark areas. Values above 1 increase contrast, values below 1 reduce it toward flat gray. contrast(0) produces a uniform mid-gray.
saturate() adjusts color intensity. saturate(0) is fully desaturated (grayscale). saturate(1) is the original. Values above 1 boost saturation beyond the original.
grayscale() converts the element to grayscale. grayscale(100%) or grayscale(1) is fully gray. Values between 0 and 1 partially desaturate.
sepia() applies a warm brownish tone resembling aged photographs. sepia(1) is fully sepia, sepia(0) is the original.
hue-rotate() shifts all colors around the hue wheel by the specified angle. hue-rotate(90deg) shifts every color 90 degrees around the wheel. A full 360deg returns to the original. This is useful for creating color variants of an element without changing its source.
.hue-shifted {
filter: hue-rotate(180deg);
}
invert() inverts the colors of the element. invert(1) or invert(100%) fully inverts. invert(0.5) produces a flat gray. Useful for dark mode icon handling.
opacity() works the same as the opacity property but as a filter function. Its main advantage when chained is that it participates in the filter pipeline along with other functions.
drop-shadow() applies a shadow that follows the exact shape of the element's content including transparency, unlike box-shadow which follows the rectangular box. The syntax is the same as box-shadow without the spread or inset options.
/* box-shadow follows the box */
.box-shadow {
box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.4);
}
/* drop-shadow follows the actual content shape */
.drop-shadow {
filter: drop-shadow(4px 4px 8px rgba(0, 0, 0, 0.4));
}
Filter Demo
The demo below applies individual filter functions to a photograph. Use the slider to control the intensity of each filter.
filter: none
Chaining Filters
Multiple filter functions in a single filter declaration chain together, each function applied to the output of the previous. The order can change the result: blurring first then adjusting brightness differs from adjusting brightness first then blurring.
/* A vintage photo effect: desaturate slightly, warm with sepia, reduce brightness */
.vintage {
filter: saturate(0.8) sepia(0.4) brightness(0.9) contrast(1.1);
}
/* A dramatic high-contrast look */
.dramatic {
filter: grayscale(0.3) contrast(1.4) brightness(1.1);
}
/* Hover to restore full color and clarity */
.card {
filter: grayscale(1) brightness(0.8);
transition: filter 0.3s ease;
}
.card:hover {
filter: grayscale(0) brightness(1);
}
The hover pattern above is a common card interaction: grayscale at rest, full color on hover. Because filter is a single property, the transition between two filter states is smooth as long as both states use the same functions in the same order.
backdrop-filter
backdrop-filter applies filter effects to the content behind an element rather than to the element itself. The element must have some degree of transparency for the effect to be visible, since the filter only affects what shows through.
.frosted-panel {
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(12px);
}
The frosted glass effect shown above is the most common use of backdrop-filter. A semi-transparent background combined with a blur on the backdrop creates the appearance of frosted or etched glass, with the blurred content behind visually suggesting depth without being readable.
Frosted Glass Demo
The demo below shows a frosted glass card over a background image. The blur and transparency are both adjustable so you can see how they work together.
Frosted Glass
This card uses backdrop-filter: blur() combined with a semi-transparent background to create a frosted glass effect.
backdrop-filter: blur(12px) | background: rgba(255,255,255,0.2)
Other backdrop-filter Uses
Blur is the most common backdrop-filter value but any filter function works. A modal overlay that desaturates the background while the modal is open, a navigation bar that slightly brightens the content behind it as you scroll, a tooltip that inverts the colors of whatever it sits over: all of these are achievable with backdrop-filter.
/* Desaturate everything behind a modal */
.modal-overlay {
backdrop-filter: saturate(0.3) brightness(0.7);
}
/* Navigation bar with subtle blur */
.navbar {
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(8px) saturate(1.2);
}
backdrop-filter still requires the -webkit-backdrop-filter prefix in Safari for full support. Always include both when using it in production: -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
Performance Considerations
CSS filters, particularly blur() and backdrop-filter, are among the most expensive visual effects in CSS. They require the browser to process every pixel in the affected area, which becomes costly on large elements or when animated.
blur() on large elements is the most problematic. A full-screen blur can take several milliseconds to render per frame, which easily exceeds the 16ms budget for 60fps. backdrop-filter: blur() is even more expensive than filter: blur() because the browser must first composite the backdrop content, then apply the blur, then composite the overlaying element.
Brightness, contrast, grayscale, and saturate are far cheaper than blur. They are generally safe to animate. Hue-rotate is moderate. Blur and backdrop blur warrant testing on target hardware before committing to them in performance-sensitive contexts like scroll-linked effects or continuous animations.
A few practical rules: keep blurred areas as small as possible, avoid animating blur values on large elements, and prefer filter: blur() on a contained element over backdrop-filter on an overlay when either approach would achieve the same visual result.
Applying backdrop-filter to an element creates a new stacking context, similar to position: fixed or transform. This can cause z-index issues with nearby elements. If elements start layering unexpectedly after adding backdrop-filter, check whether the new stacking context is interfering with your existing z-index hierarchy.
Putting It Together
CSS filters give you a post-processing pipeline for any element on the page. Static effects like grayscale-on-rest and color-on-hover are simple, low-cost, and widely used. Chained filters let you create photo treatment presets in pure CSS. Frosted glass panels built with backdrop-filter have become a standard UI pattern. The filter functions are the same whether you are applying them to an image, a div, a video, or a canvas element.
The mental model to develop is thinking of filter as a lens placed over an element: it changes what you see without changing the element itself. And backdrop-filter as a lens placed under a transparent element: it processes the view through it. Both lenses are cheap for simple adjustments and expensive for blur. Use them accordingly.