Updates:

You know what’s hot right now? Big, high-quality, Retina®-crisp JPEGs, blurred into a header background:

Triple the filesize, none of the quality!

The problem is if filters aren’t supported, the text risks being unreadable. That violates accessibility standards, and can ruin it even with perfect vision.

Browsers like Internet Explorer ruin the filter party, turning what was legible into an eyesore.

So you make a pre-blurred version of the image, but what if the design calls for dynamic blurring, like how Apple’s all about these days?

I happened upon a trick that lets non-filter-supporting browers get a color overlay instead, which isn’t as pretty, but preserves readability:

It’s grimier, but you can’t win them all. At least you can read the text again.
It’s like I’m on Medium!

The setup

I’m using a pseudo-element as a background, because filter on that is better supported than filtering background-image.

  .backdrop { position: relative }

.backdrop::after {
  content: "";

  /* Stretch it across the entire parent element */
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;

  /* Shove it underneath the parent element's contents, otherwise it’s more of a foreground */
  z-index: -2;

  /* Display the background like you normally would on the parent element */
  background: #222 url("inspirational-landscape-and/or-laughing-with-salad.jpg");
}

The filter

In this case, the design called for blurring and slightly darkening the backdrop.

  /* Don't forget your prefixes, because Safari >9.1 and all Chromes still need -webkit- */
filter: blur(4px) brightness(75%);

But we can’t do only that, because browsers that don’t support filter risk The Illegible.

The trick

Did you know there’s an opacity() filter effect? It didn’t seem very useful to me compared to the opacity property, but its existence enables a clean fallback:

  .backdrop::before {
  /* Do the stuff from earlier to make it act like another background */
  content: "";
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;

  /* Position it on top of the other pseudo-element, but still behind the element’s contents */
  z-index: -1;

  /* Make it dark enough that the text will show up over any given image */
  background: rgba(0,0,0, 0.5);

  /* Use a filter to... completely hide it?? */
  filter: opacity(0%);
}

If filters are supported, then the pseudo-element with the image is blurred and darkened, and the other with the dark color becomes invisible. If filters aren’t supported, then things aren’t as pretty, but the text is legible.

I wrapped it up in a pen, if you prefer:

This trick will probably come in handy for other uses of filter, so I’ll keep it in my back pocket.

Why not use @supports instead?

As mentioned by @iamvdo on Twitter, the handy-dandy feature-detection @supports rule has been implemented in roughly the same browsers that support filters, which could make your code clearer and less hacky:

  @supports (filter: blur(4px) brightness(75%)) or (-webkit-filter: blur(4px) brightness(75%)) {
  /* NOW do stuff that requires filters to work */
}

You can do that instead. I crunched CanIUse’s usage data and my trick works in a few places @supports doesn’t:

  • Chrome 18–27
  • Safari 8.x
  • UC Browser 9.9 (current as of this writing)

The combined usage share as of May 2016 is ≈3.6% USA and 10.5% worldwide. These numbers will decrease with time, and we’re using these filters as an enhancement, so @supports can be preferable if you want explicit CSS.