CSS4 Selectors: What Can We Expect?

Important: Due to the work-in-progress nature of the Selectors Level 4 Draft, there’s a chance that things included in this post will change or disappear. This post was written following the guidelines of the January 20th, 2017 draft.

CSS just turned 20 years old and some people still write it as if it was 2002. I’ve always felt this powerful language was often left behind in the project development process and seen as something that just needed to be done, without worrying about quality, optimization or simplicity. Thankfully, this mindset has begun to change in recent years with the growth in interest in front end stack technologies.

In my opinion, a well written/coded app falls short without a good CSS implementation. The “snappiness” of a website depends a lot on the quality of the CSS that it has. Sometimes it’s hard to achieve a specific layout or styling and this causes us to create clunky or hacky stylesheets that, in the end, may have an impact on the user experience. The good news is that CSS4 allows us to use some nice new selectors that will simplify the way we code our stylesheets, keeping them straightforward and organized.

In this post, I want to share with you some of the ideas that are being discussed on the W3C right now.

Let’s take a look at these:

Logical Combinations

Negation :not(selector1, selector2)

This pseudo-class was already present in CSS3, but it just allowed one single selector as an argument. Now, in my opinion, its use is pretty explicit as it can receive as many selectors as you want. It simply takes a list of selectors as an argument and represents one or many elements that are not represented by it’s argument.


input:not([required][type="text"]) { … } *:not(.hidden) { … }

Matches-any :matches(selector1, selector2)

This pseudo-class is the opposite of :not. It takes a list of selectors as argument and represents one or many elements that are represented by the given argument.


li:matches(:first-child, .highlighted) { … } a:matches(*:hover, *:focus) { … }

Relational :has(selector1, selector2)

It’s a pseudo-class that takes a relative selector list as an argument and represents one or many elements if any of the relative selectors match at least one element.


li:has(> ul) { … } dt:has(+ dt) { … }

Attribute selectors


The default behavior for case-sensitivity of attribute’s values and names depends on the document language. With this new selector, it is possible to match any element where the specified attribute is equal to a value of any case combination. We just need to add an i before the closing bracket (]) to indicate case-insensitivity.


/* All input elements with a value of name, Name, NAME, etc, will be matched */ input[value="name" i] { … }

Linguistic Pseudo-classes

Directionality Pseudo-class :dir()

This pseudo-class allows us to write selectors that represent an element based on its directionality (left-to-right, right-to-left), determined by the document language.


blockquote:dir(ltr) {  border-left: 5px solid #555; } blockquote:dir(rtl) {  border-right: 5px solid #555; }

Language Pseudo-class :lang()

This pseudo-class represents an element that is in a language of the listed as arguments. It was introduced first in CSS2, but in CSS4 we have the possibility to add wildcard matching.


a:lang(es-CR) {  background-color: blue; } /* The following example will match all anchor elements with a language ending in CA like en-CA or fr-CA */ a:lang(*-CA) {  background-color: red; }

Location Pseudo-classes

Hyperlink Pseudo-class :any-link

It represents an element that acts as the source anchor of a hyperlink (elements with an href attribute). In other words, any element that would match :link and :visited. It’s like a shorthand of :matches(:link, :visited).


#navbar a:any-link {  text-decoration: none; }

Reference Element Pseudo-class :scope

This selector is useful when we want to give a scope to the CSS rules that we are writing. It allow us to represent a (potentially empty) set of elements that provide a reference point for selectors to match against, just like querySelector() call in DOM, or the parent element of a scoped <style> element in HTML5.


<div>  <p>Paragraph outside the scope.</p>  <div>    <style scoped>      :scope {        background-color: black;      }      p {        color: white;      }    </style>    <p>Paragraph is inside the scope.</p>  </div> </div>

Time-dimensional Pseudo-classes

These pseudo-classes help to classify relative to the currently displayed or active position in a timeline, like a speech rendering of a document or when displaying subtitles for a video using WebVTT.

Current-element Pseudo-class :current

This represents the element, or an ancestor of the element, that is currently being displayed. Also, it’s possible to provide a list of selectors. This will represent the :current element that at the same time matches the arguments, and if there aren’t matches, the innermost ancestor of the :current element that does.


:current(p) {  background-color: lightgreen; }

Past-element Pseudo-class :past

This pseudo-class represents any element that is defined to occur entirely prior to a :current element.


:past(p) {  background-color: yellow;  opacity: .5; }

Future-element Pseudo-class :future

This selector represents any element that is defined to occur entirely after a :current element.


:future(p) {  background-color: lightblue;  opacity: .9; }

User Action Pseudo-classes

Input Focus-Ring Pseudo-class :focus-ring

This pseudo-class represents an element that matches the :focus pseudo-class, but at the same time, the UA determines the focus should be specially indicated on the element (via a “focus ring”). This is just like what happens with buttons or links when tabbing through a document.


/* This will match any focused element */ .content > :focus {  outline-width: 2px;  outline-style: solid;  outline-color: lightblue; } /* This will match just elements that display a focus ring by default, like buttons or input fields */ .content > :focus-ring {  outline-width: 2px;  outline-style: solid;  outline-color: lightblue; }

Drag-and-Drop Pseudo-classes :drop and :drop()

The :drop pseudo-class represents all elements that are drop targets while the user is “dragging” an item to be “dropped”. The :drop() functional pseudo-class is the same as :drop, but allows extra filters to be specified that can exclude some drop targets. The filters can be:

  • active: The drop target is the current drop target for the drag interaction.
  • valid: This matches if the drop target is valid (according to the document language) for the item being dragged. Otherwise, it matches all drop targets. For example, it is useful when we want to style drop targets when they just accept files.
  • invalid: This matches if the drop target is invalid (according to the document language) for the object currently being dragged. Otherwise, it matches nothing.

Multiple filters can be combined in the argument.


.drop-zone:drop(valid active) {  background-color: lightgreen; }

Input Pseudo-classes

Input Control States

Mutability Pseudo-classes :read-only and :read-write

An element matches :read-write if it is alterable by the user, as defined by the document language. Otherwise, it is :read-only.


input:read-write {  border-color: green; } input:read-only {  border-color: gray; }

Placeholder-shown Pseudo-class :placeholder-shown

This pseudo-class matches an input element that is showing placeholder text.


input:placeholder-shown + label {  display: none; }

Default-option Pseudo-class :default

This applies to the one or more UI elements that are the default among a set of similar elements. This typically applies to context menu items, buttons and select lists/menus. For example, this will match the default submit button in a set of buttons.


.btn {  background-color: lightgray; } .btn:default {  background-color: lightgreen; }

Input Value States

Indeterminate-value Pseudo-class :indeterminate

This pseudo-class applies to UI elements with a value in an indeterminate state. For example, radio and checkbox elements can be in an indeterminate state, neither checked nor unchecked.


:indeterminate, :indeterminate + label {  background: yellow; }

Input Value-checking

Validity Pseudo-classes :valid and :invalid

An element is :valid or :invalid when its content or value is valid or invalid respecting the data validity semantics defined by the document language. An element without data validity semantics is neither :valid nor :invalid.


<input type="text" required />

input:invalid {  border-color: red; }

Range Pseudo-classes :in-range and :out-of-range

These pseudo-classes apply only to elements that have range limitations. An element is :in-range or :out-of-range when its value is in range or out of range with respect to its range limits as defined by the document language. If an element doesn’t have data range limits or is not a form control it’s neither :in-range nor :out-of-range.


<input type="number" required max="5" />

input:out-of-range {  border-color: red; }

Optionality Pseudo-classes :required and :optional

A form element is :required or :optional if a value for it is, respectively, required or optional before the form can be submitted. Elements that are not form elements are neither :required nor :optional.


input:optional {  color: gray; }

User-interaction Pseudo-class :user-invalid

This represents an element with incorrect input, but only after the user has interacted with it.


input:user-invalid {  border-color: red; }

Tree-Structural pseudo-classes

:blank pseudo-class

This pseudo-class is like the :empty pseudo-class, but additionally, matches elements that only contain code affected by whitespace processing.


<p> </p>

p:blank {  display: none; }


Descendant combinator ( ) or (>>)

This selector describes an element that is the descendant of another element in the document tree. It’s essentially the same as separating compound selectors with whitespace, but it’s just more explicit.


h2 >> span { … }

Grid-Structural Selectors

Column combinator ||

This matches any cell belonging to a column in a grid or table.


<table>  <col>Title</col>  <col class="selected">Name</col>  <tr>    <td>Architect</td>    <td>John Doe</td>  </tr>  <tr>    <td>Software Developer</td>    <td>Donkey Kong</td>  </tr> </table>

col.selected || td {  background: lightblue;  font-weight: bold; }

:nth-column() pseudo-class

This matches any cell, in a grid or table, belonging to the nth column in a grid or table.


:nth-column(2n) {  background: gray; }

:nth-last-column() pseudo-class

This pseudo-class represents any cell belonging to the nth column in a grid or table, counting from the last one.


:nth-last-column(3n+1) {  background: purple; }


As you can see, there are lots of new and powerful selectors that will make our lives easier. Keep them in mind, as the future is near! Again, I want to clarify this information is based on the January 20th, 2017 editor’s draft of “Selectors Level 4”, so things may change at any moment. — Want to read more from Arturo in the future? Subscribe to our blog and follow us on Twitter.

Ready to be Unstoppable? Partner with Gorilla Logic, and you can be.