CSS generated content on replaced elements

Using pseudo-elements like :before and :after helps you specifying which content should be inserted before (or after) the content of an element. Replaced elements like input or img have no content, therefore, you shouldn’t be able to use generated content for them.

But, there’s almost always a but, it seems that you can use generated content on a number of replaced elements that varies from browser to browser. This is something that has intrigued me lately and that’s why I decided to write down this article.

CSS generated content on replaced elements preview

What is a replaced element?

An element whose content is outside the scope of the CSS formatting model, such as an image, embedded document, or applet.

This is an excerpt from the specifications, but to keep it simple, just remember that a replaced element is any element whose appearance and dimensions are predefined without any use of CSS.

Some replaced elements:

  • <img>
  • <input>
  • <select>
  • <textarea>
  • <object>
  • <br>
  • <hr>

Getting back to our topic

I’ve seen a lot of cool demos like this, this or this one that work only on Chrome (at this time) whilst according to specs, the interaction of :before and :after with “replaced elements” is not defined.

More than that, it seems that all these Chrome examples have something to do with using Shadow DOM.

Shadow DOM

What the Heck is Shadow DOM?. That’s exactly my first question that came up in my head when I first heard about this. All the above examples I mentioned about are based on this WebKit only ability to control a shadow DOM subtree of an element.

I agree it might sound a little overwhelming, and that’s why I recommend you to read Dimitri Glazkov‘s article.

What people say

The question is: Is this technically a “bug” in WebKit, or should we expect this to be implemented in other browsers too?

For this article, I had the pleasure of getting feedback from some of the best web developers I know:

Yes, although convenient, this behavior is technically a bug in these engines. I think extending :before and :after to apply to replaced elements was discussed in the CSSWG and rejected, because it’s not consistent with the way these pseudos work for non-replaced elements (they are not treated like siblings, but like children).

The Generated & Replaced content module used to define something that would help solve this problem: an ::outside pseudo-element, that could also be combined with ::before and ::after, essentially mimicking what these engines do for replaced content. Unfortunately, there are no implementations (apparently it’s hard to implement) and this draft is not actively maintained any more (last update was at 2003!) and thus, is now obsolete. I really hope someone picks it up and starts working on it, this kind of functionality is badly needed.

That’s pretty interesting. I wasn’t aware that WebKit allows that to happen. My reaction to that is that it’s a bug, and it shouldn’t work. The spec says “This specification does not fully define the interaction of :before and :after with replaced elements.” But, technically, that’s not really true. The interaction (which is to say, no interaction) is defined when the spec says:

“the :before and :after pseudo-elements specify the location of content before and after an element’s document tree content…”

A replaced element does not have any “document tree content”. So according to the spec, replaced elements shouldn’t allow pseudo-elements to be placed “inside” them. Also, if it works for an input element, then it should work with all input elements and also img tags. But it doesn’t work with “type=submit” or images, so it seems inconsistent to me.

  1. The :before and :after pseudo-elements would probably be more intuitive if they were called :prepend and :append instead, since I think that better describes what they do.
  2. I don’t use Chrome much, so I wasn’t aware that it allowed generated content on replaced elements. Other Webkit browsers don’t so it’s a bit strange that it does.
  3. It’s hard to tell what other browsers will do, but it doesn’t look like it’s coming to Firefox at least: https://bugzilla.mozilla.org/show_bug.cgi?id=241985.

The way I think about it is this:

It’s not “before” and “after” the element, it’s “before” and “after” the content inside the element.

Pseudo elements aren’t actually in the DOM, but that’s how they behave.

So if you think about a checkbox: <input type=”checkbox” />

Where does the content you are adding go? There isn’t any “inside”. As such, those elements are called “no content” elements.

Conceptually, it makes sense for me for pseudo elements to not be allowed on elements like that.

Practically, with those awesome demos you have seen, it makes for some pretty cool possibilities. I just wish it was defined better, or there were pseudo elements like :outside and :inside that accommodated those things (there were, but they were poorly defined in the spec and removed before any browser implemented them).

If I could control the world, I’d make :before and :after stop working on no content elements and get the pseudo elements and shadow DOM specs moving along so we can make those demos work in a standard way.

Also, you may want to check this cool presentation on pseudo elements made by Chris.

Well, as far as I know… :before/:after pseudo elements were never intended to be used as layers for additional visual styling and just for adding additional text in front or after some content. So it’s more of a hack. I guess the reason why lots of people ab(use) them for purely visual effects is the desire to keep the markup clean. I mean you could easily add a couple <span>’s, but that just feels unnecessary.

There are two things that would make it more useful:

  1. Have unlimited pseudo elements and not just 2.
  2. Better browser support for transitioning them. Works in Firefox but not in WebKit.

Maybe an alternative will be Web Components.

Well the spec does say that the implementation is undefined as yet, so I don’t think it’s a bug so much as a bit over-keenness on WebKit’s part, perhaps…?

It would certainly be really cool to have a) a solid definition and b) that definition to allow for content on replaced elements.

I think it makes sense that this is not defined as
if we look at the current specs we see that:

  1. The :before and :after pseudo-elements specify the location of content
    before and after an element’s document tree content.
    http://www.w3.org/[..]before-after-content
  2. A replaced element is an element whose content is outside the scope of
    the CSS formatting model [...] The content of replaced elements is not
    considered in the CSS rendering model.
    http://www.w3.org/TR/CSS21/conform.html

I think we could argue that #2 does not say for sure that pseudo-elements
could not be used with replaced elements (unless content is seen as a
whole), but IMHO, #1 is less subject to interpretation as it states that
pseudo-elements specify the location of content in relation to the *document
tree content* – and IMO, there is no such thing here.

I usually think of those as void elements, this helps me accept the fact
that I can’t use replaced elements with pseudo-elements. As it is easier to
conceptualize looking at the difference in syntax (<element/> versus
<element></element>)…

Technically, replaced elements have no content, and so therefore :before and :after shouldn’t apply in that case. But I also feel that the new possibilities afforded by the new shadow DOM hold vast potential and should be explored further.

Then again, it could well open the doors to chaos and anarchy if browsers start doing whatever they want, outside of W3C specification.

I think for generated content, any interaction is coincidental and problematic, because the spec is basically silent on the matter, and that always causes issues for browser vendors. The fact that the CSS3 version of the spec hasn’t been touched since 2003 doesn’t give me much confidence that will change anytime soon.

Anything with interaction should be really well-defined and clear, because inconsistencies in “How Stuff Works™” between browsers are really awful to deal with for developers—just look at the mess of built-in date pickers for the input type=”date” feature in HTML5.

Whatever interaction currently is possible with CSS generated content in browsers should be, if you ask me, treated as a bug with potentially confusing side effects.

One step ahead?

As you already may know, the Chrome Settings area is made with pure HTML. You may find interesting the fact that their developers took this one step further, as all their checkboxes and radios are customized using this approach.

Chrome's form elements example

So, it sounds to me that they are quite serious about further developing this technique.

A test case

While doing some research on this article, I reminded I read a while ago an article by Chris Coyier regarding styling horizontal rules. The hr is a replaced element, as Chris also states in that article. Now, the interesting part here is that pseudo-elements work on hr element whilst they shouldn’t. And this happens on all major browsers too. So, it’s pretty interesting huh?

I decided to create a rough test case for generated content on replaced elements and the results are a totally mess if you ask me :)

View test case

Also, Divya Manian was so kind and made a more advanced test for pseudos on replaced elements.

As you can notice, the results across modern browsers are inconsistent and this does not help us at all.

Conclusion

I think all these misunderstandings are caused by the way specs were written on this matter. We should all agree the spec could have been more explicit here.

Let’s just hope this situation will clarify soon so we can move further and make those cool demos work according to some clear standards.

Last but not least, a big thanks to all above people who’ve responded to my initiative. You rock!

7 thoughts on “CSS generated content on replaced elements

  1. Opera also allows these pseudo-elements for nearly everything, including replaced elements.

    I think that the main problem with ::before/::after for IMG, INPUT etc. is not the fact that they are replaced but the fact that they are void elements, which means that they could never have any content, before/after which anything could be inserted. But Opera seems to have implemented the CSS3 Generated Content draft which allows (in contrary to CSS2.1) the ‘content’ property for any element (not just pseudo-element). And if an empty element like IMG can have content (even generated) in certain situations, allowing it to insert something before/after begins to make sense.

    I used this feature of Opera to workaround its bug with rounding corners of images (prior to v. 11.60): http://jsfiddle.net/XVtMh/146/

    WebKit also allows ‘content’ for IMGs, but it has different effects in WebKit and Opera: in WebKit the element effectively turns into the simple inline container like SPAN, losing all image-specific context menu options etc., while in Opera these options are still available. I don’t know which behavior is more correct but I find the Opera’s behavior more convenient and intuitive.

  2. That one really works on Modern browsers… Expect some glitches on IE … Let s forget the existence of IE and move forward. Amazingly Opera showed smooth transitions when compared to others.

  3. This all points to the biggest issue we battle with as web developer’s, the ever evolving browser and the vendor’s not always singing off the same hymn sheet or not at the same time. Just makes life hard for projects where the user base dictates what you technology you can use :(

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>