ESC

Type to search the knowledge base.

CSS Filters and Backdrop Filter

filter vs backdrop-filter — blur, saturate, and glass effects, stacking costs, and accessibility pitfalls for frosted UI.

intermediate3 min read
  • css
  • filters
  • backdrop-filter

filter paints an effect on an element and its contents. backdrop-filter samples what’s behind the element (through its transparent areas) and filters that, which is how “frosted glass” panels work.

Docs: MDN filter, MDN backdrop-filter.

filter: the element itself

.avatar-muted {
  filter: grayscale(1) opacity(0.85);
}

.icon:hover {
  filter: brightness(1.1);
}

.hero-img.is-loading {
  filter: blur(12px);
}

Common functions: blur(), brightness(), contrast(), grayscale(), hue-rotate(), invert(), opacity(), saturate(), sepia(), drop-shadow().

drop-shadow() follows alpha shape (good for non-rect icons) unlike box-shadow.

.logo {
  filter: drop-shadow(0 2px 4px rgb(0 0 0 / 0.35));
}

Multiple functions space-separate in one filter value; order matters.

backdrop-filter: frosted chrome

.topbar {
  position: sticky;
  top: 0;
  background: rgb(255 255 255 / 0.72);
  backdrop-filter: blur(12px) saturate(1.2);
  -webkit-backdrop-filter: blur(12px) saturate(1.2); /* still useful in some engines */
}

Requirements that people miss:

  1. The element needs partial transparency — fully opaque background hides the backdrop.
  2. Something visible must scroll/paint behind it.
  3. Stacking contexts and overflow clipping can prevent sampling.
.modal-panel {
  background: color-mix(in srgb, var(--surface) 70%, transparent);
  backdrop-filter: blur(16px);
  border: 1px solid rgb(255 255 255 / 0.4);
}

Performance and layers

Filters can promote layers and are not free:

  • Large blur() radii are expensive, especially animated.
  • backdrop-filter re-samples as content moves — scroll under a full-width frosted header costs more than a solid header.
  • Prefer static blur over animating blur radius every frame.
@media (prefers-reduced-transparency: reduce) {
  .topbar {
    background: var(--surface-solid);
    backdrop-filter: none;
  }
}

Also respect prefers-reduced-motion if filters are animated.

Accessibility

  • Don’t rely on filter: grayscale alone to mean “disabled.”
  • Frosted text over busy backgrounds can fail contrast — add a stronger scrim.
  • invert() for “dark mode” breaks images and brand colors; use real theme tokens.

Worked: dimmed non-modal layer

.route-loading .app-shell {
  filter: grayscale(0.2) brightness(0.97);
  pointer-events: none;
  user-select: none;
}

This mutes the whole shell; fine for brief states, bad for permanent UI.

Interview out-loud

“filter affects the element and its descendants’ rendering. backdrop-filter filters what’s behind a semi-transparent element — glassmorphism. I keep blur radii modest, ensure translucent backgrounds, and provide solid fallbacks when backdrop-filter isn’t supported or the user prefers reduced transparency. Filters have GPU cost, especially on scroll.”

Footguns

  1. Opaque backgrounds with backdrop-filter — no visible effect.
  2. Forgetting parent overflow: hidden clipping the backdrop sample.
  3. Animating huge blurs on mobile.
  4. Using filter: opacity() instead of opacity without knowing it creates different stacking behavior.
  5. Double-applying filters on parent and child until colors look muddy.

Compositing notes

filter on a parent flattens descendants into a single filtered group for many purposes. That can break expected position: fixed containing blocks and create stacking contexts you did not plan. Prefer filtering a leaf image/icon over the whole app shell.

/* Prefer leaf */
.thumb img { filter: grayscale(0.2); }

/* Costly + side effects */
.app-shell.is-dimmed { filter: brightness(0.9); }

backdrop-filter fallbacks

.topbar {
  background: rgb(255 255 255 / 0.95); /* solid-ish fallback first */
}
@supports (backdrop-filter: blur(8px)) {
  .topbar {
    background: rgb(255 255 255 / 0.65);
    backdrop-filter: blur(12px) saturate(1.15);
  }
}

Test over busy hero videos — frost that looks fine on a flat gray fails on high-frequency backgrounds. Increase scrim opacity before increasing blur radius; blur is the expensive knob.

SVG filters

filter: url(#goo) can reference SVG filter primitives for advanced effects. Keep them rare in product UI; they are harder to theme and reason about in DevTools than CSS function filters.

Further reading

Related guides