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/axisrhythm

v1.1.10

Published

Deliberate variable font axis rhythm across paragraph lines — alternating wdth/wght values for typographic texture

Downloads

784

Readme

Axis Rhythm

npm License: MIT part of liiift type-tools

CSS applies font-variation-settings to the whole element — every line gets the same axis value. Axis Rhythm works line by line, cycling any OpenType axis through a sequence of values across paragraph lines. The result is a texture the eye reads as rhythm, not noise. Like column highlighting for text.

axisrhythm.com · npm · GitHub

TypeScript · Zero dependencies · React + Vanilla JS


Install

npm install @liiift-studio/axisrhythm

Usage

Next.js App Router: this library uses browser APIs. Add "use client" to any component file that imports from it.

Variable font required: Axis Rhythm sets font-variation-settings per line. The target font must support the axis you specify (e.g. a font with a wdth axis for axis: 'wdth'). The effect is invisible with fonts that do not have variable axis support.

React component

import { AxisRhythmText } from '@liiift-studio/axisrhythm'

<AxisRhythmText axis="wdth" values={[100, 88]} period={2} linePreservation="spacing">
  Your paragraph text here...
</AxisRhythmText>

linePreservation="spacing" prevents line overflow by compensating each line's width with letter-spacing. For display or headline text where overflow is acceptable (or part of the effect), omit it or set linePreservation="none".

React hook

import { useAxisRhythm } from '@liiift-studio/axisrhythm'

// Inside a React component:
const ref = useAxisRhythm({ axis: 'wdth', values: [100, 88], period: 2 })
return <p ref={ref}>{children}</p>

The hook re-runs automatically on resize via ResizeObserver and after fonts load via document.fonts.ready.

Vanilla JS

import { applyAxisRhythm, removeAxisRhythm, getCleanHTML } from '@liiift-studio/axisrhythm'

const el = document.querySelector('p')
const original = getCleanHTML(el)
const opts = { axis: 'wdth', values: [100, 88], period: 2 }

function run() {
  applyAxisRhythm(el, original, opts)
}

run()
document.fonts.ready.then(run)

const ro = new ResizeObserver(() => run())
ro.observe(el)

// Later — disconnect and restore original markup:
// ro.disconnect()
// removeAxisRhythm(el, original)

TypeScript

import type { AxisRhythmOptions } from '@liiift-studio/axisrhythm'

const opts: AxisRhythmOptions = { axis: 'wdth', values: [100, 88], period: 2 }

Options

| Option | Default | Description | |--------|---------|-------------| | axis | 'wdth' | Variable font axis tag, e.g. 'wdth', 'wght', 'opsz' | | values | [100, 96] | Axis values to cycle through across lines. Set period equal to the number of values for all values to appear exactly once per cycle | | period | 2 | Lines per cycle. Set equal to values.length — if smaller, trailing values are never reached; if larger, values repeat within the cycle | | align | 'top' | 'top' counts from the first line; 'bottom' counts from the last | | lineDetection | 'bcr' | 'bcr' reads actual browser layout — ground truth, works with any font and inline HTML. 'canvas' uses @chenglou/pretext for arithmetic line breaking with no forced reflow on resize (npm install @chenglou/pretext). Falls back to 'bcr' while pretext loads | | linePreservation | 'none' | 'none' — no compensation; line widths vary with the axis value (best for display type where reflow is part of the effect). 'spacing' — adjusts letter-spacing per line to match natural widths; prevents overflow; recommended for body text. 'scale' — applies a GPU scaleX transform per line; no letter-spacing changes, slight horizontal glyph compression at large axis ranges | | as | 'p' | HTML element to render, e.g. 'h1', 'div', 'li'. Accepts any valid React element type. (React component only) |


How it works

The algorithm detects visual lines by measuring word span positions with getBoundingClientRect(), then wraps each line in a <span> with its own font-variation-settings. The injected value overrides only the target axis — all other axes set on the parent element are preserved by reading and patching the computed fontVariationSettings string before writing. Runs on mount and on every resize via ResizeObserver. Re-runs when fonts finish loading (document.fonts.ready). The effect is skipped entirely if prefers-reduced-motion: reduce is set.

Line break safety: Each run starts from the original HTML, detects lines at the element's natural layout, then locks them with white-space: nowrap. Word breaks never change as a result of the axis variation.

Width overflow: Applying different axis values per line alters character widths, so lines may grow wider or narrower than the container. linePreservation: 'none' (default) is appropriate for display or headline type where the axis range is large and overflow is intentional. For body text — or any context where line edges must stay flush — use linePreservation: 'spacing' (adjusts letter-spacing to compensate) or 'scale' (GPU scaleX transform).

The linePreservation pass measures each line's natural width before applying the axis value, then applies axis and measures again. The delta becomes either a letter-spacing correction ('spacing') or a scaleX transform ('scale') per line.


Dev notes

next in root devDependencies

package.json at the repo root lists next as a devDependency. This is a Vercel detection workaround — not a real dependency of the npm package. Vercel's build system inspects the root package.json to detect the framework; without next present it falls back to a static build and skips the Next.js pipeline, breaking the /site subdirectory deploy.

The package itself has zero runtime dependencies. Do not remove this entry.


Future improvements

  • Multi-axis variation — cycle more than one axis simultaneously per line (e.g. alternate both wdth and wght independently)
  • Intersection Observer — defer the layout pass until the element enters the viewport, then re-run each time it scrolls back in
  • SSR hydration — generate stable line spans on the server to eliminate the flash-of-unstyled-text on first paint
  • RTL supportalign: 'end' to anchor the cycle to the reading direction rather than the visual start
  • Smooth re-layout — animate axis values on resize instead of snapping, for a less jarring transition when viewport width changes

Current version: v1.1.9