Two Ways to Create Blurry Background Images with Clear Text
Learn how to blur an element's background image without blurring its contents using absolute positioning and CSS grid.
Time to read: ~3 minutes
Published: Sunday, June 5th 2022
Table of Contents
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.
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
.
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.
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!