demo · v134

Range vs selectNodeContents

The CSS Custom Highlight API accepts any Range object — but how you construct the range determines exactly which characters get highlighted. Compare character-precise annotation (range.setStart/setEnd on text nodes) against whole-node selection (selectNodeContents), and see where they diverge on mixed markup.

Annotate a character range inside the paragraph below:

setStart/setEnd: chars
selectNodeContents:
The browser's CSS Custom Highlight API lets editors annotate arbitrary text ranges without touching the DOM. No span wrappers needed — just a Range and a named CSS.highlights entry.

setStart / setEnd (character-precise)

selectNodeContents (whole node)

the difference

range.selectNodeContents(el) selects every character inside a node, including all descendant text — it ignores element boundaries and grabs the entire subtree's text. range.setStart(textNode, offset) / range.setEnd(textNode, offset) lets you land on individual characters within a specific text node, giving you sub-word, mid-inline precision.

This matters most when annotating content inside mixed-inline markup — e.g., you want to highlight from the middle of a plain word across into a <strong> element. selectNodeContents can't target that span; only setStart/setEnd can.

why CSS highlight inheritance matters here

Before Chrome 134, a ::highlight() rule set at the document level might not paint on text inside a nested <em> or <strong> because those elements would restart the cascade. With the new highlight pseudo chain, the style propagates correctly regardless of which technique you used to define the Range.

see also