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-text-input-with-layout

v1.0.0

Published

React Native TextInput with onTextLayout: the layout of every rendered line, like Text#onTextLayout reports it

Readme

react-native-text-input-with-layout

React Native's TextInput with onTextLayout: the lines it lays out, measured natively and reported the way <Text onTextLayout> reports them. Draw behind, around or on top of the text as it's typed — a background per line for story captions, highlights, line counts.

  • It's React Native's own TextInput, so every prop, event and ref method works as usual.
  • Measured from the platform's own text layout as it changes: TextKit 2 on iOS, the EditText's Layout on Android. No hidden copy of the text to keep in sync.
  • Each line also comes with contentX/contentWidth: its text without the space the line was wrapped at.
  • iOS and Android, New Architecture, no other dependencies.

Requirements

  • React Native with the New Architecture (a Fabric component; tested with React Native 0.86 and Expo SDK 57).
  • iOS 15.1 or newer, Android 7.0 (API 24) or newer.

Installation

npx expo install react-native-text-input-with-layout
# or
npm install react-native-text-input-with-layout

Then rebuild the app (npx expo run:ios / run:android, or a new development build): the library has native code, so it doesn't run in Expo Go. No config plugin is needed. In a React Native app without Expo, run pod install in ios/.

Usage

import { useState } from 'react';
import { View } from 'react-native';
import {
  TextInputWithLayout,
  type TextInputLayoutLine,
} from 'react-native-text-input-with-layout';

function Caption() {
  const [text, setText] = useState('');
  const [lines, setLines] = useState<TextInputLayoutLine[]>([]);

  return (
    <View>
      {/* A background behind each line's text, under the input. */}
      {lines.map((line, index) => (
        <View
          key={index}
          style={{
            position: 'absolute',
            left: line.contentX - 6,
            top: line.y,
            width: line.contentWidth + 12,
            height: line.height,
            borderRadius: 6,
            backgroundColor: '#e2486b',
          }}
        />
      ))}
      <TextInputWithLayout
        multiline
        value={text}
        onChangeText={setText}
        onTextLayout={(event) => setLines(event.nativeEvent.lines)}
        style={{ padding: 16, fontSize: 24, color: '#fff' }}
      />
    </View>
  );
}

Line frames are relative to the input's top-left corner, padding included, so a layer positioned over the input (as above, or with StyleSheet.absoluteFill in the input's parent) takes them as they are.

API

<TextInputWithLayout>

Takes every TextInput prop, plus onTextLayout. Its ref is the TextInput's (focus(), blur(), clear(), setSelection(), …).

The input is wrapped in a native view that measures it. Styles that place the input — margin*, position and the insets, flex*, alignSelf, width/height and their min/max, aspectRatio, zIndex, transform — go to that view; all the others (padding, border, background, font, …) stay on the input.

onTextLayout(event)

event.nativeEvent.lines holds one TextInputLayoutLine per rendered line, in order:

| Field | Description | | -------------- | -------------------------------------------------------------------------------------------------------- | | text | The line's characters, with the space or line break it ends with. | | x, y | Top-left corner of the line, relative to the input's top-left corner. | | width | Width of the line. On a wrapped line, this includes the space it was broken at, as in Text. | | height | Height of the line. | | contentX | Left edge of the line's text without its trailing whitespace. On a right-to-left line, it's moved right. | | contentWidth | Width of the line's text without its trailing whitespace. Use these two to draw behind the text. | | ascender | Distance from the top of the line to its baseline. | | descender | Distance from the baseline to the bottom of the line. | | capHeight | Height of a capital letter in the line's font. | | xHeight | Height of a lowercase "x" in the line's font. |

All values are in points (dp). The fields are those of Text#onTextLayout, plus contentX and contentWidth.

It fires once the input is laid out, and again whenever its lines change: as text is typed or set from JS, and when a style or the width changes them. When the lines come out the same, it doesn't fire.

An empty input, and the line after a trailing line break, are reported as one empty line where the cursor goes.

Lines are in the coordinates of the text, not of the visible part of the input: when a multiline input with a fixed height scrolls, subtract its scroll offset (from onScroll) to place them on screen. A single-line input reports one line, which can be wider than the input when its text is.

<TextInputLayoutView>

The native view TextInputWithLayout is made of, for wrapping another TextInput-based component (say, one from a gesture or keyboard library). It takes ViewProps, onTextLayout, and value, the input's controlled value, which iOS needs to measure text set from JS. It measures the first TextInput inside it.

<TextInputLayoutView value={text} onTextLayout={onTextLayout}>
  <SomeTextInput value={text} onChangeText={setText} />
</TextInputLayoutView>

How it works

  • iOS: the view finds the UITextView (multiline) or UITextField of the input inside it, follows its text storage edits, and measures in its next layout pass. A UITextView is read through TextKit 2's NSTextLayoutManager and is never switched over to TextKit 1 (on iOS 15, or if something else already switched it, TextKit 1 is read instead). A UITextField doesn't wrap, so its single line is measured from its text.
  • Android: right before each draw, the view checks a small key (the Layout, its width, a hash of the text, the padding) and reads the EditText's Layout when it changed. React Native doesn't tell TextWatchers about text set from JS, so this catches every change; frames where nothing changed cost nothing. capHeight and xHeight are measured from "T" and "x", as React Native does for Text.

The values come from each platform's own text layout, so they differ between iOS and Android as much as the text does. On the web, the input renders as usual and onTextLayout doesn't fire.

Migrating from 0.1

0.2 replaces the input 0.1 drew itself, with Nitro, by React Native's own TextInput:

  • react-native-nitro-modules isn't needed anymore.
  • Every TextInput prop works. Events are TextInput's: onInputFocus/onInputBlur are onFocus/onBlur, and onSubmitEditing, onContentSizeChange and onSelectionChange get TextInput's events (the text is event.nativeEvent.text).
  • onTextLayout gets a native event: the lines are event.nativeEvent.lines (were event.lines). baseline is gone (it's line.y + line.ascender); contentX and contentWidth are new.
  • The ref is the TextInput's. measureLines() is gone: keep the lines from the last onTextLayout.
  • Types: TextLayout is TextInputLayoutLine, TextLayoutEvent is TextInputLayoutEvent, and TextInputWithLayoutInstance is ComponentRef<typeof TextInput>. TextInputWithLayoutView is gone; TextInputLayoutView is the wrapper described above.

Contributing

License

MIT


Made with create-react-native-library