Objective
What I am trying to do is use HTML like this (I’m removing a bunch of stuff for brevity)…
<input required />
… and CSS like this…
input[required]::before { content: '*'; position: absolute; top: 0; left: 1em; color: red; }
… to get something that looks like this…
… without having to resort to extraneous JavaScript or HTML.
Attempt
Try viewing this demo page in your favorite browser(s). The HTML for each form element is beside the form element itself and the CSS used is (essentially) that which is displayed above.
Demo
Outcome
This doesn’t seem reliably possible, in any modern browser… Which is really amazingly wrong to me…
The generated-content that I want to add is purely presentational, doing nothing more than displaying an indicator to the user so they know which fields they are required to complete. There is nothing semantic about it. This means it should be 100% “CSSable”, not requiring any extra JS or HTML. But, in the latest installation of Chrome 1, FF, IE, Opera, and Safari 2, nothing is added to the page…
Inspecting the elements that do and don’t display the generated-content in Chrome DevTools, you will see that the elements that do display the generated-content do not add any special document-fragment
to the page, appearing in DevTools like this…
<input type="radio" required=""> #document-fragment ::before </input>
… whereas the elements that do not display the generated-content, things like text-boxes and drop-downs, do add special document-fragment
to the page, appearing in DevTools like this…
<input type="radio" required=""> #document-fragment <div id="inner-editor"></div> </input>
Note that special <div id="inner-editor"></div>
in there, instead of my special ::before
… It’s like DevTools is overwriting my generated-content with theirs?
Wrap-up
Based on the nearly perfect blocking of this behavior by all the major browsers, I would have to assume it is intentional (which would mean that the few elements that do allow this behavior in Chrome would have to be oversights?). Perhaps somewhere in the labyrinth that is the HTML/CSS Specs there is a bit saying browsers shouldn’t allow generated-content on form elements, for some reason. Maybe that reason even makes sense.
But to me, this is very frustrating, because this is a perfect example of what should be done via CSS, but cannot…
So, happy unnecessary-JS-or-HTML-adding,
Atg
1 Chrome actually does add the generated-content, but only for radio
, checkbox
, progress
, color
and file
input types.
2 Update, :
These results are all on Windows 7 Pro, I will try to add iOS tests soon. In the mean time, I have been able to test the demo page on the following mobile device/browser combinations:
- HTC Desire S, Android 2.3.5:
- default browser: these work:
radio
,checkbox
, andfile
- Firefox 27: nothing works
- Opera Classic 12.10: nothing works
- Opera Mini 7.5.3: They all work!! Except for
select
andtextarea
(even all the buttons worked!)
- default browser: these work:
- iPhone 4, iOS 6.1.3:
- default Safari: They all work!! Except for
select
,textarea
andfile
and any form of button :-/ - Chrome 32: They all work!! Except for
select
,textarea
andfile
and any form of button :-/ - Opera Mini 7.0.5: They all work!! Except for
select
andtextarea
(even all the buttons worked!)
- default Safari: They all work!! Except for
Yes this is per specification. Form elements and images are “replaced elements” https://developer.mozilla.org/en-US/docs/Web/CSS/Replaced_element – discussed in length here why it doesn’t work http://www.red-team-design.com/css-generated-content-replaced-elements. TL;DR – browsers have different ways of implementing replacing, and adding :after to that would have worsened that.
Thanks, Chris, that second article is a gem, especially when you get down to Lea’s and the others’ explanations.
Doesn’t mean I like it, would be really great to be able to identify those required elements more easily, but at least I get it now…
This quote from @chriscoyier really drove home the definition of
:before
and:after
, which was not right in my head:Cheers,
Atg
The reason this doesn’t work is actually quite simple: The
::before
and::after
pseudo-elements render inside its parent element. You could have found this via a fairly simple Google search, which would lead you to this StackOverflow answer.Basically, if you try to render a
::before
pseudo-element inside a self-closinginput
element, then it won’t work, since you can’t nest any elements inside aninput
. It won’t work forselect
because everything nested inside those would have to render as anoption
, which is not what the::before
pseudo-element is.See this JSFiddle for a simple demonstration of the
::before
pseudo-element’s position in the DOM. If you inspect the DOM via Chrome’s developer tools, you’ll also see how it renders: Inside thediv
Yeah, thanks, Joey, I think all this was covered in the last comment.
Cheers,
Atg