334

I'm trying to use the :before selector to place an image over another image, but I'm finding that it simply doesn't work to place an image before an img element, only some other element. Specifically, my styles are:

.container
{
   position: relative;
   display: block;
}

.overlay:before
{
    content: url(images/[someimage].png);
    position: absolute;
    left:-20px;
    top: -20px;
}

and I find that this works fine:

<a href="[url]" class="container">
  <span class="overlay"/>
  <img width="200" src="[url]"/>
</a>

but this does not:

<a href="[url]" class="container">
  <img width="200" src="[url]" class="overlay"/>
</a>

I can use a div or p element instead of that span, and the browser correctly overlays my image over the image in the img element, but if I apply the overlay class to the img itself, it doesn't work.

I'd like to get this working because that extra span offends me, but more importantly, I've got about 100 blog posts that I'd like to modify, and I can do this in one go if I could just modify the stylesheet, but if I have to go back and add an extra span element in between the a and img elements, this will be a lot more work.

Joshua Frank
  • 13,120
  • 11
  • 46
  • 95
  • 5
    This is odd, especially since the CSS standard itself gives an example of using :before with an IMG element… –  Jan 10 '12 at 19:20
  • 1
    Possible duplicate of [CSS :after not adding content to certain elements](http://stackoverflow.com/questions/6949148/css-after-not-adding-content-to-certain-elements) – chharvey Nov 09 '15 at 19:59
  • @chharvey: That question was asked after this one. – Joshua Frank Nov 09 '15 at 20:19

16 Answers16

309

Unfortunately, most browsers do not support using :after or :before on img tags.

http://lildude.co.uk/after-css-property-for-img-tag

However, it IS possible for you to accomplish what you need with JavaScript/jQuery. Check out this fiddle:

http://jsfiddle.net/xixonia/ahnGT/

$(function() {

    $('.target').after('<img src="..." />');

});

Edit:

For the reason why this isn't supported, check out coreyward's answer.

Community
  • 1
  • 1
cwharris
  • 17,835
  • 4
  • 44
  • 64
  • 4
    your fiddle is missing. That's the beauty of jsFiddle links. – Roko C. Buljan Apr 15 '13 at 11:38
  • 1
    This answer doesn't explain why img can't have before or after. Also, the proposed javascript solution is not a proper replacement for it. – Jasper Kennis Mar 23 '16 at 14:53
  • 3
    The proposed solution is not expected to work in every scenario. It's simply an example of how one might circumvent the problem. If you'd like to propose a more general solution, feel free to post an answer. – cwharris Mar 23 '16 at 16:26
  • I ended up implementing this solution, however I had an issue. Some content that loads after this runs (e.g. ajax content loaded later) will also need to have this run again. I ran into some lazy loaded assets on a page where I noticed this. This is the best solution to the problem. Unfortunately spent almost an hour trying to debug the CSS issue before searching. – Art Geigel Aug 17 '21 at 15:48
180

The before and after pseudo-selectors don't insert HTML elements — they insert text before or after the existing content of the targeted element. Because image elements don't contain text or have descendants, neither img:before or img:after will do you any good. This is also the case for elements like <br> and <hr> for the same reason.

coreyward
  • 77,547
  • 20
  • 137
  • 166
  • Img tags are clearly elements in and of themselves, and yet we are allowed to insert them before or after other elements using the :before and :after psuedo-selectors. Is this not in the css spec? – cwharris Apr 30 '11 at 17:01
  • 13
    That's a little different. You can insert an image, but not using an image tag — you have to use the `content: url(/img/foo.jpg);` syntax. See http://www.w3.org/TR/CSS21/generate.html for the details. – coreyward Apr 30 '11 at 18:44
  • 5
    Small correction: They insert psuedo-elements, which are more like elements than they are like text nodes. – Chris Calo Oct 08 '13 at 21:57
  • 1
    Generated content becomes part of the Shadow DOM. Those new elements, attributes and text nodes are very real. You can see them e.g. in Chrome's Inspector. – Marc Diethelm Oct 07 '14 at 09:12
  • @coreyward Maybe you will trust [MDN (Replaced Element)](https://developer.mozilla.org/en-US/docs/Web/CSS/Replaced_element) – Marc Diethelm Oct 08 '14 at 16:09
45

I found a way to make this work in pure css:

The I'm just fake content-method

a pure CSS method to enable img:after.

You can check out the CodePen: I'm just fake content or see the source.

Source & Snippet

img {
    /* hide the default image */
    height:0;
    width:0;

    /* hide fake content */
    font-size:0;
    color:transparent;

    /* enable absolute position for pseudo elements */
    position:relative;

    /* and this is just fake content */
    content:"I'm just fake content";
}

/* initial absolute position */
img:before,
img:after {
    position:absolute;
    top:0;
    left:0;    
}

/* img:before - chrome & others */
img:before {
    content:url(http://placekitten.com/g/250/250);
}

/* img:before - firefox */
body:not(:-moz-handler-blocked) img:before {
    padding:125px;
    background:url(http://placekitten.com/g/250/250) no-repeat;
}

/* img:after */
img:after {
    /* width of img:before */
    left:250px;

    content:url(http://lorempixel.com/350/200/city/1);
}
<img
    alt="You are watching the ~ I'm just fake content ~ method"  
/>

Browser support

✓ Chrome 10+

✓ Firefox 11+

✓ Opera 9.8+

✓ Safari

No support

⊗ Internet Explorer 8 / 9

Please test in other browsers

user2226755
  • 12,494
  • 5
  • 50
  • 73
TimPietrusky
  • 778
  • 1
  • 16
  • 27
  • 1
    So basically you just add content:"" to the style of the img tag and :before and :after start working. Nice find, I wonder how cross-browser it is. – w00t Jun 28 '12 at 14:42
  • 2
    I set up an icon test http://jsbin.com/esewow/edit#html,live and I can confirm it doesn't work on IE8 but on Chrome and Safari it works fine. – w00t Jun 28 '12 at 14:53
  • @w00t Yes, that's basically it. Thanks for testing IE8 and Safari. – TimPietrusky Jun 28 '12 at 17:14
  • it doesn't work on IE9 either FWIW :-) IE9 shows the "missing image" icon and doesn't prepend any character, just like IE8. – w00t Jun 29 '12 at 12:16
  • Was the jsbin example ever working in Firefox? Because it doesn't (anymore?) for FF13 or above. It's just showing the missing image icon, like IE. – sp00n Apr 18 '13 at 14:51
  • @sp00n yeah, the jsbin example doesn't work in FF, but the [CodePen](http://codepen.io/TimPietrusky/pen/im-just-fake-content) one does. – TimPietrusky Apr 18 '13 at 18:14
  • 20
    The CodePen example only works in Firefox if the src of the img tag is invalid - that causes the alt attribute to be rendered as an element which actually allows :before and :after. If the img src exists, it stops working: http://codepen.io/anon/pen/EjtDu – thenickdude Aug 13 '13 at 01:45
  • 6
    Stop me if I'm wrong, but it seems it no longer works with Chrome 46 – Gyum Fox Nov 17 '15 at 14:44
  • 6
    **img:before and img:after don't work in Safari at all**. Not even this, which initially had me excited. Good try though. – Wray Bowling Aug 22 '16 at 16:03
35

Due to the nature of <img> being a replaced element, document styling doesn’t affected it.

To reference it anyway, <picture> provides an ideal, native wrapper that can have pseudo-elements attached to it, like so:

img::after,
picture::after{
    content:"\1F63B";
    font-size:larger;
    margin:-1em;
}
<img src="//placekitten.com/110/80">

<picture>
    <img src="//placekitten.com/110/80">
</picture>
dakab
  • 5,379
  • 9
  • 43
  • 67
  • 1
    it's work but in case we need exactly img tag then this solution won't help. – fudu Sep 28 '21 at 09:24
  • Confirmed the `picture` element works on an iPhone. Sad though my browser will support this on the `img` element by itself directly in the future. If the other browser vendors want to make themselves look bad through their arrogance, let them! – John Oct 01 '22 at 10:38
5

Here's another solution using a div container for img while using :hover::after to achieve the effect.

The HTML as follows:

<div id=img_container><img src='' style='height:300px; width:300px;'></img></div>

The CSS as follows:

#img_container { 
    margin:0; 
    position:relative; 
} 

#img_container:hover::after { 
    content:''; 
    display:block; 
    position:absolute; 
    width:300px; 
    height:300px; 
    background:url(''); 
    z-index:1; 
    top:0;
} 

To see it in action, check out the fiddle I've created. Just so you know this is cross browser friendly and there's no need to trick the code with 'fake content'.

Zach Saucier
  • 24,871
  • 12
  • 85
  • 147
truleighsyd
  • 291
  • 3
  • 8
  • It is helpful to apply `display: inline-block` to `#img_container`. That'll make the width of the container equal to the image at all screen sizes. So, you don't end up having `:after` content floating outside of the image. – Anonymous Oct 16 '17 at 07:41
3

I think the best way to look at why this doesn't work is that :before and :after insert their content before or after the content within the tag you're applying them to. So it works with divs or spans (or most other tags) because you can put content inside them.

<div>
:before
Content
:after
</div>

However, an img is a self-contained, self-closing tag, and since it has no separate closing tag, you can't put anything inside of it. (That would need to look like <img>Content</img>, but of course that doesn't work.)

I know this is an old topic, but it pops up first on Google, so hopefully this will help others learn.

BevansDesign
  • 4,908
  • 1
  • 12
  • 14
3

The pseudo-elements generated by ::before and ::after are contained by the element's formatting box, and thus don't apply to replaced elements such as img, or to br elements.

https://developer.mozilla.org/en-US/docs/Web/CSS/::after

Goda A
  • 63
  • 1
  • 7
2

In these cases it is preferable to use the <figure> tag, which allows you to manage the css in an optimal way This way you can use after just on the figure

Example

<div class="exemple">
  <figure>
    <img src="img1.jpg"/>
  </figure>
  <figure>
    <img src="img2.jpg"/>
  </figure>
</div>
Giuseppe Ruffa
  • 441
  • 4
  • 4
1

This one works for me:

html

<ul>
    <li> name here </li>
</ul>

CSS

ul li::before {
    content: url(../images/check.png);
}
Siful Islam
  • 1,906
  • 3
  • 21
  • 31
1

::after may be used to display the fallback image of an image


See the example below, first 2 img tags are point to the broken urls. But the second one displays the fallback image instead of the default broken logo from the browser. However, I'm not sure this's any practical, I find it kind of tricky to get it to work right.

img {
  position: relative;
  display: inline-block;
  width: 300px;
  height: 200px;
  vertical-align: top;
}
img:not(:first-child)::after {
  position: absolute;
  left: 0; top: 0; right: 0; bottom: 0;
  content: "<" attr(alt) "> NOT FOUND";
  border: 1px dashed #999;
  background: url(https://cdn.dribbble.com/users/1012566/screenshots/4187820/topic-2.jpg) center/100%;
}
<img src="https://source.unsplash.com/random/100/75" alt="logo">
<img src="https://source.unsplash.com/random/100/75" alt="logo">
<img src="https://source.unsplash.com/random/100x75" alt="logo">
Loi Nguyen Huynh
  • 8,492
  • 2
  • 29
  • 52
  • here's the result: http://i.imgur.com/TyG2zxy.png . But it's look like it only work for 2nd img only, because it's a broken img. The 3rd img are not broken, so the ::after doesn't work. – fudu Sep 28 '21 at 08:34
  • @fudu Which is what I think is expected – Loi Nguyen Huynh Sep 28 '21 at 10:57
0

<img> is a replaced element and using :before or :after pseudo-elements on it works if the image fails to load and otherwise it does not work. If you intend to have a fallback in case of image load failure,please refer to https://stackoverflow.com/a/71478688/14204452

Nasim B. D
  • 161
  • 1
  • 6
0
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <style>

      #image img{
    display: inline-block;
    max-width: 50%;
    }

      #image::after {
    position: absolute;
    top: 0;
    content: url('https://img.icons8.com/plasticine/100/000000/about.png');
    }

    </style>
    <title>img before</title>
  </head>
  <body>
    <a id="image" href="">
    <img src="https://static.remove.bg/remove-bg-web/5c20d2ecc9ddb1b6c85540a333ec65e2c616dbbd/assets/start-1abfb4fe2980eabfbbaaa4365a0692539f7cd2725f324f904565a9a744f8e214.jpg">
    </a>
  </body>
</html>
-1

Try this code

.button:after {
    content: ""
    position: absolute
    width: 70px
    background-image: url('../../images/frontapp/mid-icon.svg')
    display: inline-block
    background-size: contain
    background-repeat: no-repeat
    right: 0
    bottom: 0
}
subindas pm
  • 2,668
  • 25
  • 18
-2

I tried and found a simpler method to do so. Here is the HTML:

    <img id="message_icon" src="messages2.png">
    <p id="empty_para"></p>

What I did was place an empty <p> tag after my image tag. Now I will use p::before to show the image and position it according to my needs. Here is the CSS:

#empty_para
{
    display:inline;
    font-size:40;
    background:orange;
    border:2px solid red;
    position:relative;
    top:-400px;
    left:100px;
}
#empty_para::before
{
    content: url('messages.png');
}

Try it.

Mr Lister
  • 45,515
  • 15
  • 108
  • 150
-2

Try ::after on previous element.

-4

Just give the Image "position: relative" and it will work