All hooks / Events and input

useCaretPosition

705 B minified and brotliedSourceMarkdown✓ Works in all modern browsers

The caret's position on screen in a <textarea> or text <input>, or null while the field isn't focused. Returns a callback ref for the field.

Browsers don't expose caret coordinates, so this measures a hidden copy of the field's text and styles. Updates on typing, clicks, arrow keys, selection changes, and scrolling inside the field or the page, at most once per frame. Coordinates are in viewport pixels, for a position: fixed popup. Safe with controlled fields: it never updates state during their events.

Import

import { useCaretPosition } from '@vincent.ri/react-tils'
// or only this hook
import { useCaretPosition } from '@vincent.ri/react-tils/useCaretPosition'

Usage

const [ref, caret] = useCaretPosition()
const [text, setText] = useState('')
const mentioning = /@\w*$/.test(text)
return (
  <>
    <textarea ref={ref} value={text} onChange={(e) => setText(e.target.value)} />
    {mentioning && caret && (
      <ul style={{ position: 'fixed', top: caret.top + caret.height, left: caret.left }}>…</ul>
    )}
  </>
)

Live demo

Type @ anywhere in the text: the popup follows the caret.

not focused

When to use it

Placing an @mention, /command, emoji, or autocomplete popup next to the caret, like in chat and comment inputs.

When not to

The popup can sit below the whole field; position it from the field's own box. For rich text editors, use the editor's selection API; for text selected elsewhere on the page, use useTextSelection.

API

function useCaretPosition<T extends HTMLTextAreaElement | HTMLInputElement = HTMLTextAreaElement>(): [(element: T | null) => void, CaretPosition | null]

Returns [ref, position]. Pass ref to the textarea or input.

Types

export type CaretPosition = {
  /** Top of the caret line, in viewport pixels. Use with `position: fixed`. */
  top: number;
  /** Left of the caret, in viewport pixels. */
  left: number;
  /** Line height at the caret, in pixels. Place a popup at `top + height`. */
  height: number;
};

Browser support

UsesChromeEdgeFirefoxSafariChrome AndroidSafari iOS
Pointer eventsWidely available55+12+59+13+55+13+
selectionchange eventWidely available11+12+52+5.1+18+5+
requestAnimationFrameWidely available24+12+23+7+25+7+

First version with support, from MDN's compatibility data (web-features 3.42.0, @mdn/browser-compat-data 8.1.5).

Coming from another library?

Other libraries call this useCaretCoordinates, textarea-caret.