text-selection
Track the document text selection.
| Package | @vizejs/composable/text-selection |
| Own the source | vize lib pull composable:text-selection |
| Runtime exports | useTextSelection |
| Gzip budget | 1536 B |
Minimal setup
Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).
vp install @vizejs/composable
Import the entry directly. Call lifecycle-bound utilities inside <script setup> so cleanup follows the component scope; consult the runtime contract for this entry's SSR and hydration behavior.
import { useTextSelection } from "@vizejs/composable/text-selection";
Try it
Inspect native selection in an editable guide excerpt
Keep selected text and native ranges synchronized when the browser mutates the same Selection object in place.
What to observe: Focus the excerpt, select all, then use Shift and arrow keys for a shorter selection. The text and range shape refresh on actual selectionchange events without replacing the Selection object. Clear removes every range; a collapsed caret can have empty text and one range. Edit a new local passage and select it again.
useTextSelection reads the real document after mounting and returns readonly selection-derived text, ranges and native range rectangles. Its revision invalidation handles in-place platform mutations. This is document Selection, including a native contenteditable region; it is not textarea/input selectionStart or selectionEnd. SSR and the first hydrating render expose empty text and no ranges, and scope disposal removes the listener. Clearing is an explicit native document operation. The editable excerpt owns local DOM text only: it sends no request and has no persistence or saved-state claim.
Compare the initial and interacted states


Copy the example
Save this complete SFC in your Vue project. Its public imports and source are the same ones executed in the preview.
<script setup lang="ts">
import { useId } from "vue";
import { useTextSelection } from "@vizejs/composable/text-selection";
const excerptLabelId = useId();
const { text, ranges, rects } = useTextSelection();
function clearSelection(): void {
document.getSelection()?.removeAllRanges();
}
</script>
<template>
<div class="composable-example">
<p :id="excerptLabelId">Editable guide excerpt</p>
<div
class="selection-excerpt"
contenteditable="true"
role="textbox"
aria-multiline="true"
:aria-labelledby="excerptLabelId"
tabindex="0"
>
Inspect this Vue guide.
</div>
<output aria-live="polite">Selected text: {{ text || "none" }}</output>
<p class="selection-counts">Ranges: {{ ranges.length }} · Rectangles: {{ rects.length }}</p>
<p class="selection-geometry">
First rectangle: {{ Math.round(rects[0]?.width ?? 0) }} ×
{{ Math.round(rects[0]?.height ?? 0) }} CSS pixels
</p>
<p class="selection-shape">
{{
ranges.length === 0
? "No range"
: ranges[0]?.collapsed
? "Collapsed caret"
: "Selected range"
}}
</p>
<button type="button" @click="clearSelection">Clear text selection</button>
<p>
Focus the excerpt and select with Shift and arrow keys or your mouse. Select all and edit it
to try a different local passage. The readonly text, ranges and native rectangles refresh when
the document selection changes; editing does not save this excerpt.
</p>
<p>
The browser can mutate the same Selection object in place. An empty selected text can still
have one collapsed caret range; Clear removes all ranges. This observes the document's
selection, not input or textarea selectionStart/selectionEnd. Server rendering starts with
empty text and no ranges; the actual document is read after mounting, and scope disposal
removes its listener.
</p>
</div>
</template>
<style scoped>
.selection-excerpt {
min-block-size: 80px;
padding: 12px;
border: 1px solid currentColor;
border-radius: 8px;
overflow-wrap: anywhere;
}
</style>
Runtime contract
| Utility | Category | Stability | SSR | Hydration | Cleanup | Targets | Host globals | Uses |
|---|---|---|---|---|---|---|---|---|
useTextSelection |
dom | experimental | deterministic-fallback | stable | reactive-scope | web, desktop | document |
tryOnScopeDispose |
API
useTextSelection
Track the document text selection. Re-reads the selection on every selectionchange. Because the platform mutates the same Selection object in place, a revision counter is used to invalidate the derived text, ranges, and rects. Server renders expose an empty selection; the listener is removed with the owning scope. Inside a component the host is read after mounting, so hydration renders the server fallback first and never mismatches.
function useTextSelection(options: UseTextSelectionOptions = {}): TextSelectionControls
Types
TextSelectionHost
Document-like capability observed by useTextSelection.
| Member | Type | Description |
|---|---|---|
getSelection |
() => Selection | null |
Current selection. |
UseTextSelectionOptions
Options for useTextSelection.
| Member | Type | Description |
|---|---|---|
host? |
MaybeRefOrGetter<TextSelectionHost | null | undefined> |
Reactive document capability for alternate runtimes and tests. |
TextSelectionControls
Reactive selection state returned by useTextSelection.
| Member | Type | Description |
|---|---|---|
selection |
Readonly<ShallowRef<Selection | null>> |
Current Selection object; null during server rendering. |
text |
ComputedRef<string> |
Selected text. Empty during server rendering. |
ranges |
ComputedRef<readonly Range[]> |
Selected ranges. |
rects |
ComputedRef<readonly DOMRect[]> |
Client rectangles of each selected range. |