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

@frontia/react-native-fast-text

v0.1.0-alpha.0

Published

High-performance native typewriter text reveal component for React Native (Fabric)

Readme

@frontia/react-native-fast-text

High-performance native typewriter/reveal text for React Native. The full string is committed to native once; a native frame scheduler reveals it grapheme by grapheme with zero per-frame JS work, zero re-renders and zero text re-layout.

Status: alpha. Published as 0.1.0-alpha.x under the alpha dist-tag. API may change before 0.1.0. Verified on React Native 0.86 (New Architecture) only.

Why

The usual ways to type out a known string are expensive every frame:

| approach | per frame | | --- | --- | | React state + text.slice() | new string → full React commit → native setText → full text re-layout | | Reanimated useAnimatedProps (animateable-text) | new string on the UI runtime → Spannable rebuild → setText → re-layout | | fast-text | advance one integer, move a draw range, redraw |

Measured on a Pixel emulator (release, details in docs/benchmarks.md): 72.9 % janky UI frames for the React-state approach vs 0.48 % here; 0 React re-renders during a 1,000-grapheme reveal (vs 419); layout built once per text; completion time within +0.34 % of the configured duration even under frame drops. The native render is pixel-identical (0.0000 % diff) to RN <Text> for Korean, emoji-heavy, RTL and multi-line samples.

This is a plain-text reveal component — not a streaming/Markdown renderer and not a drop-in <Text> replacement.

Requirements

  • React Native 0.86 (the only verified version so far)
  • New Architecture (Fabric) — no legacy/Paper support
  • No Expo Modules dependency. Expo apps need a development build / expo prebuild (not Expo Go).

Installation

yarn add @frontia/react-native-fast-text@alpha
cd ios && pod install

Autolinking registers everything; no manual setup.

Quick start

import { FastText, type FastTextRef } from '@frontia/react-native-fast-text';

function Dialogue({ line }: { line: string }) {
  const ref = useRef<FastTextRef>(null);

  return (
    <>
      <FastText
        ref={ref}
        text={line}
        millisecondsPerGrapheme={14}
        style={{ color: 'white', fontSize: 16, lineHeight: 26 }}
        onComplete={() => console.log('done')}
      />
      <Button title="skip" onPress={() => ref.current?.skip()} />
    </>
  );
}

Changing text starts a new reveal (with autoPlay, the default). The component reserves its final height from the first frame — put it directly inside a ScrollView and the content size is correct while the reveal is still running.

API

Props

| prop | type | default | notes | | --- | --- | --- | --- | | text | string | — | the full string; committed to native once per value | | millisecondsPerGrapheme | number | 14 | 0 reveals instantly (still fires onComplete) | | autoPlay | boolean | true | start when text commits | | playing | boolean \| undefined | undefined | declarative play/pause; undefined = uncontrolled | | onComplete | () => void | — | at most once per run; never from a replaced text | | reduceMotion | 'system' \| 'always' \| 'never' | 'system' | active ⇒ instant reveal (event still fires) | | style | TextStyle | — | supported subset below | | numberOfLines | number | unlimited | | | testID / accessibilityLabel | string | label defaults to the full text | |

Supported style keys: color, fontFamily, fontSize, fontWeight, fontStyle, lineHeight, letterSpacing, textAlign, includeFontPadding (Android). Layout styles (margin, width, …) apply to the container. Anything else — notably padding and allowFontScaling — is not supported in the alpha.

Ref

start();   // start when idle (autoPlay={false}), or resume when paused
restart(); // play the same text again from 0 (onComplete can re-fire)
pause();
resume();
skip();    // reveal everything now; completes the run (fires onComplete)
reset();   // back to nothing revealed, idle, no event

All state transitions are defined and tested: text change mid-play, restart, skip, pause/resume, background/foreground, millisecondsPerGrapheme = 0, empty string, unmount, Reduce Motion, and text replaced right before completion (stale completions are dropped via an internal generation counter).

Grapheme correctness

The reveal advances per extended grapheme cluster using platform ICU segmentation (plus defensive merges), so Hangul, CJK, combining marks, skin-tone emoji, ZWJ families (👨‍👩‍👧‍👦), flags, variation selectors and CRLF never split mid-cluster — and the duration is clusters × ms, not UTF-16 units × ms. Verified by 13 on-device segmentation tests.

Accessibility

  • The internal transparent layout-oracle <Text> is hidden from the accessibility tree; the native view is the accessible element with the full string as its default label (the reveal is visual only).
  • reduceMotion="system" (default) follows the platform Reduce Motion setting and reveals instantly when it is on.

How it works

A transparent RN <Text> inside the wrapper acts as a layout oracle (Yoga sees the final size immediately). The absolutely-positioned native view builds its platform text layout once per text/style/width change — StaticLayout on Android, TextKit on iOS — and a Choreographer / CADisplayLink scheduler derives the revealed cluster count from monotonic elapsed time, changing only a non-metric-affecting draw range per frame. Late frames catch up by elapsed time, so completion never drifts.

Details: docs/architecture.md · docs/benchmarks.md · docs/frontia-migration.md

Known limitations (alpha)

  • allowFontScaling unsupported (density-independent px only).
  • iOS mixed-bidi lines may momentarily reveal a few extra glyphs during the animation (contiguous glyph-range drawing); Android is exact.
  • Backgrounding does not pause the clock — on return the reveal catches up (possibly completing instantly).
  • 120 Hz behavior not yet measured.
  • Only RN 0.86 is verified; other versions are unsupported until tested.

Example app

example/ contains three screens: demo (playground incl. an oracle parity toggle), bench (react-state vs animateable-text vs fast-text benchmark suite with native instrumentation counters) and frontia (a replica of a production dialogue layer — gesture zones, long-press scrubbing and nested scrolling — with an automated state-transition suite).

yarn
yarn example start
yarn example android   # or: ios

License

MIT © Frontia