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

ragtooth

v1.2.6

Published

Deliberate sawtooth rag for the web — works with React, vanilla JS, or any framework

Readme

Ragtooth

Deliberate sawtooth rag for the web.

Most tools try to smooth your rag. Ragtooth does the opposite — it shapes your text into a deliberate sawtooth pattern, alternating long and short lines to create a clean, intentional right edge. A typographic technique used in editorial and book design, now available for the web.


Install

npm install ragtooth

React 17+ is an optional peer dependency (only needed if you use RagText or useRag).


Usage

<RagText> component

import { RagText } from 'ragtooth'

export default function Article() {
  return (
    <RagText sawDepth={120} sawPeriod={2} maxTracking={0.7}>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit...
    </RagText>
  )
}

useRag hook

For more control, attach the hook to any element via ref:

import { useRag } from 'ragtooth'

export default function Paragraph({ children }) {
  const { ref } = useRag({ sawDepth: 120, sawPeriod: 2 })
  return <p ref={ref}>{children}</p>
}

Vanilla JS (no React)

import { applyRag, removeRag } from 'ragtooth'

const el = document.querySelector('p')
const original = el.innerHTML

applyRag(el, original, { sawDepth: 120, sawPeriod: 2 })

// Restore original markup
removeRag(el, original)

Options

| Option | Type | Default | Description | |---|---|---|---| | sawDepth | RagValue | 80 | How far short lines are pulled in. Higher = more pronounced sawtooth. | | sawPeriod | number | 2 | Lines per cycle. 2 = classic alternating saw. 3 = two full then one short. | | sawPhase | number | sawPeriod | Which line within each cycle is shortened (1-indexed). Default is the last line of each cycle. | | sawAlign | 'top' \| 'bottom' | 'top' | Anchor the cycle to the top or bottom of the block. 'bottom' with sawPeriod: 3 keeps the last two lines full, eliminating the awkward short-penultimate-line effect. | | maxTracking | RagValue | 0.7 | Maximum letter-spacing per line. Prevents short lines from being stretched grotesquely. |

RagValue

sawDepth and maxTracking accept a plain number (pixels) or a string with a unit:

| Value | Meaning | |---|---| | 80 | 80 px | | "80px" | 80 px (explicit) | | "20%" | 20% of the container's width | | "2em" | 2× the element's computed font-size | | "1rem" | 1× the root font-size | | "5ch" | 5× the width of the 0 glyph in the element's font |


How it works

The algorithm runs five passes on the target element:

  1. Reset — restore the element to its original HTML snapshot
  2. Widow removal — replace the last space in each block with &nbsp; to prevent orphaned final words
  3. Word wrap — walk all text nodes via TreeWalker, wrapping each word in a measurement <span> while preserving inline elements (<em>, <strong>, <a>, etc.)
  4. Line grouping — walk word spans, accumulate pixel widths, break into display:inline-block line spans separated by <br>; every nth line (controlled by sawPeriod and sawPhase) is shortened by sawDepth
  5. Tracking — distribute per-line slack as letter-spacing, capped at maxTracking

DOM reads are batched before writes to avoid layout thrashing. A ResizeObserver (debounced via requestAnimationFrame) re-runs the algorithm when the container width changes.


Notes

  • SSR safe — all DOM access is gated on typeof window !== 'undefined'; the package will not crash in Node/SSR environments
  • Dynamic contentRagText auto-tracks string children and re-snapshots on change. For non-string JSX children that change dynamically, pass a key prop to force remount: <RagText key={id}>{content}</RagText>
  • Font dependency — rag shape is font- and size-specific; the algorithm re-runs on resize but not on font-load events. Ensure fonts are loaded before mount or trigger a resize.
  • Inline elements<em>, <strong>, <a>, and other inline wrappers are preserved through the algorithm; bold and italic context is maintained at every line break
  • sawAlign: 'bottom' — uses a pre-count pass to estimate total lines, then anchors the shortened-line cycle from the bottom up. Combine with sawPeriod: 3, sawPhase: 1 for the classic pattern with guaranteed full-width last two lines

Prior art


License

MIT