Vize

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.

Open the interactive example

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
Inspect native selection in an editable guide excerpt: initial state
Initial state
Inspect native selection in an editable guide excerpt: state after the verified interactions
After the verified interactions

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.