Running on :empty
Published 17 years, 4 months past
While kicking around some ideas at the CSS workshop I led in London, I ran into some interesting problems with :empty
. If you’re not up on what that one does, it’s a way of selecting elements that have no children, including text. To quote the formal definition:
The
:empty
pseudo-class represents an element that has no children at all. In terms of the DOM, only element nodes and text nodes (including CDATA nodes and entity references) whose data has a non-zero length must be considered as affecting emptiness; comments, PIs, and other nodes must not affect whether an element is considered empty or not.
So kindly consider:
*:empty { padding: 0.25em; outline: 1px dashed red; background: yellow; }
We (re)discovered something very strange right there in the workshop: given the preceding rule, Firefox applies it to the body
element—which is, quite clearly, not empty. You can check out the fun on my bare *:empty
test page. From there, we realized that if you prepend a body
to the selector, yielding body *:empty
, the problem vanishes, as you might expect.
As it turns out, this is a known bug in Gecko. It’s been known for about 18 months, in fact. So that’s why I was confused by Firefox’s behavior.
The other problem is that :empty
in Firefox matches img
elements, whether or not they cause an image to successfully load. You can see this happen on the same test page. While it’s true that img
is an empty element, it’s still bringing in content, so from a common-sense point of view it seems that :empty
ought not to match.
In re-reading the description from the specification, though, I can’t justify my common-sense interpretation—an img
element has neither element nodes nor text nodes, so far as I’m aware. The same is true for form inputs, even text inputs that have pre-filled value text; but not for textarea
s that have pre-filled content text. But then br
elements should also be selected by this rule, and they apparently don’t get matched at all, as the test page shows. Bwuh?
Well, maybe it’s a bug. What concerns me here is that the definition of :empty
basically requires that things like images and inputs always be selected, which strikes me as kind of useless. After all, if I wanted to select all images, I’d write img
into my selector; ditto input
for inputs.
My thinking is that the description should change to state that application of :empty
is restricted to non-replaced elements. Alternatively, it could be redefined to only apply non-replaced elements and to empty replaced elements that don’t either cause something to load or have values associated with them.
Which seems a better solution to you, and why? Or, if you see a better approach than either of those, what is it?
Comments (23)
Empty seems to be quite a confusing term here, I’d rather see it separated into a couple of more useful things. One would be a way of selecting text-only nodes.
Meyer–
Playing the devil’s advocate, since it appears the pseudo-class empty is not supported by Opera or IE, why worry about it, with arguments of principle aside and providing that I am not overlooking some practical uses of it.
On theoretical purposes, possibly, the latter definition you provided.
Logic: I am a guy and at the moment, more is better.
Did I just stick my foot in my mouth? Your test example doesn’t work in Opera or IE. I have no direct knowledge whether :empty works in Opera or IE. Just ran your example through both to try to gain better understanding of a CSS element of which I was not familiar until your post.
I don’t have anything like your depth of experience here, but it seems to me that what I’d like is to select any element which could have children, based on the DTD, but doesn’t. I don’t think (haven’t actually checked) that a well-formed
img
element can have children, so I wouldn’t expect it to be selected. Dittoimg
,hr
, and anything else defined as<!ELEMENT foo EMPTY>
.This would mean that a
textarea
with pre-defined content would be selected, while aninput
with pre-defined content wouldn’t be. That’s not ideal, I guess.Elements defined
<!ELEMENT _ EMPTY>
shouldn’t be selected and I’d addtextarea
to that to keep it in line with the other form elements.It seems to me that
textarea
is implicitly an empty form element. Given that though, puttingtextarea
on the list of non-selected elements begs the question: shouldoption
elements also be included considering, liketextarea
, they also can only contain#PCDATA
.Perhaps it’s just easier to only include elements defined
EMPTY
on the “do not select” list.Great question Eric.
a) IIRC Mozilla also has known bugs with styling <br>, so it’s probably not a very good example. Form controls have a number of (mostly intended) styling restrictions as well, so you’d have to design your test case carefully for those.
b) In CSS3 whether an element is a replaced element or not will depend on the value of its ‘content’ property. To avoid a circular dependency, then, a selector that differentiates between replaced and non-replaced content can’t be defined.
Anyway, what would be your use case for a *:empty selector? I can’t think of any cases where you wouldn’t want a tag/class/id selector in there anyway.
Styling for content that isn’t there is kind of counter-intuitive to me.
Can I ask what the idea was that prompted this discovery?
While the questioning is interesting, is perfectly understandable the fact that it selects
img
,input
& other (by definition) empty elements, after all, they are empty.This may have no sense to you if you take the
:empty
statement outside of the DOM context, but at the same time is perfectly logical inside it (thebody
bug, of course, it’s just that: a bug).I do understand, however, your logic, but it reminds me of the problems measuring accessibility, which is subjective but measured by a computer. In this case I see it as the other way around: a computer tool for DOM manipulation (XPath) used in a subjective way :).
I forgot to summarize…
This is a problem of context. The
:empty
statement applies to empty elements. As far as I know it doesn’t take into consideration their attributes, and frankly I see no reason to do so, even more since you can reference them directly with[attribute]
.It seems like this is a bit open to interpretation. To me,
empty
means “no children”. So, if I want to find, say, all emptyp
elements, I could usep:empty
for my selector. In my mind, it would make sense then for*:empty
to apply totextarea
elements if there is not text in them. If there is text, then they wouldn’t be selected.I also tend to agree with Ralph that the attributes wouldn’t be considered, therefore
img
elements would always be selected, as they have no child nodes.Perhaps a new pseudo-class is needed for the situation you bring up where an
img
element’s data successfully loads or not. Something likenocontent
? I guess there would be some overlap betweenempty
and something like that, though.Here’s another little weirdness in the spec:
<object …></object> is matched.
<object …><param name=”foo” value=”bar”/></object> is not.
The DOM-Inspector tells me that the box you see is not body but the empty node from the head.
Or am I missing something?
Err, “the empty link-Node”.
(Should have known that the blogsoft would filter it completely when written as tag)
I’d actually expect that, Sebastian, since in the first case
object
has no child nodes, and in the second case it does. On the other hand, if theparam
isn’t selected, then that’s either total spec weirdness or a browser bug. To test it, you’d have to make sure thedisplay
of bothobject
andparam
was something other thannone
.I think you are missing something, Nils. In the first part of the post, I’m talking about the
body
being inappropriately selected, as documented in the Bugzilla entry. In the second part, I’m talking about selection of empty elements that are DOM-empty but not render-empty, like images and inputs and such. I didn’t say anything about that appearance of the top-most tiny box on the test page, which is indeed thelink
element and (given that we accept HTML-empty elements are selected by:empty
) should be seen, given the rest of the styles.Interesting… I wonder if we should see the
link
element. Why wouldhead
children be rendered at all? What sense does it have? I even find thebr
element not being shown (in the test page) has more sense, I mean, this is a replaced element, isn’t?. If that’s so, then most of CSS rules don’t apply at all —though I still wonder just what kind of elementbr
is and how is it implemented across browsers.This leads me to another issue: should we be grateful with browser vendors that they give us some little freedom there (with replaced elements)? I mean, do you think this is positive at all? —If you ask me, I think it is, I tend to alter the border and background of
input
‘s, for instance. What’s your opinion?Ralph, the
head
children are being rendered because I opened up thehead
in order to display the embeddedstyle
element in the upper right corner there—and, by the by, to see if emptyhead
descendants likelink
would be matched.As for allowing us to style replaced elements, I’m ambivalent, for many and sundry reasons.
What happens to ?
I agree that logic should rule what is affected and not.
Meaning that elements that render content on the page are NOT :empty, such as images and :s.
:empty elements are what the validator warns you about, I.E. elements that, possibly, contains only white-space nodes, comments or PI.
Not what is referred to as EMPTY elements in the spec.
I just found a name for my nascent web design business:
head:empty
:empty seems incompatible with html/xhtml, since whitespace is supposed to be ignored. For instance, if the html/xhtml spec says that:
<p></p>
and
<p> [spaces] </p>
are supposed to be equivalent, it seems to me that the browser could legitimately collapse the distinction b/t the two in the DOM model before applying the CSS styling. …Or I could be wrong on that – perhaps the spec says that one space is the same as twenty spaces, but any number of spaces is different then zero spaces…
Would there be issue with any element that is self closing not being considered ’empty’? I see no benefit to targeting ‘br’ or ‘hr’ with :empty.
As for ‘img’ and ‘input’, it seems to me that an attribute selector checking their ‘src’ and ‘value’ properties respectively would suffice when you wanted to see if they were truly ’empty’.
This pseudo-class seems primarily targeted at paragraphs and divs without text, as I see little in the way of other practical uses for it (perhaps anchors?).
I’ve just run into a situation where :empty not applying to input elements definitely seemed to go against common sense. I’m using javascript to find the first empty input element in a form and set focus to it. As Christopher mentioned, the attribute selector works just peachy instead–
Here’s the selector string I’m using (with jQuery)
$("[@value='']:input:visible:enabled:first")
I agree that using on
[@value='']
or[@src='']
makes more sense then the common-sense approach to :empty, although initially I was also a little thrown.So: you led me to playing around with this :empty stuff, eh? Just because I have a new job, and now I have to play catch-up… grrr….. ;-)
Until now, I have completely ignored the :empty pseudo-element. A quick glance at the spec tells me that this might be a fairly handy feature for someone who has to populate a table with some dynamically-generated (tabular, of course) data, and wishes to highlight empty cells for whatever reason.
So I did a quick-and-dirty experiment. (Granted, the contents aren’t dynamically generated, but for this experiment, it matters little.)
Firefox 2.0.0.6/Mac handled this one with ease, albeit with a couple of minor rendering glitches; Safari 2.0.4 (419.3) blew it to pieces, styling every td as empty instead of just the empty ones. And the value of the very last cell, the number 12, is not within a p tag like the rest of the cells…
Veddy intrestink.
For the specific case of empty tables cells there’s a special rule already in CSS2.1:
empty-cells:show|hide
. Although, yes,:empty
is more flexible.