Web Platform

Selecting an HTML element's text

34 min read

select on npm

select is a tiny MIT-licensed library for selecting the content of a DOM element, providing a single simple function with no additional options:

function select(element: HTMLElement): string; // selected text

It provides a wrapper on top of the two decade old DOM Level 2 Range API. As it turns out, there is actually a .select() method, but only on <input> and <textarea> elements.

Although its usage has declined significantly recently, it still boasts an impressive 2 million downloads a week. Clearly, developers believe this bit of functionality is too difficult to implement by hand.

The initial version was extremely simple, with the function body only using up 14 lines.

Over the next two years of the package, 25 lines were added.

In this article, I will first showcase the initial version and then explain the rationale behind all the patches added later.

The .select() method on <input> and <textarea> elements has been supported since at least IE 5.5.

<div class="flex flex-col gap-2">
  <div id="selectButtons" class="flex gap-2">
    <button data-child="0">Select input</button>
    <button data-child="1">Select textarea</button>
  </div>
  <div id="selectTargets" class="flex items-center gap-2">
    <input type="text" value="Lorem ipsum" class="h-12 w-32 border p-2 dark:border-white" />
    <textarea class="h-12 w-36 border p-2 dark:border-white">Lorem ipsum</textarea>
  </div>
</div>
<script>
  document.getElementById("selectButtons").onclick = (e) => {
    const selectTargets = document.getElementById("selectTargets");
    const child = Number(e.target.dataset.child);
    selectTargets.children[child].select();
  };
</script>

For other elements, the DOM Range API can be used. First, a new range is created with document.createRange() and the chosen element is added to the range with range.selectNodeContents(element). This new range can then be added to the document selection with addRange.

Note that one does not technically need to call removeAllRanges. This makes it possible to programmatically select multiple elements, something most users do not know how to do (on desktop, some browsers support it by selecting more text while holding the Ctrl key). Try pressing the second button while already having a different piece of text selected on the page. You can even copy both at the same time with the context menu or keyboard shortcuts! As long as selectElement does not call removeAllRanges, you can select as many pieces of text on the page as you’d like with the button.

<div class="flex gap-2">
  <button id="selectText">Select the text below</button>
  <button id="selectTextKeepExisting">Select the text below without removing existing selections</button>
</div>

<p id="textToSelect" class="mt-2">
  Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
  aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
  Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
  occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>

<script>
  function selectElement(element, keepExistingSelection = false) {
    const range = document.createRange();
    range.selectNodeContents(element);
    // window. and document.getSelection do the same thing
    const selection = document.getSelection();
    if (!keepExistingSelection) selection.removeAllRanges();
    selection.addRange(range);
  }
  const textToSelect = document.getElementById("textToSelect");
  document.getElementById("selectText").onclick = () => selectElement(textToSelect);
  document.getElementById("selectTextKeepExisting").onclick = () => selectElement(textToSelect, true);
</script>

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

We shouldn’t forget that the package also returns the newly selected text. This piece of functionality is actually a simple one-liner.

First, select a piece of text on this page, then click the button below to set its text to your selection:

<button id="showSelectedText">Turn me into your selected text!</button>
<script>
  document.getElementById("showSelectedText").onclick = function () {
    this.textContent = window.getSelection().toString();
  };
</script>

Edge cases covered by the package

Read section Edge cases covered by the package

window.getSelection() cannot be used to get the text of the currently selected input

Read section window.getSelection() cannot be used to get the text of the currently selected input

Not so fast. It turns out that for input and textarea elements, the current selection is always empty. You need to access the input’s value.

The first button will always be empty after clicking it.

<div class="flex flex-col gap-2">
  <div class="flex gap-2">
    <button id="edgeCaseInputBroken">Select input and own text (broken)</button>
    <button id="edgeCaseInputWorking">Select input and replace own text (working)</button>
  </div>
  <input id="edgeCaseInput" type="text" value="Lorem ipsum" class="h-12 w-32 border p-2 dark:border-white" />
</div>
<script>
  const selectEdgeCaseInput = () => document.getElementById("edgeCaseInput").select();
  document.getElementById("edgeCaseInputBroken").onclick = function () {
    selectEdgeCaseInput();
    this.textContent = window.getSelection().toString();
  };
  document.getElementById("edgeCaseInputWorking").onclick = function () {
    selectEdgeCaseInput();
    this.textContent = document.getElementById("edgeCaseInput").value;
  };
</script>

Select elements should not be selected

Read section Select elements should not be selected

Range#selectNodeContents can select the option of a <select> element, but this is unexpected to users. Selecting a <select> should instead focus it. A <select> with selected text cannot be controlled with Enter.

Note that in Firefox, <select>’s current option can not be focused. Selecting it appears to change the color of the box but does not do anything meaningful for the user. Calling .focus() on its <option> elements also does not do anything.

<div class="flex gap-2">
  <button id="selectSelect">Select select element</button>
  <button id="focusSelect">Focus select</button>
</div>
<select id="edgeCaseSelect" class="mt-2 p-4">
  <option>A</option>
  <option>B</option>
</select>
<script>
  document.getElementById("selectSelect").onclick = () => selectElement(document.getElementById("edgeCaseSelect"));
  document.getElementById("focusSelect").onclick = () => document.getElementById("edgeCaseSelect").focus();
</script>

Just like <input> and <textarea> components, if you want to get the currently selected <option>, use document.getElementById("edgeCaseSelect").value.

Mobile Safari bugs with inputs and textareas

Read section Mobile Safari bugs with inputs and textareas

The library also contains two workarounds for bugs with Safari. It’s unknown to me whether these bugs have ever been fixed by Apple.

setSelectionRange is called after .select() for selection to work at all on Safari.

element.select();
element.setSelectionRange(0, element.value.length);

To prevent the iOS keyboard from showing up when the input is selected (this may or may not be desirable), readonly is temporarily added if it isn’t already before the select, and removed if the input was not readonly:

const readonly = element.hasAttribute("readonly");

if (!readonly) element.setAttribute("readonly", "");

element.select();
element.setSelectionRange(0, element.value.length);

if (!readonly) element.removeAttribute("readonly");

The library also has code to explicitly focus contenteditable elements before they are selected, but in my testing this is already done by the browser. This may have been added to support older browsers. The issue does not give any detailed information.

The example below simply selects a contenteditable <p> with the function given earlier. You should be able to start typing in the paragraph immediately. Please let me know if you can find a browser in which .focus() needs to be called manually.

<button id="selectEditableText">Select and focus the text below</button>
<p id="editableTextToSelect" class="mt-2" contenteditable>
  Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
  aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
  Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
  occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>

<script>
  document.getElementById("selectEditableText").onclick = () =>
    selectElement(document.getElementById("editableTextToSelect"));
</script>

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.