CSS Selectors

May 4, 2026 · View on GitHub

← Back to docs

CSS Selectors

JustHTML supports a comprehensive subset of CSS selectors for querying the DOM.

Performance Notes

  • Selector parsing is cached internally (LRU, up to 512 distinct selector strings). Repeating the same selector across many calls avoids re-parsing overhead.
  • Simple tag-only selectors like "div" use a fast path (descendant scan without running the full selector matcher).
  • If you generate lots of unique selectors dynamically, expect some parse overhead once the cache churns.
  • Selector evaluation uses resource limits for selector length, selector shape, parse depth, match operations, and string materialization work. These limits are intentionally conservative for untrusted input.

Default selector limits:

LimitDefaultApplies to
max_length8192Selector string length
max_list_items256Distinct comma-separated selector entries
max_compound_simple_selectors512Distinct simple selectors in one compound selector
max_complex_selector_parts512Combinator-linked parts in one complex selector
max_parse_depth100Nested functional pseudo-class parsing, such as nested :not(...)
max_match_depth100Nested selector matching
max_match_steps100_000_000Selector matching operations
max_match_bytes100_000_000Attribute/text string work during matching

Direct query(...) and matches(...) calls use the defaults. Sanitization transform pipelines can raise limits for trusted workloads with SanitizationPolicy(selector_limits=...); see Transforms.

Basic Selectors

SelectorExampleDescription
TagdivElements by tag name
Class.introElements with class
ID#mainElement with ID
Universal*All elements
doc.query("div")       # All <div> elements
doc.query_one("div")   # First matching <div>, or None

doc.query(".intro")    # All elements with class="intro"
doc.query("#main")     # Element with id="main"
doc.query("*")         # All elements

Attribute Selectors

SelectorExampleDescription
Has attribute[href]Elements with attribute
Exact match[type="text"]Exact attribute value
Starts with[href^="https"]Attribute starts with value
Ends with[href$=".pdf"]Attribute ends with value
Contains[href*="example"]Attribute contains value
Word match[class~="active"]Space-separated word match
Prefix match[lang|="en"]Value or value followed by -
doc.query("[href]")              # All elements with href attribute
doc.query('[type="submit"]')     # Exact match
doc.query('[href^="https://"]')  # Links starting with https://
doc.query('[src$=".png"]')       # Images ending with .png
doc.query('[class*="btn"]')      # Classes containing "btn"

Combinators

CombinatorExampleDescription
Descendantdiv p<p> anywhere inside <div>
Childdiv > p<p> direct child of <div>
Adjacenth1 + p<p> immediately after <h1>
Siblingh1 ~ pAny <p> sibling after <h1>
doc.query("div p")          # Paragraphs anywhere in a div
doc.query("ul > li")        # Direct list items
doc.query("h1 + p")         # First paragraph after h1
doc.query("h1 ~ p")         # All paragraphs after h1

Pseudo-classes

Structural

SelectorDescription
:first-childFirst child of parent
:last-childLast child of parent
:only-childOnly child of parent
:nth-child(n)Nth child (1-indexed)
:nth-last-child(n)Nth child from end
:first-of-typeFirst of its type in parent
:last-of-typeLast of its type in parent
:only-of-typeOnly of its type in parent
:nth-of-type(n)Nth of its type
:nth-last-of-type(n)Nth of its type from end
doc.query("li:first-child")       # First list item
doc.query("li:last-child")        # Last list item
doc.query("li:nth-child(2)")      # Second list item
doc.query("li:nth-child(odd)")    # Odd list items (1st, 3rd, 5th...)
doc.query("li:nth-child(even)")   # Even list items (2nd, 4th, 6th...)
doc.query("li:nth-child(3n)")     # Every 3rd item
doc.query("li:nth-child(3n+1)")   # 1st, 4th, 7th... (formula: An+B)

Content

SelectorDescription
:emptyElements with no children
:rootDocument root element
:contains("text")Non-standard (jQuery-style): elements whose descendant text contains text
:commentNon-standard: comment nodes (#comment)
doc.query("p:empty")    # Empty paragraphs
doc.query(":root")      # The <html> element
doc.query(":comment")   # Comment nodes

Negation

SelectorDescription
:not(selector)Elements not matching selector
doc.query("div:not(.hidden)")     # Visible divs
doc.query("input:not([disabled])")  # Enabled inputs

Selector Groups

Combine multiple selectors with commas:

doc.query("h1, h2, h3")           # All headings
doc.query(".btn, [type='submit']") # Buttons and submit inputs

Compound Selectors

Chain selectors without spaces:

doc.query("div.container")        # <div class="container">
doc.query("input[type='text']")   # <input type="text">
doc.query("li.active:first-child") # First li with class active

Examples

# Navigation links
doc.query("nav a")

# Active menu item
doc.query("nav li.active > a")

# Form inputs except hidden
doc.query("form input:not([type='hidden'])")

# Alternating table rows
doc.query("tr:nth-child(even)")

# First paragraph in each container
doc.query("div > p:first-of-type")

# External links
doc.query('a[href^="http"]:not([href*="mysite.com"])')