Pseudo-class selectors start with a colon character “:” and match according to the _ state _ of the current element. This state may be relative to the document tree, or it may be a response to a state change, such as hover or Checked.

:any-link

Although defined at selector level 4, this pseudo-class has been supported across browsers for quite some time. The any-link pseudo-class will match an anchor hyperlink as long as it has an href. It matches in the same way: Link and: Visited. Essentially, if you’re adding a basic property, like color, that you want to apply to all links regardless of their access state, this could make your style one less selector.

:any-link { color: blue; The text - underline - offset: 0.05 em. }Copy the code

An important point to note about specificity of A is that even if :any-link is placed low in the cascade, it will triumph over A as a selector because of its class particularity. In the example below, the link will be purple.

:any-link {
  color: purple;
}

a {
  color: red;
}
Copy the code

Therefore, if you introduce :any-link, note that you need to use it as a selector on instances of A if they will directly compete for specificity.

:focus-visible

I’d bet that one of the most common accessibility violations across the web is the removal of outline on interactive elements such as links, buttons, and form inputs :focus state. One of the main purposes of this Outline is to provide visual instructions for users who primarily use the keyboard for navigation. The visible focus state is crucial as a wayfinding tool for these users in the interface and helps reinforce what the interactive elements are. Specifically, visible focus is covered in WCAG success standard 2.4.11: Focus appearance (minimum).

The purpose of the: Focus-visible pseudo-class is to display only when the user agent heuristically determines that the focus ring should be visible. In other words: the browser decides when to apply focal-Visible based on the input method, element type, and interaction context. For testing purposes, typing through a desktop computer’s keyboard and mouse, you should see :focus-visible, when you tag into an interactive element, but not when you click on it, except for text input and text areas, should display all focus input types: Focus-Visible.

Note: For more details, check out the working draft of the: Focus-Visible specification.

The latest versions of Firefox and Chromium browsers now seem to be handling form input :focus-visible according to the specification, which states that UA should remove the :focus style when: Focus-Visible matches. Safari doesn’t support: Focal-Visible yet, so we need to make sure to include a: Focus style as a backup to avoid removing outline for accessibility.

Given a button and text input and the following styles, let’s see what happens.

input:focus, button:focus { outline: 2px solid blue; The outline - offset: 0.25 em. } input:focus-visible { outline: 2px solid transparent; border-color: blue; } button:focus:not(:focus-visible) { outline: none; } button:focus-visible { outline: 2px solid transparent; box-shadow: 0 0 0 2px #fff, 0 0 0 4px blue; }Copy the code

Chromium and Firefox

  • input

    When the element is focused with mouse input, it is removed correctly:focusStyle, while supporting:focus-visibleBring about changeborder-color, hidden during keyboard inputoutline
  • button

    Not only use:focus-visibleAnd no extrabutton:focus:not(:focus-visible)Rules, that is, delete:focusIs visible only during keyboard typingbox-shadow

Safari park

  • input

    Continue to use only:focusstyle
  • button

    This seems to respect the button in part:focus-visibleThe intent is hidden when clicked:focusStyle, but still displayed during keyboard interaction:focusStyle.

Therefore, the current recommendation is to continue to include: Focus style, and then step up to use what the demo code allows: Focus-Visible. Here’s a CodePen for you to continue testing.

Check out Stephanie Eckles’ : Focus-Visible pen test app.

:focus-within

The: focal-within pseudo-class is supported in all modern browsers and acts almost like a parent selector, but only for a very special condition. When concatenated to a contain element and a child element matching :focus, styles can be added to any other element in the contain element _ and/or _ container.

One practical improvement to using this behavior is to shape the form label when the relevant input is in focus. To do this, we pack the label and input in a container and then put :focus-within, while selecting the label.

.form-group:focus-within label {
  color: blue;
}
Copy the code

This way, when the input has focus, the label turns blue.

The CodePen demo also includes adding an outline directly to the.form-group container.

Check out Stephanie Eckles’s Pen test app at Focus-Within.

:is()

Also known as the “match anything” pseudo-class, :is(), can accept a list of selectors to try to match. For example, you can group them under the :is(H1, H2, H3) selector instead of listing the header styles individually.

A few unique behaviors about :is() selector lists.

  • If the listed selectors are invalid, the rule continues to match valid selectors. In view of the:is(-ua-invalid, article, p), the rule will matcharticlep
  • The calculated specificity will be equal to the selector that passes with the highest specificity. For example,:is(#id, p)Will have#idThe specificity of -1.0.0 – while:is(p, a)The specificity will be 0.0.1.

Ignoring the first behavior of an invalid selector is a key benefit. When other selectors are used in a group where a selector is invalid, the browser throws the entire rule. This can come into play in a few cases where vendor prefixes are still necessary, and splitting the selectors with and without prefixes causes the rule to fail in all browsers. With :is(), you can safely group these styles so that when they match, they are applied and when they don’t, they are ignored.

To me, grouping header styles like the one mentioned earlier is already a big win for this selector. This is also the type of rule I can safely use when applying non-critical styles, without back-up measures such as.

:is(h1, h2, h3) {word-break: break-all; } :is(h2, h3):not(:first-child) { margin-top: 2em; }Copy the code

In this example (from document styles in my SmolCSS project), inheriting a larger line-height from the base style, or a lack of margin-top, isn’t really a problem for browsers that don’t support it. It’s just not ideal. You don’t want to use :is() yet, because it’s a key layout style, such as Grid or Flex, that can significantly control your interface.

In addition, when chained to another selector, you can test whether the base selector matches the subordinate selector in :is(). For example, the following rule selects only passages that are direct descendants of the article. The universal selector is used as a reference to the p basic selector.

p:is(article > *)
Copy the code

To get the best current support, if you want to start using it, you’ll also want to double down on styles by including repeat rules using :-webkit-any() and :matches(). Remember to make these separate rules, or even supported browsers will throw them out. In other words, include all of the following.

:matches(h1, h2, h3) { }

:-webkit-any(h1, h2, h3) { }

:is(h1, h2, h3) { }
Copy the code

It’s worth mentioning at this point that, along with the newer selector itself, there is an updated variant of @supports, @Selector. This can also be called @ Supports Not Selector.

Note: Currently (among modern browsers), only Safari does not support this at-rule.

You can check support for :is() with the following method, but in fact you will lose support for Safari because Safari supports :is() but does not support @selector.

@selector (:is(h1)) {:is(h1, h2, h3) {line-height: 1.1; }}Copy the code

:where()

Pseudo-class :where() is almost identical to :is() except for one key difference: it will _ always _ have zero specificity. This has an incredible impact on those who are building frameworks, themes, and designing systems. Using :where(), authors can set default values, and downstream developers can include overrides or extensions without specific conflicts.

Consider the following set of IMG styles. Using :where(), even with a higher specificity selector, the specificity is still zero. In the example below, what color do you think the border of the image will be?

:where(article img:not(:first-child)) {
    border: 5px solid red;
}

:where(article) img {
  border: 5px solid green;
}

img {
  border: 5px solid orange;
}
Copy the code

The first rule has zero specificity because it is entirely contained in :where(). So go straight to rule number two, rule number two wins. Introduce the IMG single element selector as the last rule, which will win due to cascading. This is because it will calculate the same specificity as the: Where (article) IMG rule, because the: Where () section does not increase specificity.

It is difficult to use :where() with the fallback rule due to its zero-specificity, as this feature may be the reason you want to use it instead of :is(). If you add fallback rules, by their very nature, these rules may defeat :where(). Also, it has better overall support than @Supports Selector, so trying to make a fallback rule with it is unlikely to provide much, if any, benefit. Basically, be careful not to do :where() correctly, and double-check your own data to determine if it’s safe to start using it for your unique audience.

You can further test this with the following CodePen :where(), which uses the img selector above.

See Stephanie Eckles’ PenTesting: Where () Specificity.

The enhanced:not()

Basic: The NOT () selector is supported as of Internet Explorer 9. But selector level 4 enhances :not(), allowing it to accept a list of selectors like :is() and: Where ().

The following rule provides the same result in supported browsers.

Article :not(h2):not(h3):not(h4) {margin-bottom: 1.5em; } article :not(h2, h3, h4) {margin-bottom: 1.5em; }Copy the code

The :not() ability to accept a list of selectors is greatly supported by modern browsers.

As we saw in :is(), enhanced :not() can also use * to contain a reference to the underlying selector, as a descendant. The CodePen demonstrates this ability by selecting links that are not descendants of _ nav.

See Stephanie Eckles’ Pen Test: Not () with a descendant selector.

Bonus: The previous demo also included an example of chaining :not() and :is() to select images of adjacent siblings that are not h2 or H3 elements.

Suggested but “risky” —:has()

The last pseudoclass, a very exciting suggestion that no browser currently implements, even experimentally, is :has(). In fact, it is listed as “at risk” in the selector level 4 edit draft, which means it is considered to have difficulties in completing the implementation, so it _ may _ be removed from the recommendation.

If implemented, :has() will basically be the “parent selector” that many CSS people crave. It works like a combination of :focus-within and :is(), with descendant selectors where you look for the existence of descendants, but the styles applied will be parent elements.

Considering the following rule, navigation reduces padding at the top and bottom if it contains a button.

Nav {padding: 0.75rem 0.25rem; Nav: from the (button) {padding - top: 0.25 rem; Padding - bottom: 0.25 rem; }Copy the code

Again, this isn’t currently implemented in any browser, even experimentally — but it’s interesting to think about. Robin Rendle at CSS-Tricks offers more insight into this future selector.

Honorable mention of the third level.:empty

You might have missed a useful pseudo-class in the third-level selector: :empty, which matches an element with no child element, including text nodes.

Rule P: Empty will match

, but not

Hello

.

One method you can use with :empty is to hide elements that might be placeholders for dynamic content filled with JavaScript. Maybe you have a div that will receive the search results, and when it is filled, it will have a border and some padding. But since you don’t have results yet, you don’t want it to take up space on the page. Use :empty to hide it.

.search-results:empty {
  display: none;
}
Copy the code

You might consider adding a message in an empty state and be tempted to use a pseudo-element and content. The catch here is that messages may not be available to users of assistive technologies that have access to content. In other words, to ensure that “no result” type information is accessible, you want to add it as a real element, such as a paragraph (for a hidden div, aria-label will no longer be accessible).

Learning selector resources

CSS also has many selectors, including pseudo-classes. Here are a few places to learn more about what’s available.

  • MDN’s CSS selector documentation includes a comprehensive list of categories.
  • I’ve written a two-part guide to advanced CSS selectors that you can start with.
  • Learn the fun of CSS selectors by playing CSS Diner.
  • Kitty Giraudel has created a selector interpretation tool that will break down and describe portions of the provided selector.