Overlay Text on Responsive Images

When putting text over an image, readability can be a concern. By combining the responsive HTML5 <picture> container with some best practices for image overlays, we can achieve a good result.

The initial decision when adding text over an image is color. Black or white on their own aren’t very good options, as they’re likely to have a low contrast with the image.

<figure>
  <img src='/images/ricardo-gomez-angel-metal-abstract.jpg' alt='Metal abstract, Basel, Switzeland by Ricardo Gomez Angel' />
  <figcaption><span style='color: black;'>Black</span> & <span style='color: white;'>White</span></figcaption>
</figure>
figure {
  width: 100%;
}

img {
  width: 100%;
  min-height: 100%;
}

figcaption {
  position: absolute;
  bottom: 0;
  color: white;
  margin: 0 auto;
  font-size: 3em;
  width: 100%;
  text-align: center;
}

While it’s certainly an option to add a bright, contrasting color such as hot pink or bright red with a border, there are more subtle ways to accomplish this.

For this, we can look toward black and white text overlays. These can be difficult to read over many images, but by adding a linear-gradient background over the image of black to white in the case of black text at the bottom or white to black for white text at the bottom of the image an attractive, readable text overlay is possible. To further improve readability, especially as the text reaches larger sizes or is longer, covering more of the image and benefiting less from the linear-gradient, a text shadow can be used which offsets the text even more from the image.

All of this is usually achieved with a <div> wrapper around <img> and the text element (<p>, <h1>, <span>, <div>, etc.) and an ::after pseudo-element sized to 100% of the parent and absolutely positioned over the wrapper:

<figure>
  <div class='image-wrap'>
    <img src='/images/ricardo-gomez-angel-metal-abstract.jpg' alt='Metal abstract, Basel, Switzeland by Ricardo Gomez Angel' />
  </div>
  <figcaption>Text with Overlay</figcaption>
</figure>
figure {
  position: relative;
}

.image-wrap::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  background: linear-gradient(to bottom, rgba(0, 0, 0, 0), rgba(0, 0, 0, .6));
  height: 100%;
  width: 100%;
}

img {
  width: 100%;
  min-height: 100%;
}

figcaption {
  position: absolute;
  bottom: 0;
  color: white;
  margin: 0 auto;
  font-size: 3em;
  width: 100%;
  text-align: center;
}

HTML’s <picture> element allows multiple representations of the same image to be offered to the browser, which it can select based on size, file type, or other preference. Each option is provided as a <source> child element specifying the srcset which contains URLs to the resources and information to help the browser to determine which source to select. The <picture> element also requires an <img> element which acts as fallback both for cases where no matching <source> is found by the browser and when the browser does not support <picture>. This makes it an excellent choice for providing responsive images at different file and image sizes and aspect ratios, as well as maintaining backward compatibility as older browsers will simply ignore <picture> and <source>.

Because the <picture> element actually wraps the <img> (whose styles apply regardless of which <source> is selected), we can do away with the more common but less semantic div.img-wrap above. The styles remain the same, with selectors for .img-wrap changing to picture.

<header>
  <picture>
    <source ...>
    <img src='/images/ricardo-gomez-angel-metal-abstract.jpg' alt='Metal abstract, Basel, Switzeland by Ricardo Gomez Angel' />
  </picture>
  <h1>Overlay with Picture</h1>
</header>
header {
  position: relative;
}

picture::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  background: linear-gradient(to bottom, rgba(0, 0, 0, 0), rgba(0, 0, 0, .6));
  height: 100%;
  width: 100%;
}

img {
  width: 100%;
  min-height: 100%;
}

h1 {
  position: absolute;
  bottom: 0;
  color: white;
  margin: 0 auto;
  width: 100%;
  text-align: center;
}

Our HTML is much more expressive. The markup does not contain an element that only exists to support the visual display of information. The CSS styles extend the markup to add our nicer overlap.

Further reading:

Photo by Ricardo Gomez Angel