What CSS3 selectors does jQuery really support, e.g. :nth-last-child()?

According to http://api.jquery.com/category/selectors/ we can use a large amount of CSS selectors in jQuery, but e.g. :nth-last-child() is not mentioned there. However, when I test the following (with jQuery 1.7.1 as from Google), it actually works on Firefox, Chrome, and IE 9, but not on IE 9 in IE 8 emulation mode:

$('li:nth-last-child(2)').css('color', 'red');

So what’s happening? It looks as if jQuery generated CSS code, like li:nth-last-child(2) { color: red } and somehow injected it, which then works OK on browsers that support the selector used. But that would be odd.

Most importantly, is there some trick to make jQuery support such selectors on all browsers?


While jQuery advertises compliance with the Selectors level 3 standard on its home page, it does not fully implement the spec. In its own Selectors documentation, it clarifies that it "[borrows] from CSS 1–3, and then [adds] its own" selectors.1

Starting from jQuery 1.9, virtually all selectors in the level 3 standard are supported by Sizzle (its underlying selector library), with the following exceptions:

  • jQuery cannot select any pseudo-elements as they are CSS-based abstractions of the document tree that can't be expressed through the DOM.

  • jQuery is unable to resolve dynamic pseudo-classes, such as :link/:visited for hyperlinks and :hover, :active and :focus for user interaction. The latter set of pseudo-classes in particular are state-based and not event-based, so you need to use event handlers rather than pseudo-classes to run code when elements enter and leave these states. See this answer for an explanation.

  • jQuery is also unable to resolve namespace prefixes since it does not support namespacing in CSS.

The following level 3 selectors are implemented in jQuery 1.9 and newer, but not jQuery 1.8 or older2:

  • :target
  • :root
  • :nth-last-child()
  • :nth-of-type()
  • :nth-last-of-type()
  • :first-of-type
  • :last-of-type
  • :only-of-type

Additionally:

  • :lang(), introduced in CSS2, is also missing.

The reason why your selector appears to work in Firefox, Chrome and IE9 is because jQuery first passes the selector string to the native document.querySelectorAll() implementation before falling back to Sizzle. Since it is a valid CSS selector, document.querySelectorAll() will successfully return a node list for jQuery to use, thereby obviating the use of Sizzle.

In the event that document.querySelectorAll() fails, jQuery automatically falls back to Sizzle. There are a number of scenarios that can cause it to fail:

  • The selector is invalid, not supported, or otherwise cannot be used (see the Selectors API spec for details).

  • The document.querySelectorAll() method itself is not supported (jQuery actually tests this with a simple if statement so it doesn't fail in that sense of the word, but you get the picture).

In your case, although IE9 and IE8 implement document.querySelectorAll(), IE8 doesn't support :nth-last-child(). Since jQuery/Sizzle doesn't implement :nth-last-child() either, there's no fallback behavior to use, resulting in complete failure on IE8.

If you're not able to update jQuery to 1.9 even at the very least (the backward-compatible release branch), you can always use the custom selector extensions to implement the missing pseudo-classes yourself. However, since jQuery 1.9 adds support for the above selectors while maintaining compatibility with old IE versions, it is best to update to that version at the very minimum if you need the compatibility.


1It does support :contains(), last defined in this old CR revision of the spec before being dropped later, as well as extending :not() from the standard. The differences between jQuery's implementation and the current standard are covered in this question.

2A few other selectors (like the + and ~ sibling combinators, :empty, :lang() and some CSS2 attribute selectors) were going to be dropped as well during jQuery's early development, just because John Resig didn't think anybody would use them. Almost all of them made it into the final release after some more tests were made available. :lang() was the only one that never made it into any release before 1.9, as stated above.