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

@liiift-studio/fit-flush

v1.0.2

Published

Binary-search font-size fitting — lock any text to its container width, height, or both, with variable-font axis safety

Readme

V1.1

fit-flush

npm License: MIT part of liiift type-tools

Fit text to its container. Binary-search sizing with variable-font axis safety, for the rare case neither clamp() nor container-query units will do the job.

Site · npm · GitHub

TypeScript · Zero runtime dependencies · React + Vanilla JS


The problem

CSS has no way to say "size this text exactly as large as possible without overflowing its container."

  • clamp() is viewport-linear, not container-aware.
  • Container-query units (cqw, cqh) give coarse scaling, not precise text-fit.
  • Neither is aware of variable-font axis travel — text that fits today will overflow tomorrow when an axis animates to its max.

fit-flush solves all three: it measures the text off-screen, searches for the largest font-size that fits width and/or height, and — if you pass vfSettings — holds every axis at its max during measurement so the fit survives future axis animation.


Install

npm install @liiift-studio/fit-flush

Usage

Next.js App Router: add "use client" at the top of any file using the hook or component — fit-flush touches window and ResizeObserver.

React component

"use client"
import { FitFlushText } from "@liiift-studio/fit-flush"

export default function Hero() {
	return (
		<section style={{ width: "100%", height: "60vh" }}>
			<FitFlushText as="h1" mode="both" max={320}>
				Headline
			</FitFlushText>
		</section>
	)
}

React hook

"use client"
import { useFitFlush } from "@liiift-studio/fit-flush"

// Inside a React component:
export function Title() {
	const ref = useFitFlush<HTMLHeadingElement>({ mode: "width" })
	return <h1 ref={ref}>Resizing headline</h1>
}

The hook re-runs on container resize (ResizeObserver, width-only dedup) and after web fonts load (document.fonts.ready). It cleans up on unmount.

Vanilla JS — one-shot

import { fitFlush } from "@liiift-studio/fit-flush"

const target = document.querySelector<HTMLElement>("h1")!
const size = fitFlush(target, { mode: "both", max: 240 })

Vanilla JS — live handle

import { fitFlushLive } from "@liiift-studio/fit-flush"

const target = document.querySelector<HTMLElement>("h1")!
const handle = fitFlushLive(target, { mode: "both", max: 240 })

// Later — clean up:
// handle.dispose()

fitFlushLive attaches a ResizeObserver to the container and re-fits after document.fonts.ready. Call handle.refit() to re-run manually after changing the text, and handle.dispose() to stop observing and restore the original fontSize.

Variable-font worst-case safety

If you animate variable-font axes elsewhere on the page, pass the full axis ranges so fit-flush measures at the worst case:

fitFlush(target, {
	mode: "width",
	vfSettings: {
		wght: { min: 100, default: 400, max: 900 },
		wdth: { min: 75,  default: 100, max: 125 },
	},
})

TypeScript

import { fitFlush, type FitFlushOptions } from "@liiift-studio/fit-flush"

const options: FitFlushOptions = { mode: "both", min: 12, max: 320, precision: 0.25 }
const size: number = fitFlush(document.querySelector<HTMLElement>("h1")!, options)

Options

| Option | Type | Default | Description | |---|---|---|---| | mode | 'width' \| 'height' \| 'both' | 'both' | Which container dimension(s) to fit. 'width' uses an analytical fast path (no-wrap single line). 'height' reflows normally. 'both' takes the stricter of the two. | | min | number | 8 | Minimum font-size in px. | | max | number | 400 | Maximum font-size in px. | | precision | number | 0.5 | Binary-search convergence precision in px. | | padding | number \| { x?, y? } | 0 | Inset from container edges in px. A single number insets both axes. | | vfSettings | Record<string, { max: number }> | — | Variable-font axis ranges. When present, measurement runs at every axis' max for worst-case safety. | | container | HTMLElement | target.parentElement | Override the container used for measurement. |


How it works

  1. Snapshot container — reads container dimensions in a single batch, subtracts padding.
  2. Clone probe — creates a position: fixed; left: -99999px; visibility: hidden measurement span, style-copied from the target via getComputedStyle. The probe is aria-hidden and appended to document.body — never injected into the target's subtree, so there is zero visible layout disruption during measurement.
  3. Apply max VF axis — if vfSettings is present, the probe's font-variation-settings is set to the maximum of every axis before the search begins.
  4. Search for size
    • mode: 'width' uses an analytical fast path: measure at 100 px, linearly predict the target size, verify in one write. Typically one or two measurements.
    • mode: 'height' and 'both' use binary search: ~10 iterations to converge over [8, 400] at 0.5 px precision.
  5. Write — sets target.style.fontSize to the computed size and removes the probe.
  6. Restore scroll — saves window.scrollY before mutation and restores via requestAnimationFrame (iOS Safari does not honour overflow-anchor: none, so heightmutations can trigger scroll jumps).

Line break safety

For mode: 'height' and 'both', the probe is measured with the same inner width and white-space: normal as the target. Line breaks are whatever the browser produces at the fitted size — the tool never rewrites word breaks or injects spans into your live DOM.

SSR

fitFlush and fitFlushLive are SSR-safe. On the server, fitFlush returns 0 and fitFlushLive returns a no-op handle.

prefers-reduced-motion

fit-flush v0.0.1 is a one-shot size — no animation, nothing to honour. A future animated-transition mode will gate on prefers-reduced-motion.


Dev note — next in devDependencies

The root package.json lists next in devDependencies. This is intentional — Vercel inspects the root package.json to detect the framework for the site/ subdirectory deploy. Removing next causes Vercel to fall back to a static build and skip the Next.js pipeline.


Future improvements

  • Animated transitions between target sizes on resize (gated by prefers-reduced-motion)
  • shared option — fit a group of elements to a common size for headline grids
  • onFit callback fired after every recompute
  • Rich inline HTML preservation in the probe (currently text-only)
  • Measurement caching — skip re-measurement when text, container size, and options are unchanged

Current version: v1.0.1