The CSS Working Group recently published the first working draft of CSS4 Selectors. “Wait…CSS 4? I thought CSS3 was still incomplete” you might ask. The spec process changed after CSS2.1. Instead of one monolithic spec, CSS3 was broken down into smaller bite sized chunks. Each chunk or module graduate to the Recommendation stage independently when they’re ready. As each module doesn’t depend on each other (typically. Some related modules might have dependancies, such as CSS3 Selectors on CSS Namespaces) work can start on the next level of the module–if there is demand for a new version–after the current level reaches REC. Therefor work on CSS4 specs will go on in parallel with CSS3 modules. The selectors specification is one such module where there is demand for some new features .
Introducing Selectors level 4
The CSS4 Selectors spec has already seen a large number of revisions since level 3. As it is still the first draft of the spec, it will likely change in numerous ways before it becomes a recommendation itself and there are browser implementations. Lets have a quick dig into the draft and see what has changed.
Moving homes. In-N-Out the spec
There are some selectors that were moved into CSS4 Selectors from other specs, and some that were removed and are looking for a new home:
One of the first things you may notice about the spec is that pseudo-elements are gone from the spec. Don’t worry though, they will find a new home in a different spec in the future. I’m not sure if this will be its own spec or merged into another one. I wonder if this is to prepare for styling the Shadow DOM.
UI state pseudo-classes
While pseudo-elements were moved out, the user interface state pseudo-classes from CSS3 Basic User Interface have been moved into the spec. These target various states that UI components can be in, such as if checkbox is checked or unchecked or if a value is required or not. They were mapped to XForms states in CSS3 UI, but they are in general language agnostic. They’re perfect for styling HTML5 Form elements. I assume they were moved to put all the selectors in one place except pseudo-elements. Makes sense to me.
If you don’t understand what the :scope pseudo-class does then don’t worry. It is quite difficult to understand from the spec, and I had to ask the Lachlan Hunt myself. As far as I understand it, the :scope pseudo-class acts as a placeholder in the selector list. It is replaced by a specified reference element. In the case of Selectors API 2, the element can be specified as an argument in the querySelector, querySelectorAll and matchesSelector methods. This is much easier to understand if I show it in an example:
var el = document.querySelector("#foo"); // returns first element with ID of foo
var bar = document.querySelector(":scope > p", el); // returns equivalent of "#foo > p"
In the above example, “:scope > p” and “#foo > p” are not 100% equivalent. The later will return the first p element that is a child of an element with ID of “foo”. With the former, instead of any #foo, it will only use the first #foo in the document. As multiple IDs are illegal the difference in this particular example will not show off too often, but as you can imagine, the difference is important when using a reference element can appear multiple times in the document.
The :scope selector is most useful when reference elements are generated programatically, so it is not possible to write a static selector string. The selector is not currently implemented in any browser.
Changes to existing selectors
In CSS3 selectors, the negation selector (:not) can only accept a simple selector. A simple selector is either a type selector (an element name such as p or em), universal selector (the selector), attribute selector (those that target attributes such as [att], [att=val], etc.), class selector, ID selector, or a pseudo-class selector. Pseudo-elements and combinators (e.g. ul li, ul >li etc.) are not supported, and the negation selector can not be nested. These restrictions have been relaxed somewhat in CSS4 Selectors to also allow a selector list. Both simple and compound selectors are allowed. Pseudo-elements and nested selectors are still not supported however.
There are quite a few new selectors that have been proposed in the new spec, including one that has been much demanded by web developers.
The :matches() pseudo-class is the standardised version of Mozilla’s :-moz-any() pseudo class. This is useful for when you need a number of similar selector strings, but change one part such as the pseudo-classes. Instead of writing li a:link, li a:hover, li a:visited, li a:focus, you can write li a:matches(:link, :hover, :visited). Only simple or compound selectors are allowed. Complex selectors, nesting and using within :not()is forbidden.
The dir() pseudo-class selects elements depending on its directionality. This is commonly the direction of any potential text in the element, set with the HTML dir attribute. Note that the dir attribute does not need to be set directly on the element. It could be inherited from its parent. It does not match based on the CSS direction property.
The currently defined values are rtl (right to left, e.g. used with Hebrew or Arabic text) and ltr (left to right, e.g. used with latin text). Other values are not invalid, but are not defined to do anything. This allows the spec to be extended if new values are needed (top to bottom for example?).
The any-link() pseudo-class matches links, no matter if their history state is unvisited (:link) or visited. This is needed because once a link is visited the regular :link pseudo-claass does not apply any longer. This saves you having to specify both when you want to style both states the same way. The name is not set in stone, so if you have a better suggestion then get in touch with the working group.
Local link pseudo-class
The :local-link pseudo-class selects elements whose links are local links. If you use the plain :local-link pseudo-class, without parenthesis, then it matches elements that link to the current page. That is exactly the same URL as the document, sans any fragment identifier. Parenthesis can be added, taking a digit as a value. Using 0 (a:local-link(0)) will match a link that is the same domain. A value of 1 matches the domain and the first path segment. A value of 2 matches the domain and the first two path segments, and so on. For example, if the URL of the document is http://www.example.com/foo/bar/baz, the a:local-link(1) would match a elements with links that matches http://www.example.com/foo, while a:local-link(2) would match a elements with links that matches http://www.example.com/foo/bar.
There are three time-dimensional pseudo-classes: :past, :current, and :future. These relate to time related rendering (or time-dimensional canvas as the spec says), such as speech rendering of HTML documents (text to speech). The :current pseudo-class matches the element that is currently being rendered (such as styling the image that is currently being spoken). The :past pseudo-class styles matches elements that were rendered in the past (e.g have already been spoken), and :future matches elements that will be rendered in the future (will be spoken). There is also a version of :current that takes arguments in parenthesis. This works in much the same way as the :matches() pseudo-class, where it matches any of the comma separated selector compound selector strings. I am not 100% if I got this one right, as I am not familiar enough with time-dimensional canvas.
New tree-structural pseudo-classes
There are two new tree-structual pseudo-classes: nth-match and nth-last-match. These work much the same way as nth-child and nth-last-child, using the same an+b notation. However you also include the of keyword and a selector string that is used to match a subset of the results. This is difficult to understand when reading the spec, but an example makes it clearer. If you have the selector button:nth-match(even of .active), instead of selecting all even button elements as with button:nth-child(even), it will first match the subset of elements with a class of active, then match the even elements from that subset.
There are three grid-structure pseudo-classes: :column(), :nth-column(), and :nth-last-column. In HTML individual cells of tables are marked up in rows (using the tr element). The column the cell belongs to is implied rather than than the cell being a child of the col element. Due to this one dimensional hierarchy, to style individual table cells based on the column it belongs to, we need the :column() selector. It accepts a list of one or more selectors. Based on the semantics of the language, it calculates if the cells belong in the columns that match the selector list. For example :column(col.total) selects all the td cells in the total column.
The nth-column() and nth-last-column accept an+b notation as their argument, in the same way as other similar pseudo-classes. For example, :nth-column(odd) would select all table cells in odd columns.
Reference combinators allow you to select elements that are referenced by ID by another element. One of the most common occurrences of this is when a label references the ID of its related control in the for attribute. You define a reference combinator by surrounding the attribute with forward slashes (/). For example, if you wanted to style an input element when you hover over its label, you could use the selector label:hover /for/ input (providing the label has a for attribute with the correct ID).
The mythical unicorn: parent selector
That most demanded of selectors is now in the spec. Well not quite. It isn’t just a parent selector. You can now select which element is the subject of the selector. Traditionally this is the right most compound selector in the selector string. This is currently defined with the dollar sign ($), but there is currently some debate on the mailing list, and will likely change. The compound selector that is marked as the subject is the one that will be styled by the CSS rule. If you have a selector such as ul li.selected, it will match an element that is of type li that is a decedent of a ul element. However, if you want to only style a ul if one of its li elements has a class of selected, you need to make the ul the subject of the selector like so: $ul li.selected. Now all your prayed are answered!
This is the current state of play with the first CSS4 Selectors draft. Anything can and will change as the spec progresses. There are very little in the way of implementations of these features, so I can’t check if I understood them all 100% correctly. If I didn’t then let me know and I’ll update the post.