{"slug": "next-level-frosted-glass-with-backdrop-filter", "title": "Next-level frosted glass with backdrop-filter", "summary": "The article explains how to enhance the CSS `backdrop-filter: blur()` frosted glass effect by addressing a common oversight: the default algorithm only blurs pixels directly behind an element, ignoring nearby content. The author demonstrates a workaround using a child element that extends beyond the parent's boundaries and then applies a mask to trim the excess, allowing the blur to incorporate surrounding colors for a more realistic, lush result. The post also provides a brief overview of CSS filters and their application through the `filter` and `backdrop-filter` properties.", "body_md": "[Introduction]\n\nOne of my all-time favourite CSS tricks is using `backdrop-filter: blur()`\n\nto create a frosted glass effect. I use it in just about every project I work on, including this blog!\n\nHere’s a quick demo, to show what I’m talking about:\n\nThis effect helps us add depth and realism to our projects. It’s lovely.\n\n**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!\n\nIn 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!\n\n[Link to this heading](#css-filters-1)CSS filters\n\nTo briefly explain the underlying concept: CSS gives us quick and easy access to SVG filters via the `filter`\n\nproperty.\n\nFor example, we can give elements a Gaussian blur with `filter: blur()`\n\n:\n\nThere 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:\n\nIn these examples, I’m applying the filters to an `<img>`\n\ntag, but we can apply them to standard DOM nodes as well:\n\nLorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.\n\nPretty neat, right?\n\nThings get even cooler with `backdrop-filter`\n\n. This property lets us apply these same filters to the stuff *behind* a given element.\n\nFor example:\n\nIn this demo, the `.magic-ring`\n\nelement sits in front of a photo ([ source(opens in new tab)](https://unsplash.com/photos/assorted-signages-on-buildings-nTBW1cOY1qI)). It uses the `backdrop-filter`\n\nproperty to apply some filtering to everything behind it, which can be used for some pretty artistic effects.\n\nIn practice, I pretty much only use `backdrop-filter`\n\nfor one use case: blurring everything behind an element, usually a header, to create the “frosted glass” effect I mentioned earlier:\n\nAlright. Let’s talk about the thing most developers miss.\n\n[Link to this heading](#the-issue-2)The Issue\n\n**Here’s the problem:** The `backdrop-filter`\n\nalgorithm only considers the pixels that are *directly behind* the element.\n\nFor filters like `brightness`\n\nor `hue-rotate`\n\n, that makes perfect sense. With blur, though, we actually want to consider pixels that are *near* the element too.\n\nThis is one of those things where a demo is worth a thousand words. Check out the difference:\n\nBy 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.\n\nThat’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.\n\nUnfortunately, this isn’t something we can configure directly. Instead, we need to be a bit crafty.\n\nHere’s the code:\n\nCode Playground\n\nResult\n\nIt looks complicated, but the principle isn’t too scary.\n\nIf we want the blur to consider elements nearby, we need to *extend* that element so that it covers those elements. Then, using a mask, we trim the excess away, so that it’s visually the same size as we originally intended.\n\n**Let’s walk through it step by step.** First, we have a header with a backdrop blur:\n\nCode Playground\n\nResult\n\nBecause 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.\n\nRather than give the `<header>`\n\nan explicit height (which would lock us into a specific size, rather than a dynamically-calculated one), let’s move the `backdrop-filter`\n\nto a child element, and set that child element to be twice as large as its parent:\n\nCode Playground\n\nResult\n\nAlright, now we’re getting somewhere! The `.backdrop`\n\nchild grows to cover most of the red ball, blurring it correctly.\n\nNow, we don’t actually want to *see* all of this excess backdrop. We need to trim it back to the size of the `<header>`\n\nparent element.\n\nMaybe we can solve this with `overflow: hidden`\n\n?\n\nCode Playground\n\nResult\n\nWhat you see here depends on your browser. On Firefox and Safari, this works great! But sadly, it doesn’t work on Chrome. There’s no soft red glow.\n\nI think it’s an order-of-operations issue. In Chrome, the overflow trimming occurs *before* the filters are applied, so when the blurring algorithm is executed, the content has already been hidden.\n\nFor the same reasons, we can’t use `overflow: clip`\n\nor `clip-path`\n\n, but fortunately, we *can* use `mask-image`\n\n. The masking algorithm happens *after* the filters, in all browsers. ✨\n\nMasking is a huge topic which is well beyond the scope of this tutorial, but the basic idea is that we can specify how transparent parts of an element should be. For example, if our mask is an opaque circle in a transparent box, that opaque shape can be applied to any other element:\n\nMost commonly, masks are images in a format that supports transparency (like `.png`\n\nor `.gif`\n\n), but we can also use *gradients* as masks. For example, we can fade an image from opaque to transparent:\n\nCode Playground\n\nResult\n\nFor our glassy header optimization, we’re using `mask-image`\n\nto make the *original* header size fully opaque, and everything past that point fully transparent. Essentially our mask looks like this:\n\nThe relevant code looks like this:\n\n```\n.backdrop {\n  height: 200%;\n  mask-image: linear-gradient(\n    to bottom,\n    black 0% 50%,\n    transparent 50% 100%\n  );\n}\n```\n\nOur mask doesn’t *look* like a gradient, does it? I typically picture gradients fading smoothly from one color to the next.\n\nIt might feel like an Term from the LEGO world, referring to assembling LEGO bricks in a way that the manufacturer did not intend., but this is what we need in this case. Our gradient is solid black from 0% to 50%, then it instantly becomes transparent for the final 50%.\n\n**Why 50%?** We set `height`\n\nto 200%, so that `.backdrop`\n\nwill always be twice as tall as its container. The percentages inside `mask-image`\n\n’s gradient are relative to the *current element’s size*.\n\nFor example, if our `<header>`\n\nis 200px tall, our `.backdrop`\n\nwill grow to 400px (200% of its parent). Then, our mask will show the first 50% of this element (0px to 200px), and hide the rest (200px to 400px).\n\nHere’s the code again. Feel free to experiment with it, to develop your intuition for what’s happening:\n\nCode Playground\n\nResult\n\nThis is the basic idea behind this solution, but there’s a bug we need to fix, and a couple more optimizations we can consider.\n\n[Link to this heading](#pointer-events-3)Pointer events\n\nOur current implementation has a pretty big issue: nearby elements become unclickable and unselectable.\n\nTry to select the text just below the header:\n\nCode Playground\n\nResult\n\nHere’s what happens when I try on desktop:\n\n**Here’s the problem:** the `mask-image`\n\nproperty will *visually* hide parts of an element, but the element is still there. We’re not able to click on the text because that `.backdrop`\n\nis extending out and covering it!\n\nFortunately, it’s an easy fix:\n\n```\n.backdrop {\n  position: absolute;\n  inset: 0;\n  height: 200%;\n  backdrop-filter: blur(16px);\n  mask-image: linear-gradient(\n    to bottom,\n    black 0% 50%,\n    transparent 50% 100%\n  );\n  pointer-events: none;\n}\n```\n\nThe `pointer-events`\n\nproperty allows us to specify that an element should be ignored when resolving click/touch events. `mask-image`\n\nmakes the backdrop invisible, and `pointer-events: none`\n\nmakes the backdrop Something that can be seen but not felt, like a mirage or a ghost.\n\nThis is another reason why `.backdrop`\n\nneeds to be a *child* element. We don’t want the `<header>`\n\nitself to ignore clicks, since it typically has navigation links. We want to target the frosted glass element specifically.\n\n[Link to this heading](#flickering-top-4)Flickering top\n\nBy 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.\n\nBut what about when things leave the top of the viewport?\n\nThings aren’t quite so nice. **Scroll down slowly in this demo:**\n\nNotice that weird goopy flickering, at the very top of the viewport?\n\nIt’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.\n\n**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()`\n\n, even if the elements are layered correctly.\n\nThe best solution I’ve found for this solution is to add a gradient that covers the flickering:\n\nHere’s the code:\n\n```\n.backdrop {\n  position: absolute;\n  inset: 0;\n  height: 200%;\n  background: linear-gradient(\n    to bottom,\n    /*\n      Replace this with your site’s\n      actual background color:\n    */\n    hsl(0deg 0% 0%) 0%,\n    transparent 50%\n  );\n  backdrop-filter: blur(16px);\n  mask-image: linear-gradient(\n    to bottom,\n    black 0% 50%,\n    transparent 50% 100%\n  );\n  pointer-events: none;\n}\n```\n\nUntil now, the `.backdrop`\n\nelement has been fully transparent; we haven’t applied a `background`\n\nat 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.\n\n[Link to this heading](#thicker-glass-5)Thicker glass\n\nIn some circumstances, the frosted glass effect can be a bit distracting:\n\nThis 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.\n\nThere are two main ways to fix this. We could increase the blur radius:\n\nOr, we could add a `background-color`\n\nto the parent `<header>`\n\n, making it semi-opaque:\n\n(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.)\n\n[Link to this heading](#browser-support-6)Browser support\n\n`backdrop-filter`\n\nhas been around in all major browsers for a number of years now; [according to caniuse(opens in new tab)](https://caniuse.com/css-backdrop-filter), it’s above 97% support as I write this in December 2024. For our main optimization, we also need `mask-image`\n\n, which is [almost as well supported(opens in new tab)](https://caniuse.com/mdn-css_properties_mask-image), sitting at 96.3%.\n\nBoth properties require a `-webkit`\n\nprefix for some browsers, but most CSS tooling will add this for you automatically.\n\nAt 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.\n\n[Link to this heading](#glassy-edge-7)Glassy edge\n\nAs 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:\n\nIsn’t that lovely?!\n\nHere’s how this works: The bottom edge is a separate DOM node with *its own* `backdrop-filter`\n\n. 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`\n\nfilter to really make it pop. ✨\n\nThe code for this is a bit gnarly 😅. I’ve done my best to explain it in the comments below:\n\n```\n<style>\n  .backdrop {\n    position: absolute;\n    inset: 0;\n    height: 200%;\n    border-radius: 4px;\n    background: hsl(0deg 0% 100% / 0.1);\n    pointer-events: none;\n    backdrop-filter: blur(16px);\n    mask-image: linear-gradient(\n      to bottom,\n      black 0,\n      black 50%,\n      transparent 50%\n    );\n  }\n\n  .backdrop-edge {\n    /* Set this to whatever you want for the edge thickness: */\n    --thickness: 6px;\n\n    position: absolute;\n    inset: 0;\n    /*\n      Only a few pixels will be visible, but we’ll\n      set the height by 100% to include nearby elements.\n    */\n    height: 100%;\n    /*\n      Shift down by 100% of its own height, so that the\n      edge stacks underneath the main <header>:\n    */\n    transform: translateY(100%);\n    background: hsl(0deg 0% 100% / 0.1);\n    backdrop-filter: blur(8px) brightness(120%);\n    pointer-events: none;\n    /*\n      We mask out everything aside from the first few\n      pixels, specified by the --thickness variable:\n    */\n    mask-image: linear-gradient(\n      to bottom,\n      black 0,\n      black var(--thickness),\n      transparent var(--thickness)\n    );\n  }\n</style>\n\n<header>\n  <div class=\"backdrop\"></div>\n  <div class=\"backdrop-edge\"></div>\n</header>\n```\n\n[Link to this heading](#the-final-code-8)The final code\n\nPhew! We covered a lot of ground in this one.\n\nHere’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.\n\n**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.\n\nCode Playground\n\nResult\n\n[Link to this heading](#continue-learning-9)Continue learning\n\nIf you’ve enjoyed this blog post, you might like to know that I have an entire course about CSS!\n\nIt’s called [CSS for JavaScript Developers(opens in new tab)](https://css-for-js.dev), 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!\n\nYou can learn all about my course here:\n\n### Last updated on\n\nApril 27th, 2026", "url": "https://wpnews.pro/news/next-level-frosted-glass-with-backdrop-filter", "canonical_source": "https://www.joshwcomeau.com/css/backdrop-filter/", "published_at": "2024-12-02 13:15:00+00:00", "updated_at": "2026-05-22 14:56:28.674390+00:00", "lang": "en", "topics": [], "entities": [], "alternates": {"html": "https://wpnews.pro/news/next-level-frosted-glass-with-backdrop-filter", "markdown": "https://wpnews.pro/news/next-level-frosted-glass-with-backdrop-filter.md", "text": "https://wpnews.pro/news/next-level-frosted-glass-with-backdrop-filter.txt", "jsonld": "https://wpnews.pro/news/next-level-frosted-glass-with-backdrop-filter.jsonld"}}