Be careful with whitespace in CSS selectors

If you know me, you probably know that I love CSS, and you may know that Selectors are my specialty. This is the first of, I guarantee, many articles about Selectors to come.

A number of weeks ago, I came across this question. It contains a line of CSS that looks like this:

.responsive  * :not(.fixedSize,.fixedSize *){max-width:100%;}

Notice the rather sloppy placement of whitespace:

  1. Two spaces before the first *, which is not a big deal, but still an unusual sight.
  2. One space after the first *, before the :not() pseudo-class. That’s a big deal.
  3. The complete and utter lack of whitespace between the selector and declaration block, and within the declaration block, a stark contrast to the mess of whitespace in the selector.

Why is #2 a big deal? Because in selector syntax, whitespace that separates any two simple selectors is a descendant combinator. This includes the universal selector *. Think of it like this: in much the same way that

#id .cls

matches any .cls descendant of #id,

* .cls

matches any .cls descendant of, well, any element. In other words, any .cls element that has an ancestor (effectively excluding :root.cls). And since pseudo-classes are simple selectors just like type, class and ID selectors,

* :not(.cls)

likewise matches any :not(.cls) element that has an ancestor.

Compare the example selectors above with these ones:

#id.cls
*.cls
*:not(.cls)

Removing the space results in

  1. a selector matching any element with both the respective ID and class name,
  2. a selector matching any element with the respective class name, and
  3. a selector matching any element without the respective class name.

Notice that there’s no mention of ancestors or descendants. That’s because I removed the descendant combinator!

Also, as you might have guessed by comparing the second and third examples, yes, you don’t actually need the * in front of a pseudo-class. Just like class and ID selectors, as well as pseudo-elements, the * can be left implicit.

Which leads me to another interesting point: while the spec does recommend adding the * in some cases to improve readability:

Note: In some cases, adding a universal selector can make a selector easier to read, even though it has no effect on the matching behavior. For example, div :first-child and div:first-child are somewhat difficult to tell apart at a quick glance, but writing the former as div *:first-child makes the difference obvious.

… if you read that note in the context of this article, suddenly it takes on a completely different meaning!

So, be careful with whitespace when writing CSS selectors! Whitespace generally doesn’t change the meaning of a selector anywhere else, but when the syntax expects you to chain your simple selectors as tightly as possible, it means it.

Here are some related Stack Overflow answers of mine you may be interested in:

Comments are closed.