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

altsvg

v1.0.0

Published

Turn alt text into a same-size SVG placeholder: wrapped, fitted and accessible. For broken images, lite modes, offline pages and mockups.

Downloads

40

Readme

+--[ altsvg ]--------------------------------------------+
|                                                        |
|                                                        |
|         Mountains at sunrise over a still lake         |
|                                                        |
|                                                        |
+------------------------------------------- 400x250 ----+
npm i altsvg
import { generateAltSVG } from "altsvg";

generateAltSVG(400, 250, "Mountains at sunrise over a still lake");
// <svg xmlns="http://www.w3.org/2000/svg" width="400" height="250" viewBox="0 0 400 250" role="img" ...>

Why

When an image is missing, pages break in quiet ways. Thumbnails collapse, captions jump up, overlays land on top of the text, and the reader has no idea what was there. altsvg puts an SVG of the same size in the image’s place, showing its alt text. The layout holds and the meaning survives.

Common uses:

  • Broken images. Swap any image that 404s, times out or is blocked for its alt text, in one line.
  • Lite and data-saver modes. Skip image downloads and keep a page that reads well and doesn’t shift as it loads.
  • Offline copies and archives. Strip heavy media from saved pages and documentation bundles without wrecking the layout.
  • Mockups and tests. Exact-size placeholders that say what goes there, generated locally with no image service.
  • Alt text review. Render a page with every image replaced by its alt text, so file names, empty alts and vague descriptions stand out.
  • Static sites and pipelines. Rewrite generated HTML at build time with the CLI or one function call.

Features

  • Two styles. plain draws clean, fitted sans-serif text. ascii draws a printed, terminal-style card: a +--[ alt ]--+ frame in monospace, which also comes out as plain text.
  • Fits the text to the box. Text is wrapped using per-character font metrics. If it still doesn’t fit, the font shrinks toward a minimum size, and only then is the last line truncated with an ellipsis. Text never spills outside the image.
  • Same size, scales with CSS. Width and height match the image, and a viewBox keeps the text in proportion under max-width: 100%.
  • Accessible. Every SVG has role="img", an aria-label and a <title>.
  • Safe with any input. Text is XML-escaped exactly once, and characters that would break XML are removed.
  • Any script. Long words, URLs and text without spaces (such as CJK) are split by grapheme, so emoji and accented letters stay intact.
  • Tiny images handled. Boxes too small for legible text get a clean frame, with the text kept in <title>.
  • Runs anywhere. The output is a plain string, so it works in Node, workers, edge runtimes and browsers. There are zero dependencies, the browser bundle is 4.7 kB gzipped, and TypeScript types are included.

Install

npm i altsvg

Or load it from a CDN, which exposes everything on the AltSVG global:

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/altsvg.min.js"></script>

Requires Node.js 18.3 or later. Works with import and require.

Usage

Generate a placeholder

import { generateAltSVG, generateAltSVGDataURI } from "altsvg";

// SVG markup
const svg = generateAltSVG(400, 250, "Mountains at sunrise over a still lake");

// A compact data URI, for <img src> or CSS backgrounds
const src = generateAltSVGDataURI(400, 250, "Mountains at sunrise over a still lake", {
  background: "none",
  borderColor: null,
});

The ASCII style

import { generateAltSVG, generateAltASCII } from "altsvg";

// A printed, terminal-style card as SVG, at exactly 400 × 250
const card = generateAltSVG(400, 250, "Mountains at sunrise over a still lake", {
  style: "ascii",
  label: "hero.jpg", // tag in the top border (default "alt", null for none)
});

// The same card as plain text, for terminals, READMEs, logs or print
console.log(generateAltASCII(400, 100, "Mountains at sunrise over a still lake"));
+-[ alt ]---------------------------------------------+
|                                                     |
|       Mountains at sunrise over a still lake        |
|                                                     |
|                                                     |
+------------------------------------------ 400x100 --+

The frame, label and pixel size fill the image box. Each run of characters is pinned to its grid cell, so the card lines up in any monospace font. style: "ascii" works everywhere options are accepted, including installAltFallback and replaceImagesWithPlaceholders. Boxes too small for a readable grid fall back to the plain style.

Replace broken images in the browser

import { installAltFallback } from "altsvg";

// Replaces images that have already failed, and any that fail later,
// including images added to the page after this call.
const stop = installAltFallback(document);

// Stop watching when you no longer need it
stop();

With no bundler:

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/altsvg.min.js"></script>
<script>
  AltSVG.installAltFallback();
</script>

The size comes from the image’s width and height attributes, then its rendered size, then defaultWidth × defaultHeight (300 × 150). To replace a single image immediately, call replaceImage(img).

Rewrite an HTML string

import { replaceImagesWithPlaceholders } from "altsvg";

const lite = replaceImagesWithPlaceholders(html, {
  filter: (img) => Number(img.width) >= 48, // keep small icons as real images
  svg: { background: "#f4f4f5" },
});

For each <img> with numeric width and height, replaceImagesWithPlaceholders:

  • sets src to a placeholder data URI;
  • removes srcset, sizes, data-src and data-srcset, so the original is never requested;
  • adds the class altsvg-placeholder;
  • keeps every other attribute.

Images without a usable size are left untouched.

Command line

# One placeholder
npx altsvg 400 250 "Mountains at sunrise" -o placeholder.svg

# As a data URI
npx altsvg 120 80 "A red fox" --data-uri

# The printed ASCII card, as SVG or as text in your terminal
npx altsvg 400 250 "Mountains at sunrise" --ascii -o card.svg
npx altsvg 400 250 "Mountains at sunrise" --text --label hero.jpg

# Rewrite every sized <img> in a file (or stdin with -)
npx altsvg --html page.html -o page.lite.html
cat page.html | npx altsvg --html - --no-border --background none

| Flag | Effect | | ----------------------- | ------------------------------------------ | | -o, --out <file> | Write to a file instead of stdout | | --data-uri | Print a data: URI instead of raw SVG | | --html <file\|-> | Rewrite <img> tags in an HTML file | | --ascii | Use the printed ASCII style | | --text | Print the ASCII card as plain text | | --label <text> | ASCII: tag in the top border | | --no-label | ASCII: no tag in the top border | | --no-size | ASCII: no pixel size in the bottom border | | --font-size <px> | Preferred font size (default 14) | | --min-font-size <px> | Smallest size before truncating (default 9)| | --padding <px> | Inner padding (default 8) | | --background <color> | Background fill, or none | | --text-color <color> | Text colour | | --border <color> | Border colour | | --no-border | Draw no border |

API

Functions

| Function | Returns | Description | | ------------------------------------------------ | ------------ | --------------------------------------------------------------------------- | | generateAltSVG(width, height, alt, options?) | string | SVG markup of exactly width × height px. Throws RangeError on a non-positive or non-finite size. Empty alt text shows fallbackText. | | generateAltSVGDataURI(width, height, alt, options?) | string | The same SVG as a URL-encoded data:image/svg+xml URI. | | generateAltASCII(width, height, alt, options?) | string | The ASCII-style card as plain text, matching what style: "ascii" draws at that pixel size. Empty string when the box is too small. | | installAltFallback(root?, options?) | () => void | Browser only. Replaces images under root (default document) that fail to load, now or later. Returns a function that stops watching. | | replaceImage(img, options?) | void | Browser only. Replaces one image right away. Calling it twice is safe. | | replaceImagesWithPlaceholders(html, options?) | string | Rewrites <img> tags in an HTML string. Options: svg, filter(attrs), className. | | svgToDataURI(svg) | string | Encodes any SVG string as a data URI. | | layoutText(text, innerWidth, innerHeight, opts)| TextLayout | Runs only the fitting step and returns { lines, fontSize, lineHeight, truncated }. |

Also exported: layoutAscii, wrapText, ellipsize, measureText, measureCells, charWidth, escapeXML and normalizeAltText.

Options

| Option | Default | Description | | --------------- | ------------------------------------------------- | -------------------------------------------------------- | | style | "plain" | "ascii" for the printed, monospace card | | fontSize | 14 (12 for ASCII) | Preferred font size in px | | minFontSize | 9 | Smallest font size tried before truncating | | lineHeight | 1.3 | Line height as a multiple of the font size | | padding | 8 | Inner padding, capped at 10% of the shorter side | | fontFamily | Arial, Helvetica, "Liberation Sans", sans-serif | Font stack. The default matches the built-in metrics | | background | #f8f9fa | Background fill, or "none" for transparent | | textColor | #54595d | Text colour | | borderColor | #c8ccd1 | 1px border, or the frame characters in the ASCII style. null for none | | label | "alt" | ASCII only: tag in the top border, or null | | showSize | true | ASCII only: pixel size in the bottom border | | fallbackText | "Image unavailable" | Text used when the alt text is empty | | defaultWidth | 300 | Browser helpers only: width for images with no size | | defaultHeight | 150 | Browser helpers only: height for images with no size |

How text fitting works

  1. Wrap. Break lines between words at the preferred font size. A word wider than the box is split by grapheme.
  2. Shrink. If the block is too tall, reduce the font size one pixel at a time, down to minFontSize.
  3. Truncate. If it still doesn’t fit, keep the lines that fit and end the last one with …. If fewer than three characters would be visible, draw an empty frame instead.

Widths are estimated from Helvetica metrics, which Arial and Liberation Sans share, with a small safety margin. This is why the default font stack lists those fonts first. Other fonts work too, but the text may wrap slightly earlier or later than it would with those metrics.

Limitations

  • Text width is estimated, not measured by a browser. Scripts without a metrics table use a conservative average width.
  • Line breaking follows simple rules (spaces, then graphemes), not the full Unicode line-breaking algorithm.
  • replaceImagesWithPlaceholders uses a tolerant regex-based tag parser. It handles real-world <img> markup well but is not a full HTML parser.

Development

npm install
npm test            # node:test suites in test/
npm run typecheck   # type-check sources and tests
npm run build       # dist/ (CommonJS + types), the browser bundle, and site/altsvg.min.js

The landing page lives in site/. It is a static page that runs the real browser bundle, so after npm run build you can serve site/ from any static host, such as GitHub Pages, Netlify or Vercel.

src/
  index.ts      Public API
  svg.ts        generateAltSVG, generateAltASCII, layoutText, svgToDataURI
  ascii.ts      The ASCII style: grid layout and SVG rendering
  browser.ts    installAltFallback, replaceImage
  html.ts       replaceImagesWithPlaceholders
  wrapText.ts   Word wrapping, grapheme breaks, ellipsis
  measure.ts    Text width estimation
  escape.ts     XML escaping
  cli.ts        The altsvg command
test/           Tests
site/           Landing page

License

ISC