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

@sizzlorox/react-pretext

v0.1.5

Published

React hooks and components for @chenglou/pretext — high-performance text measurement and layout without DOM reflows

Readme

react-pretext

React hooks and components for @chenglou/pretext — high-performance text measurement and layout without DOM reflows.

Live Demo

What is this?

Pretext measures and lays out multiline text using pure arithmetic, avoiding the expensive DOM operations (getBoundingClientRect, offsetHeight) that cause layout reflow. This library wraps its two-phase API in idiomatic React hooks and a ready-to-use component.

Install

npm install @sizzlorox/react-pretext @chenglou/pretext

Requires React 18+.

Quick start

import { PretextText } from '@sizzlorox/react-pretext'

function App() {
  return (
    <PretextText
      text="The quick brown fox jumps over the lazy dog."
      font="16px Georgia, serif"
      lineHeight={24}
      style={{ width: '300px' }}
    />
  )
}

Or with the hooks directly:

import { usePretext } from '@sizzlorox/react-pretext'

function MyText({ text, maxWidth }: { text: string; maxWidth: number }) {
  const result = usePretext(text, {
    font: '16px Inter, sans-serif',
    maxWidth,
    lineHeight: 24,
  })

  return <div style={{ height: result?.height }}>{text}</div>
}

API

usePretext(text, options) — combined convenience hook

The main hook for most use cases. Handles both the prepare and layout phases internally.

usePretext(text: string, options: UsePretextOptions): LayoutResult | null
usePretext(text: string, options: UsePretextOptions & { withLines: true }): LayoutLinesResult | null

Options:

| Option | Type | Description | |--------|------|-------------| | font | string | CSS font string, e.g. "16px Inter, sans-serif" | | maxWidth | number | Container width in pixels | | lineHeight | number | Line height in pixels | | prepareOptions | PrepareOptions? | e.g. { whiteSpace: 'pre-wrap' } | | withLines | boolean? | When true, returns per-line text data |

Returns null when text/font is empty or maxWidth is 0.


usePretextContainerWidth(ref) — measure container width

usePretextContainerWidth(ref: RefObject<HTMLElement | null>): number | null

Tracks an element's pixel width via ResizeObserver. Returns null until the first measurement (SSR-safe — no hydration mismatch). Use this to feed a responsive container width into usePretext.

const ref = useRef<HTMLDivElement>(null)
const width = usePretextContainerWidth(ref)

const result = usePretext(text, {
  font: '16px Inter, sans-serif',
  maxWidth: width ?? 0,
  lineHeight: 24,
})

return <div ref={ref}>...</div>

Granular hooks

For more control, use the lower-level hooks directly.

usePreparedText(text, font, options?) — Phase 1

Memoizes prepare(). Returns PreparedText | null.

usePreparedText(text: string, font: string, options?: PrepareOptions): PreparedText | null

usePreparedTextWithSegments(text, font, options?) — Phase 1 (with lines)

Memoizes prepareWithSegments(). Required for usePretextLines. Returns PreparedTextWithSegments | null.

usePreparedTextWithSegments(text: string, font: string, options?: PrepareOptions): PreparedTextWithSegments | null

usePretextLayout(prepared, maxWidth, lineHeight) — Phase 2

Runs layout(). Returns { lineCount, height } | null.

usePretextLayout(prepared: PreparedText | null, maxWidth: number, lineHeight: number): LayoutResult | null

usePretextLines(prepared, maxWidth, lineHeight) — Phase 2 (with lines)

Runs layoutWithLines(). Returns { lineCount, height, lines[] } | null.

usePretextLines(prepared: PreparedTextWithSegments | null, maxWidth: number, lineHeight: number): LayoutLinesResult | null

Example — virtualized list:

import { usePreparedText, usePretextLayout } from '@sizzlorox/react-pretext'

function ListItem({ text, font, containerWidth }: Props) {
  // Memoized: re-runs only when text or font changes
  const prepared = usePreparedText(text, font)

  // Pure arithmetic: safe to call every render
  const layout = usePretextLayout(prepared, containerWidth, 20)

  return <div style={{ height: layout?.height ?? 'auto' }}>{text}</div>
}

Example — custom rendering per line:

import { usePreparedTextWithSegments, usePretextLines } from '@sizzlorox/react-pretext'

function HighlightedText({ text, font, width, lineHeight, searchTerm }: Props) {
  const prepared = usePreparedTextWithSegments(text, font)
  const result = usePretextLines(prepared, width, lineHeight)

  return (
    <div>
      {result?.lines.map((line) => (
        <div key={line.start.segmentIndex} style={{ height: lineHeight }}>
          {line.text.includes(searchTerm) ? <mark>{line.text}</mark> : line.text}
        </div>
      ))}
    </div>
  )
}

<PretextText> — drop-in component

Renders text using pretext layout. Measures its own container width automatically.

| Prop | Type | Description | |------|------|-------------| | text | string | Text to display | | font | string | CSS font string (must match the rendered font) | | lineHeight | number | Line height in pixels | | prepareOptions | PrepareOptions? | Forwarded to prepareWithSegments | | lineClassName | string? | Class applied to each line <span> | | lineStyle | CSSProperties? | Style applied to each line <span> | | ...rest | HTMLAttributes<HTMLDivElement> | Forwarded to the container <div> |

Note: The font prop must exactly match the element's computed CSS font. The browser renders text using the computed font; pretext measures using the font prop. A mismatch causes incorrect line breaks.


Low-level layout primitives

Re-exported from @chenglou/pretext for advanced use cases where you need to lay out text with a varying width per line (e.g. flowing text around shapes).

layoutNextLine(prepared, cursor, maxWidth)

Lays out a single line starting at cursor, constrained to maxWidth. Returns the line's text and an updated cursor pointing to the next character, or null when the text is exhausted.

import { layoutNextLine } from '@sizzlorox/react-pretext'
import type { LayoutCursor } from '@sizzlorox/react-pretext'

let cursor: LayoutCursor = { segmentIndex: 0, graphemeIndex: 0 }

while (true) {
  const line = layoutNextLine(prepared, cursor, maxWidthForThisLine)
  if (!line) break
  // line.text — the text for this line
  // line.end  — cursor to pass to the next call
  cursor = line.end
}

walkLineRanges(prepared, cursor, maxWidth, callback)

Iterates over grapheme ranges within a single line, calling callback for each segment. Useful for rendering inline highlights or mixed styles without splitting the string manually.


Utilities

Re-exported from @chenglou/pretext:

import { clearCache, setLocale } from '@sizzlorox/react-pretext'

setLocale('ja-JP') // Affects word-break rules for CJK, Arabic, etc.
clearCache()       // Release measurement caches (e.g. after locale change)

How it works

Pretext uses a two-phase design:

  1. Prepare (once per text+font): Segments the text and pre-measures character widths using the browser's font engine. ~19ms for 500 texts.
  2. Layout (per render): Pure arithmetic over the cached widths. ~0.09ms per call — safe to run every render.

usePreparedText / usePreparedTextWithSegments memoize Phase 1 so it only reruns when text or font changes. usePretextLayout / usePretextLines run Phase 2 on every render but this is negligible.

License

MIT