I learned two nice little tricks today, and thought I'd write a short article on them.
The challenge
Often, we have background images that we put text on top of. An example could be a hero section, or the above-the-fold content on basically any marketing site these days.
Some times, we need to improve the contrast between the text and the background image. Sure, we could just change the image itself - but some times that's not an option.
The old and clunky way 👴
There are several ways to solve this, but this is how I learned to do it back in the days. I typically create the following HTML structure:
<div class="image-box">
<div
class="image-box__background"
style="--image-url: url('some-image.jpg')"
></div>
<div class="image-box__overlay"></div>
<div class="image-box__content">
<h1>Buy our product</h1>
</div>
</div>
I would then make the image-box
relatively positioned, all children absolutely positioned inside, and stack them in the order I would like.
What is this --syntax?
Note that we're passing in the image url via something called CSS Custom properties. You might also know them as CSS variables. It's a way to pass values between our HTML and CSS. You can read more about CSS Custom properties on MDN.
The styles required to style this could look like this:
/*
The container box is relative so we can position stuff inside of it
*/
.image-box {
position: relative;
}
/*
The background and overlay need to be absolutely positioned
*/
.image-box__background,
.image-box__overlay {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
/*
The background image div sizes and positions the background itself.
It's also at the bottom-most position in our "div stack" (z-index 1)
We set the image url via a CSS custom property, that's set via the style attribute in our HTML
*/
.image-box__background {
background: var(--image-url) center center no-repeat;
background-size: cover;
z-index: 1
}
/*
The overlay div is just a colored element with some opacity.
It's above the background image in our stack, so it appears to
darken the image
*/
.image-box__overlay {
background: rgba(0, 0, 0, 0.5);
z-index: 2;
}
/*
The content div is at the top of our stack.
We'd probably add some padding or flexbox properties here as well,
to place the content appropriately
*/
.image-box__content {
position: relative;
z-index: 3;
/* Finally, style and place the content */
color: white;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
A lot of code, but it works pretty well. Here's a CodePen that implements it:
The new cool way! 😎
That was a lot code. Turns out, it doesn't have to be that way.
Let's change our HTML to look like this:
<div class="image-box" style="--image-url(some-image.jpg)">
<h1>Buy our product</h1>
</div>
That looks a bit simpler, right? Let's implement the CSS as well:
.image-box {
/* Here's the trick */
background: linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0.5)) , var(--image-url) center center;
background-size: cover;
/* Here's the same styles we applied to our content-div earlier */
color: white;
min-height: 50vh;
display: flex;
align-items: center;
justify-content: center;
}
Here's a CodePen implementing it:
What's happening here?
As you might have noticed, we now specify two background images:
.image-box {
background-image:
linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0.5)),
var(--image-url);
}
The first background image is a linear gradient that goes from and to the same color. That color is a semi-transparent black, which works as an overlay for your second background.
And that's it really. If you're feeling clever, you could also pass in the amount of darkening you'd want as a second css variable, for further customization. Or use an actual gradient to make your images pop a bit more.
Using box shadow to achieve the same
Turns out, CSS has several ways of layering "meta-content" on top of a background image. Another way to achieve the same is by using the box-shadow
property with a huge spread value and the inset
setting.
.image-box {
/* Here's the trick */
box-shadow: inset 0 0 0 100vw rgba(0,0,0,0.5);
/* Basic background styles */
background: var(--image-url) center center no-repeat;
background-size: cover;
/* Here's the same styles we applied to our content-div earlier */
color: white;
min-height: 50vh;
display: flex;
align-items: center;
justify-content: center;
}
Here's a CodePen with this implementation as well:
This gives you something we can animate as well (notice what happens when you hover the image), which can be a nice UX delight. You don't have the same control over the gradient, however, so which technique you should choose is depending on the context of your design. It's also been noted that this technique might not be as good for performance, especially on lower end devices. Remember to consider this as well when deciding on your technique.
Thanks for coming to my DEV talk.
Top comments (22)
Nice usage of custom css properties! But I'm worried about the example with
box-shadow
- it could be really heavy for mobile devices.I'd rather avoid that one, and keep it just as an example of how many possible ways we have ;)
Yeah I was wondering if it would cause any perf issues. I’ll add a note about it :-)
This is really great! I just spent waay too long trying to accomplish this using the "old way"--you're a life saver.
Just one question: where do you learn about these sorts of modern CSS approaches? There are a lot of mediocre resources out there (I'm looking at you, W3Schools) and it's hard to know what's worth reading through
Hi! I’m glad you found it useful!
To be honest, most of these techniques I’ve read about in blogs and other non-structured places of learning.
MDN is great as a reference, css-tricks.com is great for picking up techniques, and tympnus is great for inspiration.
and css.christmas is great for, well, small tips and tricks - that was our project for Christmas last year 😄
Thanks for the suggestions!
I have already started reading through css.christmas--very funny! I look forward to reading the rest of it.
Thank you. Save me.
This is very clever! Thank you for sharing.
Just one question, how well supported is the var(--image-url) method?
I can't seem to find it on caniuse.com.
Cheers!
It’s not supported in IE, unfortunately, since it doesn’t support css dynamic properties. Otherwise, it’s well supported.
Ahhh, that darn web browser! haha
That's great news it's supported by modern browsers.
Thanks again, really appreciate the knowledge :-)
Awesome! This really helped me a lot!
Thanks for letting me know! 🥳
Somehow it doesn't work for me with grid. I only get the invalid property value on the style="--........
sorry to hear! Are you using React or something? I'd love to help, but there's not a lot I can do with the information provided ☹️
hi!
truth be told this is my first website and I am a bit in the dark. I have nested grids applied and very minimal styles (just some font, plus the necessary for the grid to map out the grid areas)
I just wanted a nice overlay for the background pic so the text would be more readable (and it would look a bit fancier too)
I must apologize.
one word: TYPO
it doesn't matter how many times I read my code I could not find it. I made my partner read it, and bang!
lesson learned!!
people! make others read your code too!!
I'm sorry, it's working fine!!!
What a wonderful article, THANK YOU!
Thanks for letting me know! 🎉
Could the same result be also archived by CSS filters?
Great question! Yeah, you'd think that by adding a
filter: brightness(0.5)
, you'd end up with something similar. Unfortunately, that darkens everything, including the content in front of the background image.Here's a codepen showing what I mean:
This is really helpful and working perfectly! Thank you so much!
I'm glad you found it helpful! :)
Thnx dude that was helpfull