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

@pretext-studio/core

v0.2.0

Published

React hooks for pixel-perfect text layout prediction — no DOM reflow, no layout shifts

Readme

@pretext-studio/core

React hooks for pixel-perfect text layout prediction — no DOM reflow, no layout shifts.

Built on top of @chenglou/pretext, a pure-JS text layout engine that computes line breaks and heights from font metrics alone — before anything renders.

npm install @pretext-studio/core @chenglou/pretext

Why

The browser can't tell you how tall a block of text will be until it lays it out. This forces one of two bad patterns:

  • Guess and adjust — render first, measure with getBoundingClientRect, update. Causes layout shift.
  • Arbitrary capsmax-height: 9999px for accordion animation. Causes broken easing.

@pretext-studio/core gives you exact pixel heights and line counts before paint, with zero DOM reads.


Installation

npm install @pretext-studio/core @chenglou/pretext

Both packages are required. @chenglou/pretext is a peer dependency — the layout engine that does the measurement.


Hooks

useTextLayout

Predicts the height and line count of a block of text at a given width.

import { useTextLayout } from '@pretext-studio/core'

function Article({ text }: { text: string }) {
  const { height, lineCount, isReady } = useTextLayout({
    text,
    font: '16px Inter, sans-serif',
    width: 640,
    lineHeight: 24,
  })

  return (
    <div style={{ minHeight: isReady ? height : undefined }}>
      {text}
    </div>
  )
}

Options

| Prop | Type | Description | |------|------|-------------| | text | string | The text to measure | | font | string | CSS font string — must match what the browser uses | | width | number | Container width in px | | lineHeight | number | Line height in px |

Returns { height, lineCount, isReady }


useBubbleMetrics

Finds the tightest possible bubble width that keeps the same line count. Eliminates the dead space CSS fit-content leaves on short last lines.

import { useBubbleMetrics } from '@pretext-studio/core'

function ChatBubble({ text, side }: { text: string; side: 'sent' | 'recv' }) {
  const { tightWidth, height, isReady } = useBubbleMetrics({
    text,
    font: '15px Helvetica Neue, sans-serif',
    lineHeight: 20,
    maxWidth: 280,
    paddingH: 12,
    paddingV: 8,
  })

  return (
    <div style={{
      width: isReady ? tightWidth : undefined,
      height: isReady ? height : undefined,
      alignSelf: side === 'sent' ? 'flex-end' : 'flex-start',
    }}>
      {text}
    </div>
  )
}

Options

| Prop | Type | Default | Description | |------|------|---------|-------------| | text | string | — | Message text | | font | string | — | CSS font string | | lineHeight | number | — | Line height in px | | maxWidth | number | — | Max bubble width including padding | | paddingH | number | 12 | Horizontal padding each side | | paddingV | number | 8 | Vertical padding each side |

Returns { tightWidth, height, lineCount, wastedPixels, isReady }


useStableList

Pre-computes heights for a list of items so the list can be rendered without layout thrash. Useful for virtualized lists, masonry grids, and any list where you need heights before render.

import { useStableList } from '@pretext-studio/core'

function MessageList({ messages }: { messages: { id: string; text: string }[] }) {
  const { heights, totalHeight, isReady } = useStableList({
    items: messages,
    font: '15px Helvetica Neue, sans-serif',
    width: 360,
    lineHeight: 22,
    paddingV: 16,
  })

  return (
    <div style={{ height: totalHeight, position: 'relative' }}>
      {isReady && messages.map((msg, i) => {
        const top = messages.slice(0, i).reduce((sum, m) => sum + (heights.get(m.id) ?? 0), 0)
        return (
          <div key={msg.id} style={{ position: 'absolute', top, height: heights.get(msg.id) }}>
            {msg.text}
          </div>
        )
      })}
    </div>
  )
}

Options

| Prop | Type | Default | Description | |------|------|---------|-------------| | items | { id: string; text: string }[] | — | List items | | font | string | — | CSS font string | | width | number | — | Container width in px | | lineHeight | number | — | Line height in px | | paddingV | number | 16 | Vertical padding per item |

Returns { heights: Map<string, number>, totalHeight, isReady }


MeasuredText

A drop-in component that pre-sizes itself to the predicted height and optionally reports prediction mismatches for debugging.

import { MeasuredText } from '@pretext-studio/core'

<MeasuredText
  text="The quick brown fox..."
  font="16px Inter, sans-serif"
  width={640}
  lineHeight={24}
  debug={process.env.NODE_ENV === 'development'}
  onMismatch={({ predicted, actual, delta }) => {
    console.warn(`Layout mismatch: predicted ${predicted}px, got ${actual}px (Δ${delta}px)`)
  }}
/>

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | text | string | — | Text content | | font | string | — | CSS font string | | width | number | — | Container width in px | | lineHeight | number | — | Line height in px | | debug | boolean | false | Show predicted height/line overlay | | onMismatch | (m: LayoutMismatch) => void | — | Called when actual height differs by >2px | | style | React.CSSProperties | — | Extra styles on the wrapper div | | className | string | — | Class name on the wrapper div |


clearAllCaches

The SDK caches prepare() results internally (LRU, max 2000 entries). Call this if you need to free memory explicitly.

import { clearAllCaches } from '@pretext-studio/core'

clearAllCaches()

The font string

The font prop must exactly match the CSS font the browser will use — same family, size, weight, and style. A mismatch between the font you pass and the font the browser renders will produce wrong measurements.

// Match your CSS exactly
font: '16px Inter, sans-serif'
font: '500 15px "Helvetica Neue", Helvetica, Arial, sans-serif'
font: 'italic 14px Georgia, serif'

If you're using a web font, make sure it's loaded before measurements run. Use the isReady flag to gate renders until the font is available.


TypeScript

All hooks and the component are fully typed. Key types:

interface TextLayoutOptions {
  text: string
  font: string
  width: number
  lineHeight: number
}

interface TextLayoutResult {
  height: number
  lineCount: number
  isReady: boolean
}

interface BubbleMetrics {
  tightWidth: number
  height: number
  lineCount: number
  wastedPixels: number
}

interface LayoutMismatch {
  predicted: number
  actual: number
  delta: number
}

License

MIT