JavaScript Selection API Table
| Selector | Selects | Example |
|---|---|---|
.card | Class - every element carrying it | <div class="card"> |
#header | ID - the one element with it | <header id="header"> |
button | Type - every such element | all <button>s |
*[lang] | Attribute present | any element with a lang attribute |
input[type="checkbox"] | Attribute equals | checkbox inputs only |
nav a | Descendant - any depth | links inside nav, however nested |
nav > a | Child - one level only | direct link children of nav |
h2 + p | Adjacent sibling - immediately after | the first paragraph following an h2 |
h2 ~ p | General sibling - anywhere after | all following paragraph siblings |
a:hover, :focus-visible | Pseudo-class - state | hovered or keyboard-focused links |
p::first-line | Pseudo-element - sub-part | the first rendered line of each paragraph |
li:nth-child(2n) | Structural pattern | even-numbered list items |
nav a is fine but needlessly long chains are slow to match and brittle to maintain. Specificity climbs in the order type < class < id; the discipline that keeps stylesheets sane is staying in class territory and treating ids as JavaScript hooks, not style targets. Bottom line: the three combinators (space, >, +) are three different promises about structure - pick the weakest one that says what you mean. Related tools: pseudo-elements table and pseudo-classes table for the colon families, CSS animations table for what selectors trigger, and specificity calculator for the arithmetic.The Selection API reads and drives what the user highlights: window.getSelection() is the global handle, toString() gives the text, selectionchange on document fires on every caret move, and Range objects define boundaries - you can both READ and SET selections programmatically.
Bottom line: character counters, toolbar-at-selection editors and quote-sharing all run on this API. isCollapsed distinguishes a caret from a real span, selectionchange needs debouncing, and the Clipboard API (not the deprecated execCommand) does the copying the selection feeds.
The honest part: selections are per-document singletons with engine quirks - selections inside inputs and textareas do not appear in window.getSelection() on all engines (they have their own selectionStart/End), so form-field selection logic is a separate code path.
How to use
- Read the highlighted text: const text = window.getSelection().toString() on selectionchange (debounced ~150ms) - the quote-share and counter feed.
- Position a toolbar at the selection: range.getBoundingClientRect() after checking !selection.isCollapsed - the medium-style floating toolbar anchor.
- Select programmatically: selection.removeAllRanges(); selection.addRange(range) - quote buttons that pre-select the citable span.
Frequently asked questions
How do I react to selection changes without drowning in events?
selectionchange fires on document for every caret movement - typing, arrow keys, drags - far faster than any UI should react. The pattern is a debounce (150-250ms timer, reset on each event, react when quiet) for toolbar show/hide, or requestAnimationFrame throttling for continuous needs like cursor-position followers. One cross-engine note: on some engines the event does not bubble from inputs, so the listener goes on document - and selections inside input/textarea fields are reported by those elements' own selectionStart/selectionEnd, not by the global selection, on several engines. Form fields are the separate code path.
What is a Range and why does selection code revolve around it?
The boundary object. A Range describes a fragment of the document by start container/offset and end container/offset - it exists independently of any visible selection, which is why it can define, move and measure text spans without the user selecting anything. Selections are essentially ranges the user made: getRangeAt(0) retrieves the first one (multiple ranges exist with Ctrl-based multi-selection, but nearly all UI cares about range 0). Ranges also do their own work: range.getBoundingClientRect() positions UI at the selection, range.selectNodeContents() builds selections to apply, and Range.extractContents() moves the fragment - the vocabulary of every rich-text editor.
Why is isCollapsed the first check in selection UI?
It distinguishes a CARET from a SELECTION. A collapsed selection (start equals end) is just the blinking cursor - there is no highlighted span, so a toolbar that formats or shares text has nothing to act on. Selection-driven UIs check isCollapsed first and hide themselves for carets; forgetting it produces the floating toolbar that appears every time you click. The same check guards copy-and-share handlers: acting on an empty selection copies nothing and looks broken.
How do I select text programmatically and is that ever legitimate?
Build a Range and apply it: selection.removeAllRanges(); selection.addRange(range) - where the range might come from document.createRange() plus selectNodeContents(element), or from locating text. Legitimate uses: pre-selecting the citable portion when the user clicks a quote button (so Ctrl+C copies exactly the right span), selecting generated content after creation, and re-selecting a user's selection after a re-render restores the DOM. The line not to cross: hijacking the selection on page load or mid-read to force attention - selections belong to the user, and programmatic selection is a convenience for THEIR intent, never an attention mechanism.