selectors: Which CSS selectors selectr supports, and what they translate...

selectorsR Documentation

Which CSS selectors selectr supports, and what they translate to

Description

A reference table of every combinator, simple selector, attribute operator and pseudo-class selectr recognises: whether it is supported, restricted to the html/xhtml translators, never matches (a static-document limitation), or is rejected as an error, plus the XPath a representative selector translates to. This complements the prose in css_to_xpath, which explains why the divergences from CSS Selectors Level 4 below exist; this page is the flat list to check "is X supported?" against.

Every example on this page is exercised by tests/testthat/test-selectors-reference.R against a live css_to_xpath call, and that test also asserts every xpath_*_pseudo, xpath_*_function and xpath_*_combinator method of GenericTranslator and HTMLTranslator is represented on this page, so this table cannot silently drift from the translators' code.

Combinators

Selector Meaning Example XPath ("e ? f", generic)
e f descendant descendant-or-self::e//f
e > f child descendant-or-self::e/f
e + f direct adjacent sibling descendant-or-self::e/following-sibling::*[1][self::f]
e ~ f indirect (general) sibling descendant-or-self::e/following-sibling::f
e || f column (Selectors 4) error - not supported, see below

Simple selectors

Selector Meaning Example XPath
* universal descendant-or-self::*
e type (no namespace) descendant-or-self::e
.class class ...[contains(concat(' ', normalize-space(@class), ' '), ' class ')]
#id ID descendant-or-self::*[@id = 'id']

Class matching splits @class on XML whitespace (space/tab/CR/LF) via normalize-space(); HTML's own "set of space-separated tokens" also treats U+000C form feed as a separator, which this does not - negligible in practice, since form feed in a class attribute is vanishingly rare.

Attribute selectors

Selector Meaning Example XPath ("[attr ? val]")
[attr] has attribute descendant-or-self::*[@attr]
[attr=val] equals descendant-or-self::*[@attr = 'val']
[attr~=val] includes a whitespace-separated token ...[contains(concat(' ', normalize-space(@attr), ' '), ' val ')]
[attr|=val] equals, or a "val-" prefix ...[@attr = 'val' or starts-with(@attr, 'val-')]
[attr^=val] starts with descendant-or-self::*[starts-with(@attr, 'val')]
[attr$=val] ends with ...[substring(@attr, string-length(@attr)-2) = 'val']
[attr*=val] contains substring descendant-or-self::*[contains(@attr, 'val')]

Every operator above accepts a trailing Selectors 4 case-sensitivity flag, i (ASCII case-insensitive) or s (case-sensitive, the default and so a no-op): [attr=val i] translates to ...[translate(@attr, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') = 'val']. Any other flag is a parse error.

Under the html translator, the attributes HTML lists as ASCII case-insensitive (type, rel, lang, hreflang, dir, media, method, target, checked, disabled, readonly, selected, multiple, shape, scope, align, charset, http-equiv, enctype, accept and the rest of HTML's "Case-sensitivity of selectors" list) match their values that way with no flag written, as they do in a browser: input[type=radio] selects <input type="RADIO">. An explicit s restores the exact comparison. class, id, href, name, data-* and every namespaced attribute keep it, and so does every attribute under the generic and xhtml translators, which serve XML rather than HTML documents.

Structural pseudo-classes

Selector Status Example XPath
:root supported descendant-or-self::*[not(parent::*)]
:first-child supported ...[count(preceding-sibling::*) = 0]
:last-child supported ...[count(following-sibling::*) = 0]
:only-child supported preceding- and following-sibling counts both 0
e:first-of-type supported (needs a named element) ...e[count(preceding-sibling::e) = 0]
e:last-of-type supported (needs a named element) ...e[count(following-sibling::e) = 0]
e:only-of-type supported (needs a named element) preceding- and following-sibling e counts both 0
*:first-of-type etc. error needs each sibling's own name; not expressible in XPath 1.0
:nth-child(An+B) supported :nth-child(2n+1) gives ...[count(preceding-sibling::*) mod 2 = 0]; a B outside the first cycle adds a >= B-1 bound and an offset in the mod
:nth-child(An+B of S) supported preceding-siblings filtered to S, plus self:: test
:nth-last-child() supported as :nth-child(), counting from the end
e:nth-of-type(), e:nth-last-of-type() supported (needs a named element) as above, restricted to siblings named e
:empty supported, Selectors 3 semantics ...[not(*) and not(string-length())] - see "Divergences" below
:scope supported, leading position only self::* (replaces the prefix); errors elsewhere in a selector

Selector-list pseudo-classes

Selector Meaning Example XPath
:not(e) none of the arguments match descendant-or-self::*[not(self::e)]
:is(e, f) (alias :matches()) any argument matches descendant-or-self::*[self::e or self::f]
:where(e, f) any argument matches (zero specificity) same XPath as :is()
:has(> e) a descendant/relative match exists descendant-or-self::*[child::e]

Each accepts a full selector list, but a :scope inside any of them is an error (see :scope above), and the column combinator inside them is likewise unsupported.

Linguistic and directionality pseudo-classes

Selector Status Notes
:lang(range) supported, translator-dependent generic: XPath lang(), prefix match only. html/xhtml: RFC 4647 extended filtering for multi-subtag ranges (subtags may be skipped between the ones named, but a leading * consumes the tag's primary subtag, so :lang(*-CH) does not match lang="ch-DE"). Every translator rejects a range that is not an RFC 4647 extended language range (:lang(en-), :lang(en*)); see css_to_xpath for the full rules
:lang("") supported, every translator matches an element with no content language anywhere in its ancestor-or-self chain
:dir() never matches, every translator descendant-or-self::*[0]; directionality needs a live DOM (dir="auto", bdi, form controls)

Link and interaction-state pseudo-classes

Selector generic html / xhtml
:link, :any-link never matches matches a and area elements with an href (a link element is metadata, not a hyperlink)
:visited never matches never matches (no browser history in a static document)
:hover, :active, :focus, :focus-within, :focus-visible never matches never matches (runtime UI state)
:target, :target-within never matches never matches (needs the document's URL fragment)
:local-link never matches never matches (needs the document's URL)

"Never matches" translates to descendant-or-self::*[0]: valid CSS, always zero results, rather than an error. In a larger compound the always-false 0 absorbs the compound's other conditions, which cannot change the outcome, so "a.external:visited" translates to descendant-or-self::a[0] as well.

HTML form-state pseudo-classes

These are only meaningfully supported by the html and xhtml translators (under generic they never match, listed above as the general runtime-state case). Every one matches by local name regardless of namespace, so "*|input:disabled" works the same as "input:disabled" on an unnamespaced document. :enabled and :disabled match only the elements listed below - in particular a hyperlink is not :enabled; use :link or :any-link for links.

Selector Elements and condition (html/xhtml)
:enabled / :disabled button, input, select, textarea, optgroup, option, fieldset; a disabled ancestor fieldset disables descendants (nested fieldsets included) except inside its first legend, and a disabled select or optgroup disables the optgroups and options below it
:checked checked checkbox/radio inputs and selected options; does not infer the implicit default selection of an unadorned single-select or radio group
:required / :optional input of a type that takes required (every type but hidden, range, color, submit, image, reset and button), select, textarea, by presence of required
:read-write an input of a type that takes readonly (every type but hidden, color, checkbox, radio, file, submit, image, reset, button and range) or a textarea, that is not readonly/disabled; or an element whose nearest contenteditable ancestor-or-self is not "false" (only "", "true", "plaintext-only" and "false" set the state - "inherit" and unrecognised values inherit)
:read-only the negation of :read-write (matches everything else, e.g. a checkbox or a plain div)
:placeholder-shown textarea, or an input of a type that takes placeholder (every type but hidden, checkbox, radio, file, submit, image, reset, button, color, range, date, month, week, time and datetime-local), with a non-empty placeholder and an empty current value
:default a selected option, a checked checkbox/radio, or the first submit button in its nearest ancestor form (does not follow a form= attribute)

Because HTML's type is an enumerated attribute, these match its keywords ASCII case-insensitively (<input type="RADIO"> is :checked). An input with no type, or with an unrecognised one, is in the text state, as it is for an HTML parser.

Column combinator and pseudo-classes (unsupported)

The Selectors 4 column combinator (a || b) and the column pseudo-classes :nth-col() / :nth-last-col() are rejected with an error: which column a cell belongs to depends on colspan/rowspan table-layout arithmetic that XPath 1.0 cannot express.

Namespaces

Selector Meaning Example XPath ("? p")
p p in no namespace descendant-or-self::p
d|p p in the namespace prefix d resolves to via the ns map descendant-or-self::d:p
*|p p in any namespace descendant-or-self::*[local-name() = 'p']
|p p in no namespace, spelled explicitly descendant-or-self::p

Prefixes such as d above are resolved through the ns argument passed to xml_find_all / getNodeSet at query time, not through whatever prefix the document itself uses; see querySelectorAll. A prefix is written into the generated XPath as it stands, so it has to be a name XPath can parse (an XML NCName, which is not restricted to ASCII); one that is not, such as the escaped \31 ns|div, is rejected with an error rather than compared against the document's own prefix. An escaped * (\2a|div) is such a prefix too: only the delimiter * of *|p above is the any-namespace wildcard, and a prefix spelled by an identifier is one no @namespace rule could have bound. Local names carry no such restriction: one that cannot be written as a name test is compared with local-name() instead, e.g. d|\31 becomes d:*[local-name() = '1']. HTMLTranslator additionally lower-cases every element and attribute name - folding A-Z only, as an HTML parser does, so a non-ASCII name is left as written - including namespaced ones, so svg|linearGradient becomes svg:lineargradient, which matches libxml2's HTML parser but would be wrong against a tree that restores camelCase SVG/MathML names (browsers, html5ever).

Divergences from CSS Selectors Level 4

  1. :empty keeps Selectors 3 semantics: an element containing only white space, e.g. <p> </p>, does not match. Selectors 4 loosened this to also match white-space-only content, but no browser has shipped that change, so it is treated as not implemented, tracking browser behaviour rather than the spec text.

  2. :checked tests only @checked/@selected. It does not infer the implicit selectedness of an option with no selected attribute anywhere in its select (the first option is selected by default), nor a radio group's mutual exclusivity - both need a live DOM to resolve.

  3. HTMLTranslator lower-cases foreign-content element and attribute names unconditionally, targeting libxml2-style HTML trees (see "Namespaces" above); an HTML5 parser that restores camelCase SVG/MathML names would disagree.

  4. Class/token matching (.foo, [attr~=val]) does not treat U+000C form feed as whitespace, unlike HTML's ASCII whitespace definition (see "Simple selectors" above).

Author(s)

Simon Potter

References

CSS Selectors Level 4 https://www.w3.org/TR/selectors-4/, XPath https://www.w3.org/TR/xpath/.

See Also

css_to_xpath for the full prose explanation of each divergence above and the error classes raised for unsupported selectors; querySelectorAll for namespace and chaining semantics when querying a document.


selectr documentation built on Sept. 17, 2026, 9:06 a.m.