npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-native-caret-overlay

v0.2.0

Published

Positions a React Native view at a TextInput's caret, natively, with no JS round trip

Downloads

427

Readme

react-native-caret-overlay

Render any React Native view at the native caret of a multiline TextInput. It is intended for custom carets such as glowing bars, animated shapes, branded indicators, or anything that cannot be expressed with selectionColor.

The package reads the caret position from the text field's native text layout. It does not measure a mirrored <Text>, estimate glyph widths, or send the position through JavaScript on every keystroke. This keeps the overlay aligned during IME composition, line wrapping, emoji input, bidirectional text, selection movement, and internal TextInput scrolling.

Requirements

  • React Native New Architecture
  • React Native 0.85 or a compatible Codegen version
  • A multiline TextInput
  • iOS and Android

Installation

npm install react-native-caret-overlay

Install iOS pods after adding the package:

cd ios
pod install

Rebuild the native application. A Metro reload or over-the-air JavaScript update cannot add the native view manager to an existing binary.

Basic usage

CaretOverlayView expects exactly two direct children, in this order:

  1. A subtree containing the TextInput. The first native text input found in this subtree is tracked.
  2. The custom caret view.
import {useState} from 'react';
import {Platform, StyleSheet, TextInput, View} from 'react-native';
import {CaretOverlayView} from 'react-native-caret-overlay';

export function Composer() {
  const [text, setText] = useState('');

  return (
    <CaretOverlayView style={styles.field}>
      <View>
        <TextInput
          value={text}
          onChangeText={setText}
          multiline
          // Keep UIKit's caret geometry available for the iOS loupe.
          caretHidden={Platform.OS !== 'ios'}
          style={styles.input}
        />
      </View>

      <View pointerEvents="none" style={styles.caret}>
        <View style={styles.glow} />
      </View>
    </CaretOverlayView>
  );
}

const styles = StyleSheet.create({
  field: {
    flex: 1,
    backgroundColor: '#111',
  },
  input: {
    fontSize: 17,
    lineHeight: 22,
    padding: 0,
  },
  caret: {
    position: 'absolute',
    left: 0,
    top: 0,
    // On iOS this opaque layer masks the system caret without disabling the
    // geometry UIKit needs for its multiline loupe.
    backgroundColor: '#111',
  },
  glow: {
    width: 3,
    height: 30,
    borderRadius: 2,
    backgroundColor: '#fff',
    shadowColor: '#fff',
    shadowOpacity: 1,
    shadowRadius: 8,
  },
});

The second child must start at the overlay's origin:

{position: 'absolute', left: 0, top: 0}

Native code moves a native-owned container from that origin. Transforms and animations inside the React caret child are safe.

The custom caret's own height is used and vertically centered on the native text line. It may therefore be taller than the system caret. The caret child itself should be opaque on iOS; animate nested content rather than fading the mask.

Caret visibility and selections

The package positions the second child; it does not decide when that child should be visible. Keep the second child mounted so the native target remains stable, and hide it with opacity unless:

  • the input is focused; and
  • the selection is collapsed (start === end).
const [focused, setFocused] = useState(false);
const [collapsed, setCollapsed] = useState(true);

<CaretOverlayView>
  <View>
    <TextInput
      multiline
      caretHidden={Platform.OS !== 'ios'}
      onFocus={() => setFocused(true)}
      onBlur={() => setFocused(false)}
      onSelectionChange={event => {
        const {start, end} = event.nativeEvent.selection;
        setCollapsed(start === end);
      }}
    />
  </View>

  <View
    pointerEvents="none"
    style={[styles.caret, !(focused && collapsed) && {opacity: 0}]}>
    <CustomCaret />
  </View>
</CaretOverlayView>

On iOS, React Native implements caretHidden by returning CGRectZero from caretRectForPosition:. That also makes UIKit's multiline loupe magnify the wrong line. Leave the system caret enabled on iOS and have the custom caret include an opaque background-colored mask beneath its animated content. During long-press and selection gestures, the package hides the custom caret so the loupe contains only UIKit's caret.

API

CaretOverlayView

A native view with standard React Native ViewProps and one additional prop:

| Prop | Type | Default | Description | | --- | --- | --- | --- | | enabled | boolean | true | Stops native caret positioning without unmounting the children. |

CaretOverlayViewProps

The exported TypeScript type for CaretOverlayView.

How positioning works

iOS

The wrapper finds the first UITextView inside its first child.

  • Text edits are observed with UITextViewTextDidChangeNotification.
  • While the field is editing, a display link observes selection-only movement without replacing React Native's UITextViewDelegate.
  • contentOffset is observed for internal multiline scrolling.
  • The base UITextView implementation of caretRectForPosition: provides geometry even when React Native has hidden its caret.
  • That rect is converted into the overlay's coordinate system.
  • A native-owned, non-interactive container avoids Fabric transform resets and is hidden while UIKit's loupe or selection interaction is active.

No delegate proxying, method swizzling, or private UIKit API is used.

Android

The wrapper finds the first EditText inside its first child.

  • A TextWatcher updates after text edits.
  • An OnPreDrawListener handles selection-only changes and scrolling.
  • Layout.getPrimaryHorizontal() provides the horizontal caret coordinate.
  • Line top, line height, padding, and scroll offsets come from the same Layout used to draw the text.
  • Ancestor offsets are accumulated into the overlay's coordinate system.

Why native layout matters

A JavaScript mirror can be close for simple Latin text, but it can diverge from the input for:

  • marked text and IME composition;
  • Vietnamese tone composition;
  • emoji and multi-code-point grapheme clusters;
  • ligatures and font shaping;
  • bidirectional text;
  • a caret moved into the middle of a wrapped word;
  • platform-specific padding and multiline scrolling.

This package asks the native text widget for the caret produced by the layout it already rendered, avoiding a second text-layout model.

Current limitations

  • Only multiline TextInput is supported. The iOS implementation tracks the UITextView backing a multiline React Native input.
  • The first text input found inside the first child is tracked.
  • The caller owns focus and range-selection visibility.
  • For a correct iOS loupe, callers must not set caretHidden on iOS and should mask the system caret beneath their custom caret.
  • A native rebuild is required when first installing the package.

Development status

The package is tested against React Native 0.85.3 with the New Architecture:

  • Android Codegen and assembleDebug
  • iOS Codegen, CocoaPods integration, and simulator compilation
  • TypeScript consumption and Metro bundling from a React Native application

Device-level interaction should still be verified for the target application's fonts, IMEs, and keyboard configuration.

License

MIT