Selection
A floating toolbar that follows a text selection, anchored to the start of the highlight.
Anatomy
A scoped root that listens for selection events inside a child region. When a non-empty selection settles after pointerup, the toolbar appears in a portal at the start of the first visual rect. The action buttons receive the selected text and decide what happens next.
The component is unopinionated about actions. Add any combination of buttons — copy, quote, share, highlight, define, cite. Each button receives the selected text and decides what to do with it.
Behavior
- The toolbar appears once a selection settles after
pointeruportouchend, never during the drag. Following the cursor mid-selection feels wrong, so the toolbar waits for the user to release. - Selections that start or end outside
SelectionContentare ignored. TwoSelectionroots on the same page do not interfere with each other. - When the preferred
sidewill not fit, the toolbar flips to the other side. When it still does not fit either way, it clamps withinedgePaddingof the viewport edge. The actual rendered side lands ondata-sidefor consumers who want to style it. - The toolbar repositions on scroll and resize, including the visual viewport, so the on-screen keyboard and pinch zoom keep things anchored.
- Clicking the toolbar does not deselect the text. The toolbar disables its own mousedown so the highlight survives the click.
Dismiss
The toolbar closes on any of these events. All of them fire onOpenChange(false) if the consumer is listening.
- The selection collapses, either because the user clicks elsewhere or starts a new selection.
- The user clicks outside both
SelectionContentand the toolbar. - The user presses
Escape. - A
SelectionButtonaction runs withclearOnSelectorcloseOnSelect(both true by default).
Mobile
The native callout from iOS and Android shares the screen with the toolbar. The component does not try to suppress the OS menu — it sits below it and relies on the user dismissing whichever one they do not want. The toolbar repositions against window.visualViewport, so the on-screen keyboard does not push it off-screen.
Selection still works through touch: long-press to start, drag the handles to adjust, lift to commit. The same onSelect and SelectionButton handlers fire that fire on desktop.
API
Reference for each part of the component, including its available props and behavior.
Selection
The root. Owns selection state, attaches the document-level listeners scoped to SelectionContent, and renders nothing of its own. Pass it children including a SelectionContent and a SelectionToolbar.
The root has no DOM. It only provides context. The actual selectable region is whatever SelectionContent wraps, and the toolbar is rendered through a portal to document.body so layout containers do not clip it.
SelectionContent
The scope. Anchors the listeners to a specific subtree. Selections that touch any node outside this subtree are ignored, so the toolbar only opens for text the consumer marked as eligible.
The element itself is unstyled. Add classes for whatever typography the content needs.
SelectionToolbar
The floating surface. Portals to document.body, fixes itself to the viewport, and anchors to the first visual rect of the current selection. Repositions on scroll and resize, flips to the opposite side when the preferred side does not fit, and clamps within the viewport's edgePadding.
Toolbar size is taken from the rendered content, so widths can vary by how many actions the consumer drops in.
SelectionButton
An action button bound to the toolbar. Extends the kit's Button with a ghost default variant and tighter spacing, and adds onSelect to receive the selected text.
Two flags control the post-click behavior. clearOnSelect clears the browser selection (the highlight disappears, the toolbar closes via the selectionchange listener). closeOnSelect only hides the toolbar without touching the highlight, useful for highlight-then-annotate flows. Both default to true.
Call event.preventDefault() in onClick to block both the action and the close. The onClick runs before onSelect, so this is the place to gate the action behind a confirmation.
SelectionSeparator
A thin vertical divider for visual grouping inside the toolbar. Inert and aria-hidden.
useSelection
Hook for reading the current selection from a custom part inside the root. Use it when building a custom action that needs the selection but does not want the styling of SelectionButton, or to drive an outside affordance from the same state.
Returns:
text— the selected text as a string. Empty string when nothing is selected.range— the liveRangeobject backing the selection, ornullwhen nothing is selected. Live, so it tracks edits the user makes to the same selection.rect— the first visualDOMRectof the range (the start of the selection in viewport coordinates), ornullwhen nothing is selected. Snapshot, so consumers anchoring their own UI to the selection should recompute fromrangeon scroll.open— whether the toolbar is currently visible. Reflects the controlled or uncontrolled state of the root.dismiss()— closes the toolbar without clearing the highlight.clearSelection()— clears the browser selection, which in turn closes the toolbar through the selectionchange listener.
dismiss and clearSelection are exposed separately so consumers can compose either behavior. Most actions want both, which is the default for SelectionButton.