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:
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.