JoshWComeau

Next-level frosted glass with backdrop-filter

Filed under
CSS
on
in
December 2nd, 2024.
Dec 2024.
Last updated
on
in
October 13th, 2026.
Oct 2026.
Introduction

One of my all-time favourite CSS tricks is using backdrop-filter: blur() to create a frosted glass effect. I use it in just about every project I work on, including this blog!

Here’s a quick demo, to show what I’m talking about:

Some Example Website
https://www.joshwcomeau.com/example-website

Some Website

This is an example website showing how I typically use backdrop-filter to create glassy headers.

Notice that as the cupcake moves behind the header, it appears blurry, as it would if it was passing behind frosted glass.

This effect helps us add depth and realism to our projects. It’s lovely.

But when I see this effect in the wild, it’s almost always missing some crucial optimizations. A couple of small changes can make our frosted glass so much more lush and realistic!

In this post, you’ll learn how to make the slickest frosted glass ever ✨. We’ll also learn quite a bit about CSS filters along the way!

Link to this headingCSS filters

To briefly explain the underlying concept: CSS gives us quick and easy access to SVG filters via the filter property.

For example, we can give elements a Gaussian blur with filter: blur():

My 3D mascot smiling with his tongue out. It’s very blurry, but dragging the slider makes it clearer
16px

There are lots of fun filter options, the sorts of things you’d find in image-editing software. Like, rotating the hue of all the colors:

My 3D mascot making a puzzled face. Dragging the slider causes him to become unnatural shades of green and pink.
0deg

In these examples, I’m applying the filters to an <img> tag, but we can apply them to standard DOM nodes as well:

Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since 1966, when designers at Letraset and James Mosley, the librarian at St Bride Printing Library in London, took a 1914 Cicero translation and scrambled it to make dummy text for Letraset's Body Type sheets.

0px
0%

Pretty neat, right?

Things get even cooler with backdrop-filter. This property lets us apply these same filters to the stuff behind a given element.

For example:

50%
130%

In this demo, the .magic-ring element sits in front of a photo ( source(opens in new tab)). It uses the backdrop-filter property to apply some filtering to everything behind it, which can be used for some pretty artistic effects.

In practice, I pretty much only use backdrop-filter for one use case: blurring everything behind an element, usually a header, to create the “frosted glass” effect I mentioned earlier:

Some Example Website
https://www.joshwcomeau.com/example-website

Some Website

This is an example website showing how I typically use backdrop-filter to create glassy headers.

Notice that as the cupcake moves behind the header, it appears blurry, as it would if it was passing behind frosted glass.

Alright. Let’s talk about the thing most developers miss.

Link to this headingThe Issue

Here’s the problem: The backdrop-filter algorithm only considers the pixels that are directly behind the element.

For filters like brightness or hue-rotate, that makes perfect sense. With blur, though, we actually want to consider pixels that are near the element too.

This is one of those things where a demo is worth a thousand words. Check out the difference:

By default, the gaussian blur algorithm is applied to all of the pixels behind the element. This means that if a big colorful element is near the element, it won’t have any effect.

That’s not really how frosted glass works in real life though. Light bounces off of objects and then goes through the glass. It looks so much better when the blurring algorithm includes nearby content.

Unfortunately, this isn’t something we can configure directly. Instead, we need to be a bit crafty.

Here’s the code:

Code Playground

Result

Let’s walk through it step by step. First, we have a header with a backdrop blur:

Code Playground

Result

Because the red ball isn’t behind the header at all, it isn’t being considered by the blurring algorithm, and so we don’t get that soft red glow. We need to extend the header so that it covers at least some of the ball.

Rather than give the <header> an explicit height (which would lock us into a specific size, rather than a dynamically-calculated one), let’s move the backdrop-filter to a child element, and set that child element to be twice as large as its parent:

Code Playground

Result

Alright, now we’re getting somewhere! The .backdrop child grows to cover most of the red ball, blurring it correctly.

Now, we don’t actually want to see all of this excess backdrop. We need to trim it back to the size of the <header> parent element.

We can do that with overflow: hidden:

Code Playground

Result

When I originally published this blog post in late 2024, this approach worked in Firefox and Safari but didn’t work in Chromium browsers. I believe this was an order-of-operations issue; the overflow trimming occurred before the filters were applied, so when the blurring algorithm is executed, the content has already been hidden.

Fortunately, this behaviour was fixed in Chrome 142, released in October 2025. This means that overflow: hidden is now a viable solution to this problem. 😄

Alternatively, if you need to support older versions of Chrome, you can also use the mask-image property on the child, which effectively does the same thing by only painting the top half of the nested .backdrop element:

Code Playground

Result

This is the basic idea behind this solution, but there’s a couple more optimizations we should consider.

Link to this headingFlickering top

By extending the glassy backdrop below the header, we can ensure that the blurring algorithm takes it into consideration even before that element reaches the header.

But what about when things leave the top of the viewport?

Things aren’t quite so nice. Scroll down slowly in this demo:

Some Example Website
https://www.joshwcomeau.com/example-website

Notice that weird goopy flickering, at the very top of the viewport?

It’s the same issue as before. The gaussian blur algorithm is only considering the pixels directly underneath it. When a yellow longboard is scrolled out of view, for example, that data is no longer factoring into the blur algorithm, causing those unnatural color flickers.

Unfortunately, we can’t re-use our solution here. As far as I can tell, elements outside the viewport are never considered by backdrop-filter(), even if the elements extend past the top.

The best solution I’ve found for this solution is to add a gradient that covers the flickering:

Some Example Website
https://www.joshwcomeau.com/example-website

Here’s the code:

.backdrop {
  position: absolute;
  inset: 0;
  height: 200%;
  background: linear-gradient(
    to bottom,
    /*
      Replace this with your site’s
      actual background color:
    */
    hsl(0deg 0% 0%) 0%,
    transparent 50%
  );
  backdrop-filter: blur(16px);
}

Until now, the .backdrop element has been fully transparent; we haven’t applied a background at all. This gradient makes it opaque at the very top, blocking the flickering colors from view, but fading to transparent, to show the frosted glass effect.

Link to this headingThicker glass

In some circumstances, the frosted glass effect can be a bit distracting:

Some Example Website
https://www.joshwcomeau.com/example-website
Some Website

This feels too “busy” to me; the blurry text sitting behind the header makes the site name and navigation too hard to read. It all feels a bit messy, and not as subtle as I want.

There are two main ways to fix this. We could increase the blur radius:

Some Example Website
https://www.joshwcomeau.com/example-website
Some Website
12px

Or, we could add a background-color to the parent <header>, making it semi-opaque:

Some Example Website
https://www.joshwcomeau.com/example-website
Some Website
0.6

(We could also tweak the gradient we added in the previous section, making it fade from fully-opaque to semi-opaque, but I prefer to keep the two things separate, so that I can tweak them independently.)

Link to this headingBrowser support

backdrop-filter has been around in all major browsers for a number of years now; according to caniuse(opens in new tab), it’s above 97% support as I write this in October 2026.

At the bottom of this blog post, I’ll include the full copy-ready code, which uses feature queries to make sure that older browsers still have a usable experience. They won’t get the frosted glass effect, but everything will still be readable and usable.

Link to this headingGlassy edge

As if this stuff wasn’t complicated enough already, Artur Bien came up with an extra twist; we can create the illusion of a 3D piece of glass by adding a second blurred element with different filter settings:

3px
8px

Isn’t that lovely?!

Here’s how this works: The bottom edge is a separate DOM node with its own backdrop-filter. I find it looks better with a smaller blur radius (eg. 8px in the bottom edge, 16px in the main backdrop), and with an extra brightness filter to really make it pop. ✨

The code for this is a bit gnarly 😅. I’ve done my best to explain it in the comments below:

<style>
  .backdrop {
    position: absolute;
    inset: 0;
    height: 200%;
    border-radius: 4px;
    background: hsl(0deg 0% 100% / 0.1);
    pointer-events: none;
    backdrop-filter: blur(16px);
    /*
      Trim away the overflow. I'm using “mask-image” here
      rather than “overflow: hidden” because it seems to
      work better when dealing with nested elements like
      this (though I haven’t done extensive testing).
    */
    mask-image: linear-gradient(
      to bottom,
      black 0,
      black 50%,
      transparent 50%
    );
  }

  .backdrop-edge {
    /* Set this to whatever you want for the edge thickness: */
    --thickness: 6px;

    position: absolute;
    inset: 0;
    /*
      Only a few pixels will be visible, but we’ll
      set the height by 100% to include nearby elements.
    */
    height: 100%;
    /*
      Shift down by 100% of its own height, so that the
      edge stacks underneath the main <header>:
    */
    transform: translateY(100%);
    background: hsl(0deg 0% 100% / 0.1);
    backdrop-filter: blur(8px) brightness(120%);
    pointer-events: none;
    /*
      We mask out everything aside from the first few
      pixels, specified by the --thickness variable:
    */
    mask-image: linear-gradient(
      to bottom,
      black 0,
      black var(--thickness),
      transparent var(--thickness)
    );
  }
</style>

<header>
  <div class="backdrop"></div>
  <div class="backdrop-edge"></div>
</header>

Link to this headingThe final code

Phew! We covered a lot of ground in this one.

Here’s the final code, with all of the optimizations we’ve discussed. I’ve also included feature queries, to make sure that our website remains legible on older browsers.

Feel free to copy this code, and make it your own! This is intended to be a starting point, not a complete solution. For example, you may wish to tweak the size of the backdrop’s overlap for your particular circumstances.

Code Playground

Result

Link to this headingContinue learning

If you’ve enjoyed this blog post, you might like to know that I have an entire course about CSS!

It’s called CSS for JavaScript Developers(opens in new tab), and it’s built using the same tech stack as this blog: it’s chock full of interactive articles, demos, and opportunities to experiment. There are also bite-sized videos, exercises, workshops, and even a few mini-games!

You can learn all about my course here:

Last updated on

October 13th, 2026

# of hits