A Range spans two boundary points, each a node plus an offset (a character index in a Text node). The document's Selection wraps a range but adds direction: the anchor is where the user started, the focus where they stopped, while a range's start always precedes its end.
The demo highlights every "DOM" with the CSS Custom Highlight API (Baseline 2025), which styles ranges via ::highlight() without inserting elements, then selects "API that" backward.
<style>p { font: 18px Georgia, serif; } ::highlight(hit) { background: #ffe082; }</style>
<p id="quote">The DOM is not JavaScript: the DOM is an API that JavaScript uses.</p>
<pre id="out" style="font-size: 14px"></pre>
<script>
const text = document.querySelector('#quote').firstChild; // the Text node
const ranges = [...text.data.matchAll(/DOM/g)].map((m) => {
const range = new Range();
range.setStart(text, m.index); range.setEnd(text, m.index + 3);
return range;
});
CSS.highlights.set('hit', new Highlight(...ranges)); // DOM unchanged
const sel = getSelection();
sel.setBaseAndExtent(text, 49, text, 41); // anchor after focus
const r = sel.getRangeAt(0);
document.querySelector('#out').textContent = `"${sel}" ${sel.direction}: anchor ` +
`${sel.anchorOffset}, focus ${sel.focusOffset}; range ${r.startOffset}-${r.endOffset}`;
</script>
Ranges also edit content (extractContents(), insertNode(), surroundContents()); listen for selectionchange on document to track the user.