The Future Generation of CSS Selectors: Level 4

Share this article

Back in January 2014 I wrote the article The Current Generation of CSS3 Selectors. The goal of that article was to introduce the new generation of selectors that often fell under the “CSS3” umbrella. Thabet group of selectors has been well documented in a lot of places, and browser support for those features is quite strong (all browsers including IE9+).

The future of CSS selectors is also looking bright, with the Selectors Level 4 specification currently in Working Draft status, and an Editor’s Draft of the same spec still in progress (the editor’s draft is generally viewed as more authoritative).

This article will focus on the new selectors not discussed in my previous article. Browser support for many of these is pretty poor, so I don’t recommend using many of these in production. View this post as a peek into what’s to come when the spec is further along and browsers start their implementations. I’ve included demos for those that have support.

:read-only and :read-write

These selectors are pretty straightforward. Any element that’s editable by the user is in the “read-write” state. Otherwise, the element is in the “read-only” state.

Take the following HTML:

<input type="text" readonly>
<input type="text" disabled>
<input type="text">
<div contenteditable></div>

Now consider this CSS:

:read-only {
  outline: solid 1px blue;
}

:read-write {
  outline: solid 1px red;
}

Here’s a breakdown of what this CSS does in relation to the HTML:

  • The first two elements will have a blue outline because they are set to “readonly” and “disabled” in the HTML, respectively.
  • The third element will have a red outline because it’s naturally editable (“read-write”), as are all inputs by default. A textarea would be the same.
  • The last element (the div) will have a red outline because of the contenteditable attribute.

In the CSS I’m using these selectors universally (i.e. without applying them to any elements). This means the red outline would be applied to all divs, spans, and other naturally uneditable elements. It’s more likely that this would be used on specific form elements or elements with a class applied, to be more specific.

The :read-write pseudo-class is listed as “at-risk” in the Editor’s Draft, so it may be removed.

Browser Support for :read-only and :read-write
Chrome, Opera, Firefox, Safari.

Note: As shown in the demo below, the browsers that support these selectors identify the “disabled” input as “read-write”, which is not correct, according to the spec.

See the Pen Demo for :read-only and :read-write by SitePoint (@SitePoint) on CodePen.

The Default-option Pseudo-class: :default

The :default pseudo-class matches elements that qualify as “default” in relation to a set that they are part of. For example, a button element that’s the default submit button for a form or the default selected item in a set of radio buttons.

You can also have multiple defaults for a single group, as shown in this HTML snippet:

<input type="checkbox" value ="f" name="group" checked> Fruits
<input type="checkbox" value ="v" name="group"> Vegetables
<input type="checkbox" value ="m" name="group" checked> Meats
<input type="checkbox" value ="p" name="group" checked> Poultry
<input type="checkbox" value ="n" name="group"> Nuts
<input type="checkbox" value ="b" name="group"> Breads

Now let’s pair the HTML above with the following CSS:

input[type=checkbox]:default {
  outline: solid 1px hotpink;
}

In this case, all the elements with the checked attribute present will be styled with the outline.

Browser Support for :default
Chrome, Opera, Firefox, Safari.

As shown in the demo, WebKit/Blink browsers do not apply the outline to the “default” checkboxes, even though they should. This seems to be a bug. Firefox has the correct behavior.

See the Pen Demo for :default by SitePoint (@SitePoint) on CodePen.

Validity Pseudo-classes: :valid and :invalid

These pseudo-classes are useful in HTML forms for giving visual clues as to the validity of the data entered by the user, something that would normally be done with JavaScript.

As an example, if your form has the following field:

Email: <input type="email" required>

Notice the field expects the data entered to be a valid email address. You can then do the following:

input[type=email]:invalid {
  outline: red solid 1px;
}

input[type=email]:valid {
  outline: lightgreen solid 1px;
}

With the above CSS, the email field will be styled with a red outline even before the user enters anything. Once the user types in a valid email address, the outline will turn green.

With these pseudo-classes you could easily add a pseudo-element in the form of a green checkmark (or something similar), to indicate valid field data.

Some notes on these selectors:

  • Interestingly, the validity can also apply to the form element itself, indicating if all the fields are valid.
  • These don’t work on common elements like div or p because those elements don’t have any way to specify expected data formats.
  • A regular “text” input type, which doesn’t require a specific format, is “valid” by default, but would be “invalid” without data if it has the required attribute set.

Browser Support for :valid and :invalid
All browsers including IE10+.

See the Pen Demo for :valid and :invalid by SitePoint (@SitePoint) on CodePen.

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

These two pseudo-classes are useful for form elements that allow data that falls between a specified range. You can style the specified input based on whether or not the element is within the allowed range.

So your HTML would be something like this:

<input type="date" 
       min="1994-01-01" max="2015-03-01"
       value="1993-01-01">

Notice the default value of “1993-01-01”, which is outside the allowed date range. You can then style the input dynamically, based on the default data or any entered data, something like this:

input[type=date]:in-range {
  outline: lightgreen solid 1px;
}

input[type=date]:out-of-range {
  outline: red solid 1px;
}

Some notes on these selectors:

  • These can be used for number, datetime, datetime-local, month, week, and any other input types that allow ranges.
  • Technically these also work with the range input type, but I don’t think there is a way to make such an element be “out of range” so the usefulness in this case seems limited.
  • As with other pseudo-classes, these will work only on elements that have the capability to define accepted ranges.

Browser Support for :in-range and out-of-range
Opera, Chrome, Firefox, Safari.

See the Pen Demo for :in-range and :out-of-range by SitePoint (@SitePoint) on CodePen.

Optionality Pseudo-classes: :required and :optional

These pseudo-classes let you style form elements based on whether they are required to be filled out or not. Take the following HTML:

<div>
  <label for="name">name:</label>
  <input type="text" id="name" required>
  <span class="msg"></span>
</div>
<div>
  <label for="email">Email:</label>
  <input type="email" id="email" required>
  <span class="msg"></span>
</div>
<div>
  <label for="address">Address:</label>
  <input type="text" id="address">
  <span class="msg"></span>
</div>

Notice the empty span elements added next to each input. Also notice that the first two fields are required but the third is not. With this HTML in place, you can do the following in your CSS:

input:required ~ .msg:after {
  content: '*';
  color: red;
}

input:optional ~ .msg:after {
  content: '(optional)';
}

Here I’m using the general sibling combinator to add a red asterisk next to each required field and I’m adding the word “optional” in parentheses beside each non-required field.

This uses the extra elements that I added. If you prefer not to use extra elements, you can either add them dynamically with JavaScript or apply styling directly to the input itself. But note that you can’t use pseudo-elements with form inputs, so you’d have to apply a different kind of styling in that case.

Browser Support for :required and :optional
All browsers.

See the Pen Demo for :required and :optional by SitePoint (@SitePoint) on CodePen.

Case-insensitive Attribute Selectors: i

By default in CSS, attribute selectors are case sensitive. So, for example, if you select all elements with href values that end with “pdf”, it will not select href values that end with “PDF” (uppercase).

There’s a useful new flag that can be added to an attribute selector that overrides this behavior.

a[href$="pdf" i] {
  color: red;
}

Now the attribute selector will select all href links pointing to PDF files, regardless of whether the .pdf extension is written in lowercase, uppercase, or even mixed case.

Browser Support for case-insensitive attribute selectors:
Opera (only available behind a flag; thanks to Šime Vidas for pointing this out).

See the Pen Demo for case-insensitive attribute selectors by SitePoint (@SitePoint) on CodePen.

:blank Pseudo-class

The :blank pseudo-class is kind of like the prettier cousin of :empty, which I discussed in the previous article. With :empty you can select an element based on there being no children in it, whether that be elements, text nodes, or even white space nodes. So with :empty, even if the element contains a single space and nothing else, it will not be considered “empty”.

The :blank pseudo-class, however, will select an element as long as it has no text and no other child elements, regardless of white space. So it could contain white space, line breaks, etc., and it would still qualify.

Here’s some example HTML:

<p></p>

<p> </p>

Note that the first paragraph element is completely empty, but the second one has a single space character in it. Here’s the CSS:

p:blank {
  outline: solid 1px red;
}

p:empty {
  border: solid 1px green;
}

In this case, I’m applying a red outline on “blank” elements and a green border on “empty” elements. The :empty pseudo-class will select only the first element, because it’s completely empty. But the :blank pseudo-class will apply to both, because they are both “blank” with respects to text and elements.

It might be hard to remember the difference, because the names sound too similar and this is something that is noted in an issue in the spec. So it’s possible that this selector will change names. As the spec also notes, there is an equivalent Mozilla-only version of this selector.

Browser Support for :blank
None.

Matches-any Pseudo-class: :matches()

The :matches() pseudo-class is a way to make selector grouping more succinct, and should be a useful addition to the spec when browser support improves.

In an example taken from MDN, you might have the following overly-verbose CSS, which attempts to deal with styling for main headings that are nested in various contexts:

section section h1, section article h1,
section aside h1, section nav h1,
article section h1, article article h1, 
article aside h1, article nav h1,
aside section h1, aside article h1,
aside aside h1, aside nav h1,
nav section h1, nav article h1, 
nav aside h1, nav nav h1, {
  font-size: 20px;
}

With :matches(), this can be simplified to:

:matches(section, article, aside, nav)
:matches(section, article, aside, nav) h1 {
  font-size: 20px;
}

The simplified version can be interpreted as: “If the h1 is inside any of these four elements, which in turn is inside any of the same four elements, do the following.”

Notes on :matches():

  • It used to be :any in the spec, supported with -moz- and -webkit- prefixes.
  • As CSS-Tricks points out, the principle here is similar to selector nesting in preprocessors.
  • The selector argument must be a “simple selector” (i.e. it cannot be a pseudo-element and it can’t use a combinator other than descendant).

Browser Support for :matches()
None, however, WebKit/Blink and Mozilla have their own vendor-based equivalents.

Relational Pseudo-class: :has()

The :has pseudo-class is similar to jQuery’s .has() but with some broader abilities. Examples will make clear what’s possible. Note the comments in the code, which explain what each example selects:

/* Section elements that contain a footer */
section:has(footer) {
  background: #ccc;
}

/* Any element containing a p element with a class of "alert" */
:has(p.alert) {
  background-color: red;
}

/* img element with paragraph immediately following. */
img:has(+p) {
  color: red;
}

/* list item with ul as a direct child */
li:has(> ul) {
  color: red;
}

As you can see, the name “has” isn’t just another word for “contains” (which is how jQuery’s method works); it can also mean “has as immediate child”, “has specified element following it”, etc.

Note: The :has pseudo-class is in the Editor’s Draft but not in the Working Draft. Also, as pointed out by Ralph in the comments, this selector may only be available in JavaScript (kind of like querySelectorAll), and not in CSS. See Dynamic vs Static Selector Profiles in the spec.

Browser Support for :has()
None.

This selector works as a shortcut for styling any element that can take an href attribute. This includes a, area, and link elements. It can also be used as a general shortcut for the following:

:link, :visited {
  color: #555;
}

So instead of the above, you would write:

:any-link {
  color: #555;
}

But, as mentioned, this would also cover other elements, not just anchors (a), so this selector’s usefulness might be tough to discern.

It should be noted that the Working Draft had a selector called :local-link, which has been removed in Editor’s.

Browser Support for :any-link
Chrome, Opera, and Firefox (with vendor prefixes; thanks to Selen in the comments for pointing this out).

Generalized Input Focus Pseudo-class: :focus-within

This is an interesting one that I can definitely see being useful. The :focus-within pseudo-class will select not only the element to which :focus would normally apply, but also the parent element.

Here’s some example HTML:

<div class="outer">
  <label for="email">Email:</label>
  <input type="email" id="email">
</div>

With that, you can write the following CSS:

:focus-within {
  outline: yellow solid 1px;
}

This will cause the yellow outline to apply not only to the focused input, but also to the parent div element, because they both match the focus-within state. The spec also points out that this would work the same way inside “shadow tree” elements, applying the styles to the “host element”.

Browser Support for :focus-within
None.

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

Now that drag-and-drop functionality is so common in apps, these selectors could prove valuable for improving the user experience.

The :drop selector lets you style the drop zone (the place where the element is supposed to be dropped), during the time when the user is dragging (or carrying) the element to be dropped.

.spot {
  background: #ccc;
}

.spot:drop {
  background: hotpink;
}

The above CSS will apply a neutral gray background color to the .spot element when the user is not dragging. But when the user starts dragging to the .spot element, the background color will change and stay that way until the item is dropped.

The alternate :drop() syntax takes one or more of the following keyword values:

  • active: Indicates the current drop target for the item dragged.
  • valid: Indicates if the drop target is valid in relation to the item being dragged (e.g. if the target only accepts files, other items wouldn’t be valid)
  • invalid: Opposite of previous, lets you style the drop target if it’s invalid in relation to the item dragged.

Multiple keywords can be used to make things more specific and if an argument is not given then it will just act like :drop, so there’s really no point in using the parentheses unless you plan to specify an argument.

Important notes:

  • The Working Draft version of the spec had a completely different set of pseudo-classes for this, so these are still in flux.
  • The drop() syntax is “at-risk”, so it may be removed.

Browser Support for :drop and :drop()
None.

Honorable Mentions

In addition to the ones discussed above, there are some other new features that I won’t go into detail on but are worth a brief mention:

  • The column combinator (||), for defining relationships between cells and columns in tables and grids.
  • The :nth-column() and :nth-last-column() pseudo-classes for targeting specific columns of tables and grids.
  • The attribute node selector attr(), which is the first non-element selector.
  • The alternate version of the descendant combinator, represented by >> (instead of just a space character)
  • The :user-error pseudo-class for styling inputs that have incorrect user-entered data.
  • Namespaces defined with the @namespace at-rule.
  • The :dir() pseudo-class, which lets you select elements based on their directionality (e.g. ltr).
  • The :scope pseudo-class, which provides a scope, or reference point, for selecting elements.
  • The time-dimensional :current, :past, and :future pseudo-classes for targeting elements during a timeline progression such as subtitles in a video.
  • The :placeholder-shown pseudo-class for styling placeholder text in form elements.

Final Remarks and Further Info

As already mentioned, these features are very new and as shown by the browser support info and the demos, aren’t very well supported. Many of them are therefore not ready for prime time.

To keep up with the progress, here are some resources on Level 4 selectors:

If you notice any errors or bugs, mention them in the comments.

Louis LazarisLouis Lazaris
View Author

Louis is a front-end developer, writer, and author who has been involved in the web dev industry since 2000. He blogs at Impressive Webs and curates Web Tools Weekly, a newsletter for front-end developers with a focus on tools.

css selectorscss4css4 selectorslearn-advanced-cssLouisLselectors level 4
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week