What's new in CSS Selectors 4
permalinkPlease note that this article is written about an Editor’s Draft of a specification as of January 2015, which means the information may change without notice
CSS Selectors Level 4 is the next iteration of the CSS selector spec, the last version of which was made a recommendation in 2011 after being a working draft for a number of years.
So, what’s new?
Selector Profiles
CSS selectors are now categorized into two groups: fast and complete. Fast selectors are those selectors appropriate for use in a dynamic CSS engine. Complete selectors are appropriate for use in cases where being as fast as possible isn’t necessarily a problem, document.querySelector
, for instance.
Selectors are used in many different contexts, with wildly varying performance characteristics. Some powerful selectors are unfortunately too slow to realistically include in the more performance-sensitive contexts. To accommodate this, two profiles of the Selectors spec are defined [ref].
:has
:has
is the most interesting part of CSS Selectors 4, but it comes with an important caveat described below. What it allows you to do is change the subject of the selector – i.e., the element that will actually be styled – while continuing to match elements that appear later in document order.
This opens up a great deal of new ways to match content. For instance, matching sections with a header:
// Any section that has a header element
section:has(h1, h2, h3, h4, h5, h6)
Or a developer can match all paragraphs that contain nothing but any number of images:
// Match a paragraph that does not have anything that is not an image
p
:has(img) // has an image
:not(:has(:not(img))) // does not have anything not an image
Even matching an element that has a specific number of children (in this case, five):
// Sidebar with five children
div.sidebar
:has(*:nth-child(5)) // Has a fifth child
:not(:has(*:nth-child(6))) // But not a sixth child
Caveat: at this time the :has
selector is not considered fast, which means that it may not be available for use in stylesheets. As nobody has implemented this selector yet, its performance characteristics are still an open question. If browser vendors can make it fast, it may be available for general styling as well.
In previous versions of the specification this was indicated using an exclamation mark (!
) next to the subject – that syntax is now gone.
:matches
:matches
is a standardization of :moz-any
and :webkit-any
that have existed with browser prefixes for some time. This allows a stylesheet author to collapse duplicate rule paths.
This will be useful for collapsing generated Cartesian-product-esque SCSS/SASS output like this:
body > .layout > .body > .content .post p a.image.standard:first-child:nth-last-child(4) ~ a.image.standard,
body > .layout > .body > .content .post p a.image.standard:first-child:nth-last-child(4),
body > .layout > .body > .content .post li a.image.standard:first-child:nth-last-child(4) ~ a.image.standard,
body > .layout > .body > .content .post li a.image.standard:first-child:nth-last-child(4),
body > .layout > .body > .content .page p a.image.standard:first-child:nth-last-child(4) ~ a.image.standard,
body > .layout > .body > .content .page p a.image.standard:first-child:nth-last-child(4),
body > .layout > .body > .content .page li a.image.standard:first-child:nth-last-child(4) ~ a.image.standard,
body > .layout > .body > .content .page li a.image.standard:first-child:nth-last-child(4) {
....
}
into the slightly more manageable:
body > .layout > .body > .content
:matches(.post, .page)
:matches(p, li)
:matches(a.image.standard:first-child:nth-last-child(4),
a.image.standard:first-child:nth-last-child(4) ~ a.image.standard),
....
}
The Mozilla reference page above lists some caveats about performance. As this selector makes it out into a standard, we will hopefully see performance work on this to make it leaner.
:nth-child(An+B [of S])
While :nth-of-type
has existed since the turn of the millennium, CSS Selectors Level 4 is adding the ability to filter based on a selector:
div :nth-child(2 of .widget)
The selector S
is used for determining the index and it is independent of the selector to the left of the pseudo-class. As noted in the specification, if you know the type of the element ahead of time the :nth-of-type
selector can be converted into :nth-child(... of S)
like so:
img:nth-of-type(2) => :nth-child(2 of img)
The difference between this selector and the :nth-of-type
selector is subtle but important. For :nth-of-type
, each element –whether or not you have applied a selector to it – has an implicit index for itself amongst its siblings with the same tag name. The selector in the :nth-child(n of S)
expression creates a new counter each time you use a new selector.
There’s potential for bugs with this new selector. Since the selector inside the :nth-child
pseudo-class is independent of the selector to the left of it, you can accidentally omit your subject if you specify a selector to the left that isn’t a superset of the selector inside of :nth-child
. For example:
tr:nth-child(2n of [disabled])
might not work as you expect if another, non-<tr>
element has the disabled
attribute.
In previous versions of the specification this was the :nth-match
selector.
:not()
While you might have been using :not
for some time, you’ll now be able to pass multiple arguments to it to save some bytes and typing:
// Equivalent to:
// :not(h1):not(h2):not(h3)...
:not(h1, h2, h3, h4, h5, h6)
Descendant combinator (>>)
The descendant combinator has existed in CSS from the beginning as a space ( ), but now there’s an explicit version of it:
// Equivalent to:
// p img { ... }
p >> img { ... }
The reasoning for this is to provide a bridge between the direct descendant (>
), and the shadow DOM (>>>
) operator.
Column combinator (||) and :nth-column
CSS Selectors 4 adds column operations that will allow stylesheet developers to more easily style individual columns in a table. The current approach to table styling requires using :nth-child
, which does not always match up with table columns when using colspan
attributes.
By using the new column combinator (||
) you can now style table cells that are in the same column as a given <col>
element:
// The following example makes cells C, E, and G yellow.
// (example taken from the CSS Selectors 4 specification)
col.selected || td {
background: yellow;
color: white;
font-weight: bold;
}
<table>
<col span="2">
<col class="selected">
<tr><td>A <td>B <td>C
<tr><td colspan="2">D <td>E
<tr><td>F <td colspan="2">G
</table>
Alternatively, a stylesheet author may use :nth-column
and :nth-last-column
to style cells.
In either case, if a cell spans multiple columns it will match a selector for any of those columns.
:placeholder-shown
One small addition to the selector language is :placeholder-shown
. This matches an input element if and only if the placeholder
attribute text is visible.
:any-link
The :any-link
is another small selector addition. It is defined as matching anything that either :link
or :visited
would match.
// Equivalent to:
// a:link, a:visited { ... }
a:any-link { ... }
Conclusions
CSS Selectors 4 is still a work-in-progress, but there are already useful selectors that we’ve seen that will offer web developers new patterns and tools for styling. There are other new selectors in the specification that I haven’t discussed here for concepts like accessibility, validity checking and style scoping.
If you’d like to play around with these selectors, you’ll need to wait for browsers vendors to catch up, or use some of the earlier implementations. :matches
is available as :moz-any
and :webkit-any
, and WebKit nightlies have early support for :nth-child
selectors behind a flag.
Since this is an editor’s draft, pseudo-class names may change without notice. Keep an eye on the specification for more information.
Comments? Follow me on Twitter @mmastrac and let me know.
Read full post