You are here

How to vertically center text in an absolutely-positioned div over an image

(Note that this technique is totally deprecated by Flexbox and CSS Grid, but it's interesting for historical purposes, I guess).

If you've ever tried to do it, you know that vertically centring elements in CSS is notoriously difficult. Horizontal centring is a relative breeze, but vertical is another story.

I often have a use-case where I want to vertically centre some text on top of an image, say for a site banner. For example:

Vertically centred text on banner image

I have a few requirements:

  • I want the image to be an actual image, not a background image, because I want the whole image to display and to behave responsively; I don't want to have to set a width and height
  • I want the text to be actual text, not part of the image, for searchability and usability
  • I want the text to stay perfectly vertically centred on the image, no matter what size the screen or the image are

Up till now I've done it with absolute positioning. I've created a div that contains the text and given it absolute positioning and a percentage distance from the top or bottom of the containing div that approximates vertical centering. But this doesn't work so well. One, it's not exact; and two, it starts falling apart as soon as you start resizing the screen. I've had to resort to several media queries to keep it sort of approximately vertically centred at most sizes.

This is a sloppy solution, at best. So when I was faced with this scenario yet again with my latest project, I decided to try to crack it and come up with something better.

What I came up with works in the latest versions of Firefox, Chrome, and IE 11. For my purposes, that’s good enough, but it’s not exhaustively tested, so your mileage may vary.

It’s totally responsive without using media queries, although you may need to use media queries at smaller sizes to reduce the size/padding/line-height/whatever of your text to keep it from spilling out of the container (or even remove this element entirely depending on how much text it contains and how important it is).

The only drawback is it does require a few extra divs. If you’re a purist that way, this may not appeal to you. Maybe someone’s done this a better way, but this is my solution and it works for me.

That said, let’s get down to how to do it.

Here’s the HTML:

<div class=”banner”>
<div class="banner-text-container">
<div class="banner-text-table">
<div class="banner-text-table-cell">
<p class="banner-text">AUTHENTIC KOREAN COOKING WITH A <span class="pink">KICK</span><br />
<span class="pink">We’re crazy about REAL Korean food</span></p>
</div> <!-- /banner-text-table-cell -->
</div> <!-- / banner-text-table -->
</div> <!-- /banner-text-container -->
<img src="/images/banner.jpg" alt="Homepage banner" />
</div> <!-- /banner -->

The first thing we need to do is absolutely position banner-text-container on the containing banner div, so it goes on top of the image and covers it completely:

CSS:

.banner {
position: relative;
}

.banner-text-container {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}

Setting top, right, bottom and left positions to zero makes the text container stretch to cover the whole of the containing div. Alternately, this also works:

.banner-text-container {
position: absolute;
width: 100%;
height: 100%;
}

We then need to set table display properties on the next two divs. This is so we can apply vertical-align: middle to the text. Vertical-align: middle works on divs with display set to table-cell, but unfortunately we need a parent div with display: table. We can’t set this on the absolutely-positioned banner-text-container; it just messes it all up.

.banner-text-table {
display: table;
width:100%;
height:100%;
}

.banner-text-table-cell {
display: table-cell;
vertical-align: middle;
text-align: center;
}

Vertical-align: middle is what vertically centres the text. Text-align: center horizontally centres it as well. This is so the text block sits in the absolute centre of the image along both axis. It will also centre-justify the text, so if that’s not what you’re looking for, you can set text-align: left (or whatever) on the text itself.

Now for the actual text:

p.banner-text {
display: inline-block;
background-color: rgba(255, 255, 255, 0.8);
padding: 1.5rem;
font-size: 1.5rem;
line-height: 2rem;
margin: 0;
}

You can ignore most of that as it’s just styling; the crucial declaration here is display: inline-block. This is both to horizontally centre the text, and to keep any background nicely constrained to the width of the text itself instead of spreading to 100% of the containing div.

If you have a longer chunk of text without line breaks, you may want to set a max-width on it so it doesn’t get too wide. You’ll want to set the max-width in pixels, and a width that looks right at full desktop display. This way it will resize sensibly as the width goes down, but not get too narrow, as would happen if you set it to, say, 40%.

If you try this and run into problems, please let me know! Or if you make some improvement to it, please let me know as well.