cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Selection & Range

Learn how Range objects mark pieces of the DOM, how the Selection API reads what a user selected, how text box selection differs, and how to build a safe highlighter.

You’ll learn to
  • 01
    Mark exact DOM passagesCreate Range boundaries with container nodes and offsets.
  • 02
    Inspect the live selectionRead anchor, focus, ranges, and selected text safely.
  • 03
    Edit selections carefullyHandle textarea cursors and wrap selected page text in marks.

What does “selected text” mean to JavaScript?

When someone drags across words on a page, the browser does more than paint blue pixels. It remembers where the selected passage begins and ends in the DOM tree. JavaScript can read that information, move it, copy it, delete it, or use it to build features like quotes, annotations, and rich-text editors.

This lesson separates three ideas that sound similar but behave differently:

  • A Range is a precise DOM passage: start boundary, end boundary, and all the content between them.
  • The Selection is the live thing the user currently selected in the document. It can point at a Range and also remembers drag direction through anchor and focus.
  • Input and textarea selection is tracked by the text control itself with selectionStart and friends.
The one-sentence mental model

A Range marks a passage; a Selection is the user’s current highlighter; a textarea cursor is a separate cursor inside a text box.

Real-life analogyA Range is two bookmarks in a book

Imagine placing one bookmark before a sentence and one after it. The book did not change, but you can now say “copy this passage,” “delete this passage,” or “wrap this passage in yellow paper.” A Range gives JavaScript the same precise handles inside the DOM.

In real life: The first bookmark
In JavaScript: startContainer plus startOffset
In real life: The second bookmark
In JavaScript: endContainer plus endOffset
In real life: The passage between them
In JavaScript: range.toString() or the selected DOM nodes
In real life: The chapter containing both bookmarks
In JavaScript: commonAncestorContainer

Where the analogy stops: A paper bookmark sits between printed characters. A Range boundary sits inside a DOM node: text-node offsets count UTF-16 code units, while element offsets count child nodes.

You have already met DOM nodes in The DOM tree and node-changing tools in Creating & changing elements. Here we zoom in: instead of changing a whole element, we target the exact part the reader selected.

Range objects: two boundaries, one passage

INTERACTIVE

Create a Range with document.createRange(). At first it is just an empty object with movable boundaries. You place those boundaries with setStart(container, offset) and setEnd(container, offset).

What a Range offset counts
Container nodeWhat the offset countsExample
Text nodeUTF-16 code unitsIn "A😀B", offsets are 0, 1, 3, 4 because 😀 takes two code units.
Element nodeChild nodesIn <p>Hello <b>bold</b>!</p>, offset 1 is after the first text node and before the <b>.

That UTF-16 detail is the same string rule covered in Unicode & string internals. It matters because Range boundaries are low-level: they talk to the DOM, not to what a human sees as “one character.”

Range builder: move the bookmarks
Range builder codePop out in the code editor (opens in a new tab)JavaScript
const root = document.querySelector("#demo");const range = document.createRange();range.setStart(root.childNodes[0], 6);range.setEnd(root.childNodes[2], 5);console.log(range.toString());getSelection().removeAllRanges();getSelection().addRange(range);
Live DOMTry offsets 0–10
range.toString()
—
start
—
end
—
collapsed
—
commonAncestorContainer
—
Try it yourself
Start boundary

Text offsets count UTF-16 code units. Valid offsets: 0–6.

End boundary

Text offsets count UTF-16 code units. Valid offsets: 0–10.

Visualize

Choose boundaries to create a Range.

The paragraph is built with DOM APIs inside an empty ref, so React does not manage the text nodes that Range changes or selects.

Try three experiments: make the start and end the same boundary to see a collapsed Range; choose the root element and move by child nodes; then choose a text node and move by code units. If your browser supports the CSS Custom Highlight API, the highlight mode paints a Range without changing the DOM. If not, the demo falls back to the normal Selection.

The Selection API: the user’s highlighter

INTERACTIVE
Real-life analogyThe Selection is the highlighter in the reader’s hand

When a reader drags across text, imagine a highlighter touching down, moving, and lifting up. The browser remembers both ends of that gesture. This is why a backwards drag can have the same selected words but different anchor and focus values.

In real life: Where the reader first pressed down
In JavaScript: anchorNode and anchorOffset
In real life: Where the reader let go
In JavaScript: focusNode and focusOffset
In real life: The colored passage
In JavaScript: selection.toString() and getRangeAt(0)
In real life: A caret with no highlighted text
In JavaScript: selection.isCollapsed === true

Where the analogy stops: A real highlighter leaves ink behind. The Selection is temporary browser state: click elsewhere and it can move or disappear.

window.getSelection() and document.getSelection() both access the document Selection in browser page code. Before calling getRangeAt(0), check rangeCount; there may be no selected Range yet. The newer selection.direction is useful where supported, but anchor and focus are the classic way to see where a drag began and ended.

Live selection inspector
Selection inspector codePop out in the code editor (opens in a new tab)JavaScript
document.addEventListener("selectionchange", () => {  const selection = document.getSelection();  if (!selection || selection.rangeCount === 0) return;   const range = selection.getRangeAt(0);  if (!demo.contains(range.commonAncestorContainer)) return;   console.log(selection.toString());  console.log(selection.anchorNode, selection.anchorOffset);  console.log(selection.focusNode, selection.focusOffset);});
Try selecting this textReal selectionchange

Start at either end: selecting text backwards keeps the same selected words, but anchor and focus reveal where your drag began and ended.

selection.toString()
Select inside the demo paragraph.
anchor
—
focus
—
rangeCount
0
isCollapsed
true
type
None
direction
Feature-detecting…
Try it yourself

Drag forwards, then drag backwards over the same words.

The listener is on document, but the code ignores selections whose Range belongs outside the demo paragraph.

Selections may change as you click elsewhere on the page; that is the real browser Selection at work.

Select a word, then drag from right to left over that same word. The Range’s start and end remain in document order, but anchor and focus reflect the gesture. Real editors care about this when extending a selection with Shift+Arrow keys.

Selection in inputs and textareas

INTERACTIVE
Real-life analogyA textarea selection is the cursor inside a text box

A text field behaves like a tiny editor. Its cursor lives inside the control’s value, so the control gives you direct numeric indexes instead of asking you to inspect DOM nodes.

In real life: The blinking cursor
In JavaScript: selectionStart === selectionEnd
In real life: A highlighted run of characters
In JavaScript: selectionStart to selectionEnd
In real life: Replace the highlighted run
In JavaScript: setRangeText(text, start, end, mode)

Where the analogy stops: The text box exposes string indexes, not DOM nodes. A textarea’s internal text is not the same as selecting text in normal page content.

For <input> and <textarea>, use selectionStart, selectionEnd, selectionDirection, select(), setSelectionRange(), and setRangeText(). Do not rely on normal document Ranges for text inside form controls; browsers have differed here, while the form-control API is designed for this job.

Textarea cursor lab
Textarea selection codePop out in the code editor (opens in a new tab)JavaScript
const box = document.querySelector("textarea"); box.select();box.setSelectionRange(2, 5, "forward");box.setRangeText("😀", box.selectionStart, box.selectionEnd, "end"); console.log(box.selectionStart);console.log(box.selectionEnd);console.log(box.selectionDirection);
TextareaControl-owned selection
selectionStart
0
selectionEnd
0
selectionDirection
none
Try it yourself

The numbers come from the textarea itself. They are not a normal document Range.

Click or type in the textarea too; the live readout updates on selection and input events.

Notice that setRangeText("😀", start, end, "end") can insert at a caret or replace a selected slice, then move the caret after the inserted emoji. That is perfect for mention pickers, emoji buttons, snippet expanders, and autocomplete chips.

Building a highlighter

INTERACTIVE

A highlighter begins with the current Selection, extracts its first Range, checks that the Range belongs to the part of the page you own, and wraps the selected content in a <mark> element. That containment check is important: without it, a button in your widget could accidentally rewrite a selection from the lesson text, the navigation, or another component.

Build a highlighter
Highlighter codeJavaScript
const selection = document.getSelection();const range = selection.getRangeAt(0);if (!container.contains(range.commonAncestorContainer)) return; const mark = document.createElement("mark");try {  range.surroundContents(mark);} catch {  mark.append(range.extractContents());  range.insertNode(mark);} container.querySelectorAll("mark").forEach((mark) => {  mark.replaceWith(...mark.childNodes);});container.normalize();
Editable demo DOMTry crossing the bold text
Try it yourself

Select text in the demo paragraph, then press Highlight.

The demo edits only the empty-ref container. It refuses selections from the rest of the lesson page.

The easiest path is range.surroundContents(mark). It works when the Range fully contains every non-text node it touches. It throws when the Range partially contains an element, such as selecting from the middle of normal text into only part of a <b>. The fallback is more manual but more forgiving: extract the selected contents, append them to the mark, and insert the mark back at the Range.

A safe editing habit

Only mutate DOM your component owns. In the demos, anything that inserts or removes nodes uses an empty ref container, then builds the contents with DOM APIs so React does not try to reconcile children it no longer recognizes.

Range surgery methods

STEP THROUGH

Ranges are useful because they carry methods that operate on the selected passage. selectNode(node) selects a node itself. selectNodeContents(node) selects what is inside it. collapse(true) collapses to the start; collapse(false) collapses to the end. cloneRange() copies the boundaries so you can keep a saved position.

The content methods are where things get exciting. Step through the same Range four ways and predict whether the paragraph changes.

Range surgery: copy, remove, move, insert
Step 0 of 7Ready
Your turn: follow the blue line

A Range is two boundaries. Switch the operation and predict which methods copy, remove, move, or insert content.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
const text = paragraph.firstChild;const range = document.createRange();range.setStart(text, 6);range.setEnd(text, 11);const copy = range.cloneContents();console.log(copy.textContent);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Switch the operation after the Range selects magic.

Changing the method starts a fresh replay with the same boundaries.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Where you’ll use this

Selection and Range APIs show up in tools that feel “editor-like”: quote buttons, comment anchors, rich-text editors, annotation layers, search results, and accessibility helpers that report what the user is reading. The pattern is almost always: read the Selection, guard carefully, then convert it to the form your feature needs.

Quote the selected text from an articleJavaScript
const selection = document.getSelection();if (!selection || selection.rangeCount === 0) return;const range = selection.getRangeAt(0);if (!article.contains(range.commonAncestorContainer)) return; quotePreview.textContent = selection.toString();

Line 2 prevents a crash when there is no Range. Line 4 prevents your feature from reading or changing selections outside the article. After those checks, selection.toString() is safe to put into a preview, a share card, or a comment composer.

Range, Selection, or input selection API?
  • Copy the nodes between two DOM boundaries without changing the page
  • Show the text the user just dragged over in an article
  • Insert an emoji at the cursor inside a <textarea>
  • Know whether a drag started at the end and moved left
  • Wrap selected page content in <mark>
  • Select characters 2 through 5 in a text field
Try it yourself
0 of 6 correct

Sort each realistic task by the API you would reach for first.

Choose a category for every card. You can change an answer at any time; Reset clears them all.
Fun fact: highlights without wrappers

The CSS Custom Highlight API lets newer browsers paint Ranges through CSS.highlights without inserting <mark> elements. Feature-detect it and keep a fallback, as the Range builder does.

Common misconceptions

Similar names, different jobs
ThingOwnsUse it for
RangeStart/end DOM boundariesCopy, delete, extract, insert, wrap, or inspect a passage.
SelectionThe user’s current document highlight/caretRead selected page text, anchor/focus, and the first Range.
Input selectionIndexes inside a form control valueMove a textarea cursor, select typed characters, or insert replacement text.

“A Range offset is always a character index.”

Only inside Text nodes. On element containers, offsets count child nodes. That is why choosing the root element in the Range builder jumps between the text node, the <b>, and the trailing text node.

“Selection start and Range start are the same idea.”

Range start/end are ordered in the document. Selection anchor/focus remember the user’s gesture, so they can reveal a backwards drag.

“I can use getSelection for textareas everywhere.”

Use selectionStart and selectionEnd for form controls. They are simpler and designed for that text value.

“surroundContents always wraps whatever is selected.”

It throws if the Range partially contains a non-Text node. Good highlighters catch that and fall back to extract/insert.

“Cloning a Range clones the selected DOM.”

cloneRange() copies the boundaries. Use cloneContents() to copy the selected nodes.

Practice: select, inspect, and edit

5 EXERCISES
Exercise 1 · Warm-upCount the emoji offset

Predict the two values printed by this program.

Starter codePop out in the code editor (opens in a new tab)JavaScript
const word = "A😀B";
console.log(word.length);
console.log(word.slice(1, 3));

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticePick the right API

    You are writing an emoji button for a comment <textarea>. Which property tells you the cursor’s start index?

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeModel extractContents with strings

      What remains in text after line 3?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      let text = "Range magic works";
      const selected = text.slice(6, 11);
      text = text.slice(0, 6) + text.slice(11);
      console.log(selected);
      console.log(text);

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · ChallengeName the highlighter edge case

        Which Range method can throw when the user selects from plain text into only part of a bold element?

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeObserve a backwards selection

          Use the selection inspector to select the same word forwards and backwards. Write down which fields changed and which stayed the same.

            Quiz: check your understanding

            7 QUESTIONS

            Answer each question before opening the explanation. Wrong answers are written to teach the difference, not just mark a miss.

            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7What is a Range boundary made of?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does the emoji length code print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const text = "A😀B";
              console.log(text.length);

              Choose an answer to see the explanation.

            3. Question 3 of 7Which API should you use for the cursor inside a textarea?

              Choose an answer to see the explanation.

            4. Question 4 of 7A user drags from the end of a sentence back toward the start. Which pair remembers that direction?

              Choose an answer to see the explanation.

            5. Question 5 of 7What does the slice code print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const text = "Range magic works";
              console.log(text.slice(6, 11));

              Choose an answer to see the explanation.

            6. Question 6 of 7When does range.surroundContents(mark) throw?

              Choose an answer to see the explanation.

            7. Question 7 of 7Which statement is safest for page selections?

              Choose an answer to see the explanation.

            Key takeaways

            • A Range is two boundaries: start container/offset and end container/offset.
            • Text-node offsets count UTF-16 code units; element offsets count child nodes.
            • The Selection API reads the live document selection; anchor/focus can reveal a backwards drag.
            • Inputs and textareas use their own selection properties and methods.
            • When editing selected DOM, guard the container and handle surroundContents() failures.

            Selection & Range in one line.
            A Range marks exactly where; the Selection says what the user is holding right now; text controls track their own cursor indexes.

            Up next: Introduction to events, where you will learn how listeners and event objects let page code respond to clicks, keys, input, and more.

            CompleteFrontend Clear concepts. Working examples.