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

react-semantic-images

v0.2.2

Published

Drop <SemanticImage description="..."> into your React app. Run two commands. Get the best-matching image from your pool assigned to every slot — locally, no API keys.

Readme

react-semantic-images

Instead of manually wiring up image file paths in your components, you write a plain-English description of what the image should show. Two CLI commands take care of the rest — scanning your site, running a local AI model, and assigning the best-matching image from your pool to every slot.

No API keys. No cloud. Runs entirely on your machine.

<SemanticImage description="rainy city street at night" width={1200} height={600} />
npx collect-descriptions --url http://localhost:3000
npx match-images

How it works

  1. You use <SemanticImage description="..."> in your components wherever you want an image.
  2. collect-descriptions opens a headless browser, visits every page of your running site, and collects every description into public/semantic-manifest.json.
  3. You drop image files into public/semantic-pool/.
  4. match-images uses a local CLIP model to compare each description against every image and assigns the best match.

The manifest is a plain JSON file you commit to your repo. Re-run the two commands whenever you add new components or new images.


Install

npm install react-semantic-images

Install the headless browser used by collect-descriptions (one-time):

npm install --save-dev playwright
npx playwright install chromium

Setup

Create the image pool directory inside your public folder:

your-project/
└── public/
    └── semantic-pool/    ← put your images here (JPG, PNG, WebP, AVIF)

Step 1 — Use the component

import { SemanticImage } from "react-semantic-images";

export function Hero() {
  return (
    <SemanticImage
      description="dense foggy forest with rays of light breaking through trees"
      alt="Forest"
      width={1200}
      height={600}
    />
  );
}

The description can be anything — a literal string, a variable, a value from a loop or a ternary. It gets collected at runtime, so there are no restrictions on how it is computed.

Component props

| Prop | Type | Required | Default | Description | |------|------|----------|---------|-------------| | description | string | ✅ | — | Plain-English description of what the image should show. | | as | ReactElement | | "img" | Custom image component, e.g. Next.js next/image. | | manifest | object | | — | Pre-loaded manifest object. Eliminates the loading flash on SSR. | | manifestUrl | string | | "/semantic-manifest.json" | Custom path to the manifest file. | | fallbackSrc | string | | inline SVG | Image shown when no match exists yet. | | ...rest | | | | All standard <img> attributes are forwarded. |

With Next.js next/image

import Image from "next/image";
import { SemanticImage } from "react-semantic-images";

<SemanticImage
  as={Image}
  description="sleek black sports car parked on a wet road at dusk"
  width={800}
  height={500}
/>

Eliminate the loading flash (SSR)

By default the component fetches the manifest on the client after mount. To render the correct image immediately on the server, inject the manifest into your root layout:

// app/layout.tsx  (Next.js App Router)
import manifest from "../public/semantic-manifest.json";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <script
          dangerouslySetInnerHTML={{
            __html: `window.__SEMANTIC_MANIFEST__=${JSON.stringify(manifest)}`
          }}
        />
        {children}
      </body>
    </html>
  );
}

Or pass it directly to a single component via the manifest prop.


Step 2 — Collect descriptions

Start your dev server, then run:

npx collect-descriptions --url http://localhost:3000

This opens a headless browser, visits every page by following links, and writes all descriptions to public/semantic-manifest.json as unmatched entries:

{
  "rainy city street at night": null,
  "dense foggy forest with rays of light": null
}

| Flag | Default | Description | |------|---------|-------------| | --url | http://localhost:3000 | Root URL to start crawling from. | | --out | public/semantic-manifest.json | Where to write the manifest. |


Step 3 — Match images

Drop your images into public/semantic-pool/, then run:

npx match-images

The CLIP model embeds both descriptions and images, then assigns the best match for each:

{
  "rainy city street at night": {
    "image": "/semantic-pool/city.jpg",
    "score": 0.31
  }
}

| Flag | Default | Description | |------|---------|-------------| | --pool | public/semantic-pool | Directory of pool images. | | --out | public/semantic-manifest.json | Manifest file to read from and write to. | | --public | public | Public web root (used to build image URLs). | | --cache | node_modules/.cache/react-semantic-images/embeddings.json | Cache file for image embeddings. Images are only re-embedded when their content changes. |

The first run downloads the CLIP model weights (~60 MB, cached in node_modules/.cache). Subsequent runs are fast — only new or changed images are re-embedded.


Git integration

After match-images runs, it automatically updates your .gitignore — but only if the project is inside a git repository. If no .git is found, this step is skipped entirely.

The block it writes looks like this:

# --- react-semantic-images ---
# react-semantic-images: ignore unmatched pool images
public/semantic-pool/*
!public/semantic-pool/city.jpg
!public/semantic-pool/forest.jpg
# --- end react-semantic-images ---
  • Every image in the pool is ignored by default
  • Only matched images get a ! exception so git tracks them
  • Re-running match-images replaces the block in place — it never duplicates

Rematching

Add a new image to the pool and rematch everything unmatched: Just run npx match-images again. Already-matched entries are preserved.

Force a specific description to be rematched: Set its value to null in the manifest, then run npx match-images.

Start completely from scratch:

rm public/semantic-manifest.json
npx collect-descriptions --url http://localhost:3000
npx match-images

Troubleshooting

playwright: command not found

npm install --save-dev playwright
npx playwright install chromium

Could not load the "sharp" module using the darwin-arm64 runtime

sharp installed for the wrong platform. Run this inside the package directory:

cd node_modules/react-semantic-images
npm install --include=optional sharp

Images still show the placeholder after matching

The manifest is cached in the browser. Do a hard refresh:

  • Mac: Cmd + Shift + R
  • Windows / Linux: Ctrl + Shift + R

Low match scores (below 0.20)

The pool images don't visually match the descriptions well. Either rewrite the descriptions to better match what's in the pool, or add more relevant images to the pool.


License

MIT