Grow your CSS skills. Land your dream job.

Text Blocks Over Image

Published by Chris Coyier

Someone recently asked me about this technique and my first reaction was that it was probably a little too mundane to cover as a tutorial. But then I got to thinking that there is actually a few interesting things happening here and the style is trendy enough people might be interested.

The idea is just to overlay some text over an image, but as blocks that stick out from the left with an even amount of padding all the way around the variable-length text. Here is a screenshot example:


View Demo


The Schematics


<div class="image">

      <img src="images/3754004820_91a5c238a0.jpg" alt="" />
      <h2>A Movie in the Park:<br />Kung Fu Panda</h2>


Putting the image in as a background image of the wrapping div would be easier, but in this scenario I see the images as content, and thus belongs in the HTML. We'll use that wrapping div as a container for absolute positioning.


.image { 
   position: relative; 
   width: 100%; /* for IE 6 */

h2 { 
   position: absolute; 
   top: 200px; 
   left: 0; 
   width: 100%; 

This is going to put our text right up on top of the image nicely, but it doesn't accomplish the transparent black box we want to achieve behind the text. For that, we can't use the h2, because that is a block level element and we need an inline element without an specific width.

Let's wrap the inside h2 of the in a span:

<h2><span>A Movie in the Park:<br />Kung Fu Panda</span></h2>

The use that span to style the text and background:

h2 span { 
   color: white; 
   font: bold 24px/45px Helvetica, Sans-Serif; 
   letter-spacing: -1px;  
   background: rgb(0, 0, 0); /* fallback color */
   background: rgba(0, 0, 0, 0.7);
   padding: 10px; 


When an inline element breaks, the block breaks immediately after the last character in the line, and begins immediately flush left on the next line. Padding, in this case, does not help us.

To solve this, we'll apply some more spans on either side of the <br /> element to simulate that padding.

<h2><span>A Movie in the Park:<span class='spacer'></span><br /><span class='spacer'></span>Kung Fu Panda</span></h2>

Those new spans we'll use to apply padding:

h2 span.spacer {
   padding:0 5px;

Fixing Semantics

At this point the design is accomplished, but there there is an awful lot of extra HTML elements added purely for design reasons. Namely, all those spans. jQuery can jump in and save us here. Simply remove all the spans from the markup, and dynamically apply them like this:

$(function() {

    $("h2 br")
        .before("<span class='spacer'>")
        .after("<span class='spacer'>");



  1. Cipa
    Permalink to comment#

    Thx for the rgba thingy. Didn’t know existed.
    Maybe in the future you can cover some css properties that are not often used but useful.

    • Carlos
      Permalink to comment#

      It rgba thing works only in Firefox, Safari, Chrome. Can you guess which one it doesn’t work on? IE. Not even version 8. Sad state of affairs.

    • V1
      Permalink to comment#

      What else is new ;D

    • That’s why the fallback colour is there – so that a solid colour is used where rgba is not supported. Not as slick, but better than nothing at all. I think this is the kind of approach most people are taking where CSS3 and IE are concerned.

    • he did actually cover rgba in one of his screencasts, i think it was the one on CSS3

  2. Joel Acevedo
    Permalink to comment#

    I did something very similar here ( a long time ago. But not with transparency. Good job!

    BTW. The DEMO in IE7 and Opera is showing a solid black background, no transparency. And In Firefox and Flock, the backgrounds overlap, creating a black line between the two.

    • If you want to make the transparency in IE7 / Opera work, you could set the background with a solid color transparent PNG like in this demo. There is a whole pack of those PNGs in the downloads section.

    • Joel Acevedo
      Permalink to comment#

      Cool! Thanks for the link.

      What about the overlapping issue?

    • Carlos
      Permalink to comment#

      Then there is the (ehem…) IE6 issue. I know, I said it, sacrilege. Great stuff, I am assuming this can be added to WP and a jQuery gallery? Tutorials on that would be awesome too. Thanks.

  3. is this method crossbrowsable?

  4. Permalink to comment#

    Thx Chris, u’re awesome mate !

  5. On Safari 4 the spacer background is overlaying the main span making it darker.

    I think it needs to be reset to not apply the rgba values.

    I’ve a similar problem with a line where the backgrounds overlap too as Joel.

    • Ah yeah, funny how Safari deals with that (rgba overlapping). I think that’s probably the correct way. But anyway, yet another cross-browser things to be aware of.

      You can just adjust the line-height to fix the overlapping line. It looks fine in my browser but it may have to deal with slightly different local versions of Helvetica.

  6. Carolina Imarai
    Permalink to comment#

    Hey, what about the jquery funcionality?. I’ve never figure it out, very interesting, it doesn’t need too put extra html code in the definitive html document. Or am I wrong?

  7. Chris, would using inline-block on the span fix the padding problem? I haven’t tested it but that was my first thought.

  8. Eamonn
    Permalink to comment#

    Hey Chris – cool post as usual!

    Just to query the JQuery (HA HA HAAaaaa…um) – will that not affect all h2 tags on the page? I’m not good on JQuery at all, so perhaps I’m missing something…

    • It’s just for an example here. Of course you should change selector to fit your needs…

  9. gWarez
    Permalink to comment#

    Couldn’t you eliminate the ‘spacer’ spans by just using ‘& nbsp;’ instead?

    • Originally I used just the spans so I could be super accurate with the spacing, but ultimately I changed the demo to use both the spans and &  – as IE didn’t like the empty span (it collapsed it instead of putting the padding onto it).

    • Or couldn’t you just add a margin to the break instead?

      h2 span br { margin: 0 5px; }

  10. Eamonn
    Permalink to comment#

    You could give it a class of course…

  11. Nice effect. Looks good on WordPress.

  12. Thanks! How would you solve the problem of dynamic text and padding though?

  13. I couldn’t see the background unless I had JavaScript enabled (in FF). Does rgba need js to work?
    I was thinking now that you’re using js to make your code more semantic, isn’t it better to remove the image div and h2 completely and add them via js? maybe create the h2 from the alt att of the image…

  14. Cool stuff. I do this on mouseover instead of having the titles show up right away. Initially I’ll use display:hidden and then use the :hover to trigger display: block so that the text shows. Good post. Keep up the awesome work, Chris.

  15. display:none not display:hidden. Sorry.

  16. If you’re adding extra markup (and I’d argue that even the ‘br’ is extra markup), why not just use two span elements – one around “Movie in the Park:” and one around “Kung Fu Panda”.

    You can then position them however you want but the easiest way is just by floating them.

    See for an example.

    • I like this. The only problem I see is that then you pretty much have to include the spans in the markup and not append them dynamically. Unless you get real fancy and come up with some kind of “splitting” character you could parse out with the javascript which would signify where to end one span and start the next.

    • Well, if you’re already forcing a ‘br’ into the markup then you could argue that a couple of spans aren’t a big deal. The examples you’ve used imply a sort of sub-headline anyway, which could justify splitting the text into two elements.

      Your “splitting” character idea would be perfectly doable (you could even use a tab, which would just render as a space by default) but I’m really not a fan of using JavaScript to achieve purely stylist goals and would try to avoid it all costs.

    • Art Lawry
      Permalink to comment#

      I was going to suggest the same. I recently had to try to implement this as a hover style for a client and it worked fine until the line broke/wrapped on its own due to length.

      The only solution I found was to “know” or explicitly trust the break points. If you knew where they should occur and that length would never cause the element to wrap on its own this works fine.

      However, if you already know the break points, wrapping each in their own span with display:block and float allows the effect with empty spans or breaks.

      We actually had to scratch the implementation because the content we were using was too dynamic to know where the breaks would occur.

    • I took the idea a bit further as you and @Hassan said. Unfortunately, I’m not a jQuery guru so here’s what I came up with:

      The HTML:

      The &#xa; is actually a new line, so I guess it’s safe and as I recall it looks pretty nice in IE; don’t get me wrong, I’m a Mac guy and I’m unable to test :D

      The JavaScript:

      $.each($("div.image img.text-block").attr("alt").split("\n"), function(){
      $("div.image h2").append("" + this + "</span");

      If there’s anyone willing to clean this mess up (I’m sure there’s plenty of room to do so), please let me know of your version.

      It’s an interesting article and I must confess I always liked the labels on top of images.

      Thank you very much for posting it.


  17. Permalink to comment#

    I literally just published this yesterday – I wrote an image overlay plugin for jQuery that utilizes a technique like this, except in a more dynamic fashion. Check it out here:

    • Very cool Jon! I was thinking about plug-in-izing this as well potentially for a screencast. Not trying to compete or anything =) — just think it’s a nice simple example for explaining the concept of plugin creation.

    • Federico Reinoso
      Permalink to comment#

      I really like to see that

    • Hi Jon, its brilliant. I was also thinking of something like this. And you gave me ready output. Thanks a lot.

  18. Billy White
    Permalink to comment#

    What about:

    /* for IE */
    /* CSS3 standard */

    Filter works in IE7/8 and the CSS3 Opacity standard works in all other browsers, including Opera 10. You can easily apply transparency to images and background colors. In fact, W3 has a really simple basic tutorial (where I lifted the above) and outlines how to use both:

  19. Alexis M
    Permalink to comment#

    is it possible to make a whole div (which has an image as a bg) to look transparent against the background image of the body tag?

    this is what I have
    body {
    background:url(IMG/site/clouds.jpg) no-repeat;
    #main-content {
    background:url(IMG/site/trans.jpg) repeat-x bottom center;

    Nothing happens when I stick the ” background: rgba(0, 0, 0, 0.7);” in front of the main-content selector. Any Ideas?

    • Laney
      Permalink to comment#

      I’m pretty sure the “background: rgba(0, 0, 0, 0.7)” doesn’t actually make the div transparent, instead it specifies a background colour which is transparent.
      So you would need to either make trans.jpg into a transparent .png or add “opacity:0.7;” to #maincontent. Both of these are problematic in IE, though (see Billy White’s comment above)

  20. I really like the look of the text block over the image. Looks very clean.

  21. Very cool. I like the look and design of it.
    Thank you very useful.

  22. Helen
    Permalink to comment#

    I use this technique for maps. If you want to to have the legend of a map clickable, put a map image (that contains only the contours but no legend) as a background of a relative div and an unordered list inside of the div. The ul contains all the map point and cities. Each list item should have an id and be positioned absolutely. For instance: If you have a map of the USA put list-item Seattle 5px top and left and Boston 5px top and 200 px left …

  23. Toby
    Permalink to comment#

    Shouldn’t something like this work for the IE666 transparency?

    h2 span {
           zoom: 1;
  24. Seb
    Permalink to comment#

    Ha! An actual CSS article! Nice one.

  25. Isn’t this possible to do by setting the z-index of the overlapping text and not using javascript?

  26. Permalink to comment#

    How would I get the width of the text overlay to be the same as the div it is in, seeing as spans are inline elements?

    Any ideas?

    • Just don’t use the spans at all then, style the h2 the way the spans are. The reason spans are used at all is to NOT have them stretch the entire width.

  27. Chris, thanks very much for getting back to me on this.

    The method that I had trialed out earlier was somewhat similar but I couldn’t get the background to wrap around the text. Expect a blog post from me soon sharing the link love.

  28. Ina
    Permalink to comment#

    Hey Chris,

    this is a great trick. I started web design about a year ago and your video tutorials have helped me a lot. I especially like this text over image trick. I’m sure i will use it on some image for my friends at home…


  29. Permalink to comment#

    Very handy. thanks for posting this.

  30. First off, nice new comment thingy! I know it’s nothing big but I always appreciate these small little changes and you notice it for a while.

    Cool post, not something new but I always use this technique (position absolute in relative) to put play icons on thumbnails and such and it works great.

    I’m thinking that you can also use negative margins to get the same effect, and I’m pretty sure every browser would react the same way to it and there would be less markup (hopefully) although it’s mostly a hack, and when I saw your post, I just said to myself “position absolute, definitely).

  31. I got this to work successfully at my site, in a slightly different manner. This version doesn’t use jquery or spans, but also doesn’t look exactly the same.

    Note the 2nd slide in the scroller at the top, it shows a multi-line version.

    The only changes i made were a replacement of the <h2><span> with a single <div>, then a bit of adjustment to the CSS to make it work with the new change.

  32. Nice post as usual. Even though you said that this was possibly too basic, it was certainly worthwhile, as I picked up a couple of things, such as the use of jQuery to clean up the code, and the rgba with the alpha channel.

    From reading the comments, I see it doesn’t work in IE, well I must say, I’m shocked! not!

  33. Permalink to comment#

    hi there!
    i’m trying to use this tutorial but when i inserted the function into my functions.php… when i next visited any of my site’s pages, this came up:

    Parse error: syntax error, unexpected ‘(‘, expecting T_VARIABLE or ‘$’ in /home/another8/public_html/ on line 28

    what am i doing wrong?

  34. Permalink to comment#

    btw im trying to use it with wordpress, is the thing.

  35. I just have to say this: such an ill use of h2! That souldn’t be a header should it?

  36. Nice style

  37. nice job..thank you

  38. Hi!

    I’ve been looking for something like this. But I’m not sure if you noticed, but on the demo page, there’s a black line between the tow text lines. And I couldn’t find any way to fix it.

    Any idea?

    Andor Nagy

  39. Permalink to comment#


    I am trying to apply the trick with a slideshow… but it does not work on my page :

    The caption stays behind the slideshow.

    I dont know how to put the caption at foreground ?

    Kind regards

  40. Robin
    Permalink to comment#

    I tried this on my site and it worked beautifully. But, unfortunately the CSS was applied to ALL of my h2’s on the page. What do I need to do to contain it and keep it only to the one h2?


  41. Sattran
    Permalink to comment#

    Put a separate class for h2, which u want to change for ex: when u write css for

    .required { 

    it will effect only that h2

  42. Carly
    Permalink to comment#

    Hello – I am trying to do something very similar but would like to get the image to align right, with the text box (if you will) aligned left. And then, if possible, I’d like the text box to span the width of the container. So for example if the container is 900px and the width of the photo is 600px, the black transparent box would span the entire 900px width.

    Sorry for the long explanation – any suggestions?


  43. Nathan C
    Permalink to comment#

    Got a question about this. I am pulling a post title on top of a featured image in a slider. I have a class applied that makes the title wrap into two lines by limiting the line width. My span works but has the ugly spacing issue. Since I can’t add more spans in the php what do I do to fix the ugly aesthetic issue?

    .title {
    z-index: 20;
    position: relative;
    top: 255px;
    width: 15em;
    margin-top: -85px;
    line-height: 1.75;

    h1 span {
    color: #fff;
    background: rgb(0, 0, 0); /* fallback color */
    background: rgba(0, 0, 0, 0.7);
    padding: 10px;
    margin: 0px;

This comment thread is closed. If you have important information to share, you can always contact me.

*May or may not contain any actual "CSS" or "Tricks".