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

@santjc/react-pretext

v0.1.0

Published

Simple React wrapper over @chenglou/pretext for deterministic text measurement before paint, with an advanced editorial subpath.

Readme

@santjc/react-pretext

Simple React wrapper over @chenglou/pretext for deterministic text measurement before paint, without DOM reads.

@santjc/react-pretext is intentionally a small React layer over @chenglou/pretext. It lets you predict text height and line count from text, typography, and width before the browser renders the final layout. The core use case is measurement-driven UI: accordions, cards, virtualized lists, previews, and any responsive layout where text height affects placement.

The package keeps the original pretext model intact and adds React-facing hooks, types, and semantic rendering helpers where React actually helps.

The intended adoption path is:

  • define typography once
  • measure text with useMeasuredText()
  • render semantic DOM with PText
  • use predicted heights in normal UI like accordions, cards, lists, and previews
  • opt into editorial flow only when you need custom line routing

Installation

npm install @santjc/react-pretext react react-dom

Peer dependencies: React 18 or 19.

Why use it

  • Predict text height before paint
  • Avoid hidden measurement nodes and scrollHeight reads
  • Truncate text to a known number of lines without DOM reads
  • Keep measurement inputs and render styles aligned
  • Re-layout from width changes without leaving the arithmetic path
  • Drop down to lower-level hooks only when you need more control

Start Here

Measure text with one hook

import { createPretextTypography, useMeasuredText } from '@santjc/react-pretext'

function Example({ text }: { text: string }) {
  const typography = createPretextTypography({
    family: 'Inter, sans-serif',
    size: 18,
    weight: 400,
    lineHeight: 28,
    width: 320,
  })

  const { height, lineCount } = useMeasuredText({ text, typography })

  return <div>{height}px / {lineCount} lines</div>
}

Use this for the common case where a component needs a known text height, line count, or both.

Use shared typography with PText

import { PText, createPretextTypography } from '@santjc/react-pretext'

function Example() {
  const body = createPretextTypography({
    family: 'Inter, sans-serif',
    size: 18,
    weight: 400,
    lineHeight: 28,
    width: 320,
  })

  return (
    <PText as="p" typography={body}>
      Semantic text with one source of truth for measurement and render output.
    </PText>
  )
}

PText is a semantic rendering helper for the shared typography object. It is useful when you want real DOM output to stay aligned with the same measurement inputs, but the main measurement story still starts with hooks.

Let PText observe responsive width

import { PText, createPretextTypography } from '@santjc/react-pretext'

function Example() {
  const body = createPretextTypography({
    family: 'Inter, sans-serif',
    size: 18,
    weight: 400,
    lineHeight: 28,
  })

  return (
    <div style={{ width: 'min(100%, 36rem)' }}>
      <PText as="p" typography={body}>
        This paragraph does not receive an explicit width. PText observes the element width and remeasures as the container changes.
      </PText>
    </div>
  )
}

Replace hidden measurement or scrollHeight

Before:

const ref = useRef<HTMLDivElement>(null)

useLayoutEffect(() => {
  setHeight(ref.current?.scrollHeight ?? 0)
}, [text, width])

After:

import { createPretextTypography, useMeasuredText, PText } from '@santjc/react-pretext'

function AccordionBody({ isOpen, text }: { isOpen: boolean; text: string }) {
  const typography = createPretextTypography({
    family: 'Inter, sans-serif',
    size: 18,
    weight: 400,
    lineHeight: 28,
    width: 360,
  })

  const { height } = useMeasuredText({ text, typography })

  return (
    <div style={{ height: isOpen ? `${height}px` : '0px', overflow: 'hidden' }}>
      <PText as="p" typography={typography}>
        {text}
      </PText>
    </div>
  )
}

Predict measured card or list heights

import { createPretextTypography, useMeasuredText } from '@santjc/react-pretext'

function ResultCard({ text, width }: { text: string; width: number }) {
  const typography = createPretextTypography({
    family: 'Inter, sans-serif',
    size: 16,
    weight: 400,
    lineHeight: 26,
    width: width - 32,
  })

  const { height, lineCount } = useMeasuredText({ text, typography })

  return (
    <div>
      <div>{lineCount} lines</div>
      <div>predicted body height: {height}px</div>
    </div>
  )
}

This pattern works well for feeds, search results, CMS previews, issue lists, and any responsive grid where text height affects placement.

Truncate text for previews and teasers

import { PText, createPretextTypography, useTruncatedText } from '@santjc/react-pretext'

function ResultPreview({ text }: { text: string }) {
  const typography = createPretextTypography({
    family: 'Inter, sans-serif',
    size: 16,
    lineHeight: 24,
    width: 280,
  })

  const preview = useTruncatedText({
    text,
    typography,
    maxLines: 3,
  })

  return (
    <>
      <PText as="p" typography={typography}>{preview.text}</PText>
      {preview.didTruncate ? <button>Read more</button> : null}
    </>
  )
}

useTruncatedText() is meant for cards, snippets, collapsed previews, and compact list rows where the visible text itself needs to be deterministic before render.

Typography input

createPretextTypography() accepts either a CSS font shorthand string or a structured typography object.

Structured input:

const typography = createPretextTypography({
  family: 'Inter, sans-serif',
  size: 18,
  weight: 400,
  lineHeight: 28,
  width: 320,
})

Font shorthand input:

const typography = createPretextTypography({
  font: '400 18px Inter, sans-serif',
  lineHeight: 28,
  width: 320,
})

The structured form is the recommended default because it is easier to read, easier to derive from design tokens, and less fragile in application code. Internally, both forms resolve to the same font string and matching render styles.

Stable root API

The root package is the intentional React-facing surface.

  • createPretextTypography
  • useElementWidth
  • useMeasuredText
  • usePreparedText
  • usePreparedSegments
  • usePretextLayout
  • usePretextLines
  • useTruncatedText
  • PText

Drop down to usePreparedText() and usePretextLayout() when you want to control the prepare and layout phases separately. Use usePreparedSegments() with usePretextLines() when you need actual line output.

Low-level pretext API

Raw @chenglou/pretext exports live on a dedicated pretext subpath:

import {
  prepare,
  prepareWithSegments,
  layout,
  layoutWithLines,
  layoutNextLine,
  walkLineRanges,
} from '@santjc/react-pretext/pretext'

Use this subpath when you want the original low-level pretext model without the React-first root entrypoint.

Editorial API

Editorial helpers live on the advanced editorial subpath:

import {
  FlowLines,
  useTextFlow,
  flowText,
  carveLineSlots,
  createLineSlotResolver,
  getCircleBlockedLineRangeForRow,
  pickWidestLineSlot,
  EditorialColumns,
  EditorialSurface,
  type EditorialTrack,
  type EditorialFigure,
} from '@santjc/react-pretext/editorial'

These APIs are public and tested, but they are not part of the default adoption path. Reach for them when you need custom line rendering, obstacle-aware flow, or multi-column continuation.

SSR and runtime guidance

Measurement depends on canvas-backed text metrics, so the measurement hooks are a client-side feature.

  • In Next.js and other SSR frameworks, call the hooks from client components.
  • If you need a server-rendered fallback, render with a placeholder height and replace it after hydration.
  • Keep measurement logic at the edge of the UI that actually needs it instead of pushing it into shared server code.

Example with a client component boundary:

'use client'

import { createPretextTypography, useMeasuredText } from '@santjc/react-pretext'

export function MeasuredPreview({ text }: { text: string }) {
  const typography = createPretextTypography({
    family: 'Inter, sans-serif',
    size: 16,
    lineHeight: 24,
    width: 320,
  })

  const { height } = useMeasuredText({ text, typography })

  return <div style={{ minHeight: `${height}px` }}>{text}</div>
}

Webfont guidance

Measurement is only as accurate as the font you actually render.

  • Keep the measurement typography aligned with the real DOM font.
  • Expect differences until a webfont finishes loading.
  • If first-render accuracy matters, wait for the font before measuring or remeasure once the font is ready.

Example:

useEffect(() => {
  document.fonts.ready.then(() => {
    setFontsReady(true)
  })
}, [])

Caveats

  • createPretextTypography() is the recommended way to keep measurement inputs and render styles aligned.
  • PText currently supports string children only.
  • prepareOptions currently map directly to pretext preparation options, such as whiteSpace.
  • useTextFlow expects a reference-stable getLineSlotAtY callback. Memoize custom resolvers in React.
  • Editorial lineRenderMode="justify" uses pretext-derived word-spacing and will skip justification for unsupported whitespace patterns instead of delegating wrapping back to the browser.
  • EditorialFigure treats explicit x and y as overrides over placement, and clamps the result within available bounds.

Source layout

The repository keeps package boundaries visible in the source tree:

  • src/core/* backs the root package exports
  • src/editorial/* backs @santjc/react-pretext/editorial
  • src/test/* holds cross-entrypoint package tests

Playground helpers stay in apps/playground/src/lib/* unless they are intentionally promoted into the package with matching tests and docs.