@supports selector()

Avatar of Chris Coyier
Chris Coyier on

UGURUS offers elite coaching and mentorship for agency owners looking to grow. Start with the free Agency Accelerator today.

I didn’t realize the support for @supports determining selector support was so good! I usually think of @supports as a way to test for property: value pair support. But with the selector() function, we can test for selector support as well. It looks like this:

@supports selector(:nth-child(1 of .foo)) {

}

You just drop the selector right between the parens and that’s what it tests for.

That selector above is a pretty good test, actually. It’s a “selector list argument” that works for the :nth-child ‘n’ friends selectors. As I write, it’s only supported in Safari.

So let’s say your ideal situation is that the browser supports this selector. Here’s an example. You know that with <ol> and <ul> the only valid child element is <li>. But also say this list needs separators, so you (and I’m not saying this is a great idea) did this kind of thing:

<ul>
  <li class="list-item">List item</li>
  <li class="list-item">List item</li>
  <li class="separator"></li>
  /* ... */
</ul>

Then you also want to zebra-stripe the list. And, if you want zebra striping, you need to select every other .list-item, ignoring the .separator. So…

li:nth-child(odd of .list-item) {
  background: lightgoldenrodyellow;
}

But only Safari supports that… so you can do:

@supports selector(:nth-child(1 of .foo)) {
  li:nth-child(odd of .list-item) {
    background: lightgoldenrodyellow;
  }
}

If you didn’t care what the fallback was, you wouldn’t even have to bother with the @supports at all. But say you do care about the fallback. Perhaps in the supported situation, the zebra striping does the heavy lifting of the UX you are shooting for, so all you need for the seperator is a bit of space. But for non-supporting browsers, you’ll need something beefier because you don’t have the zebra striping.

So now you can style both situations:

@supports selector(:nth-child(1 of .foo)) {
  li {
    padding: 0.25em;
  }
  li:nth-child(odd of .list-item) {
    background: lightgoldenrodyellow;
  }
  li.separator {
    list-style: none;
    margin: 0.25em 0;
  }
}
@supports not selector(:nth-child(1 of .foo)) {
  li.separator {
    height: 1px;
    list-style: none;
    border-top: 1px dashed purple;
    margin: 0.25em 0;
  }
}

If we get the @when syntax, then we can write it a little cleaner:

/* Maybe? */
@when supports(selector(:nth-child(1 of .foo))) {

} @else {

}

Anyway. The end result is…

Supported
Not Supported

There is a JavaScript API for testing support as well. I wasn’t sure if this would actually work, but it appears to! This fails in Chrome and passes in Safari as I write:

CSS.supports("selector(:nth-child(1 of .foo))")

While I was putting this together, I was thinking… hmmmmmmm — what CSS selectors are out there that have weird cross-browser support? It’s really not that many. And even of those that do have weird cross-browser support, thinking of the number of use-cases where you care to actually wrap it in an @supports (rather than just let it fail) is fairly few.

The ::marker pseudo-element would have been a great one, but it’s pretty well supported now. I was thinking the case-insensitive attribute selector, like [href$="pdf" i], would have been a good one, but nope, also well supported. Same deal with the comma-separated :not(a, .b, [c]). Maybe something like :fullscreen / :-webkit-full-screen would be interesting and useful because it’s uniquely not supported in iOS Safari?