A black room with a dimly lit old school light bulb

How to overlay your background images

  • Kristofer Giltvedt Selbekk

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.