light

Two Ways to Create Blurry Background Images with Clear Text

Person holding eyeglasses with water droplets

Photo Credit: Karl Hedin

Learn how to blur an element's background image without blurring its contents using absolute positioning and CSS grid.

Time to read: ~3 minutes


Let's say you want to have text overlaid on top of a background image. Depending on the image, this may cause an issue as the contents of the image may interfere with the text. Below is such an example, just in case you're unfamiliar with this problem.

Tough to read?

Some common solutions include:

  • darkening/lightening the image to improve the contrast between the text and the image
  • blurring the image to reduce the noise in the image so that text is more legible

We can even use both of the above-mentioned solutions together to increase the legibility even further. In theory this should be relatively simple, but in practice it can be a bit tricky as you need to blur the background image without blurring the contents within.

Nesting Content Inside Div with Blurred Background (Failed Attempt)

Below we have a failed attempt of this effect where we have a div with a blurred background image, and we have a div nested inside that contains some text.

Too Blurry?

<style> .main-content { /* ... */ } .blurred-content { background: rgba(0, 0, 0, 0.5) url('some-background-image.png'); background-blend-mode: darken; filter: blur(2px); /* ... */ } .clear-content { filter: blur(0px); /* ... */ } </style> <div class="main-content"> <div class="blurred-content"> <div class="clear-content"> <p>Too Blurry?</p> </div> </div> </div>

I feel like this is similar to everyone's first crack at this effect (at least it was what I tried first).

As you can see the background is blurred, but the text is also blurred. So much for legibility, right?

In this attempt, I've used the filter property with the value of blur(2px) to blur the background. This also blurred everything contained within the blurry-background element, so the text in the clear-content element was also blurred despite my efforts to revert the effect setting its filter property to blur(0px) (would be awesome if this worked though!).

Now that we know the filter property ends up affecting all child elements of the element it is applied to, we need to come up with a way that doesn't involve having children nested within the blurred element.

Velma from Scooby Doo on the ground reaching around to find her glasses

Absolute Positioning with Z-Index

The traditional solution would be to absolutely position the content we want to keep clear and place it above the blurred content using z-index. So let's give that a try and see how that works.

Too Blurry?

Nah, this is crystal clear!

<style> .main-content { position: relative; /* ... */ } .blurred-content { background: rgba(0, 0, 0, 0.5) url('some-background-image.png'); background-blend-mode: darken; filter: blur(2px); /* ... */ } .clear-content { position: absolute; z-index: 2; /* ... */ } </style> <div class="main-content"> <div class="blurred-content"></div> <div class="clear-content"> <p>Too Blurry?</p> <p>Nah, this is crystal clear!</p> </div> </div>

That looks a lot better! This time we can see the text clearly, and the background is still blurred.

This method is achieved by taking the content we'd like to be clear and making it a sibling of the blurred content instead of a child. We then set the main-content element to have relative positioning so we can absolutely position the clear-content and set it's z-index to 2 so it's placed above blurred-content.

Velma from Scooby Doo finds her glasses and suddenly sees a monster

CSS Grid with Z-Index

Although this works, I accidentally stumbled on another way to achieve the same effect using the more modern CSS grid. While using grid areas to place elements, I noticed that I was able to place multiple elements within the same grid area. I then tried to use the z-index property, and to my surprise it worked! According to the grid spec, this is expected behaviour. Since grid items are allowed to overlap and intersect, the use of the z-index property allows for the control of these elements on the z-axis.

In my opinion, this would be a better solution than absolute positioning as it preserves document flow and avoids potential issues when new elements are added near the absolutely positioned element.

So let's try it out!

Clear content without absolute positioning!

<style> .main-content { display: grid; grid-template-areas: "content"; /* ... */ } .blurred-content { grid-area: content; background: rgba(0, 0, 0, 0.5) url('some-background-image.png'); background-blend-mode: darken; filter: blur(2px); /* ... */ } .clear-content { grid-area: content; z-index: 2; /* ... */ } </style> <div class="main-content"> <div class="blurred-content"></div> <div class="clear-content"> <p>Clear content without absolute positioning!</p> </div> </div>

The setup is almost the same as the absolute positioning solution, but instead of the parent element being relative, we define the different grid areas. In this case, we only really require a single area, which I've called content. We then set the grid-area for both the blurred and clear content to be this newly-defined content grid area. At this point, the two elements are now competing for the same space in the grid, so we need to set the z-index of the clear content to 2 so it's placed above the blurred content.

And with that, we've achieved the same effect as the absolute positioning solution with something more modern and dynamic.

Velma and Shaggy from Scooby Doo boogying

Wrapping Up...

I love when I discover new ways to solve problems where previously I thought there was only one way to solve it. Especially when the new solution is much more elegant and simple. I've been getting more into grid lately, and finding out all the different ways to use it is amazing. Hope that this post was helpful and that you no longer have trouble blurring background images while maintaining clear content ever again!

Thanks for reading, now go build some stuff!

Feel free to reach out!


Want to show your support?

Copyright © 2021 - 2022 Andrew Grass All rights reserved.