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

@bison-lab/fonts

v3.27.1

Published

A curated, self-hosted font catalogue for Bison Lab sites: serves only the families a theme names

Readme

@bison-lab/fonts

A curated, self-hosted font catalogue for Bison Lab sites. A theme names a family by id; the site serves that family's files itself and declares only the families it names. No request goes to Google Fonts, and a family the theme does not name is never downloaded by a visitor: it costs disk in node_modules and nothing at load time.

Every family is variable weight, latin subset, licensed under the SIL Open Font License, with the licence beside its files under files/<id>/.

The catalogue

| Id | Family | Category | Weights | | --- | --- | --- | --- | | atkinson-hyperlegible-next | Atkinson Hyperlegible Next | sans | 200–800 | | inter | Inter | sans | 100–900 | | montserrat | Montserrat | sans | 100–900 | | work-sans | Work Sans | sans | 100–900 | | dm-sans | DM Sans | sans | 100–1000 | | manrope | Manrope | sans | 200–800 | | nunito | Nunito | sans | 200–1000 | | source-serif-4 | Source Serif 4 | serif | 200–900 | | lora | Lora | serif | 400–700 | | merriweather | Merriweather | serif | 300–900 | | fredoka | Fredoka | display | 300–700 | | space-grotesk | Space Grotesk | display | 300–700 | | playfair-display | Playfair Display | display | 400–900 | | fraunces | Fraunces | display | 100–900 | | jetbrains-mono | JetBrains Mono | mono | 100–800 | | fira-code | Fira Code | mono | 300–700 |

Each entry carries a one-line hint for a picker, and the CLI and the Theme admin show it beside the name.

Wiring a Next site

Three pieces: the route that serves the files, the config line that keeps the files reachable, and the theme that names the families.

1. The route. Answers /fonts/<id>/<file> from the package's own files/ directory with Cache-Control: public, max-age=31536000, immutable. Only a file the catalogue lists is served; a traversal or an unknown file is 404.

// app/fonts/[...path]/route.ts
import { serveFont } from "@bison-lab/fonts/server";

export const GET = serveFont;
export const HEAD = serveFont;

2. Keep the package external. serveFont finds the files relative to its own module, so the package must not be bundled into the route:

// next.config.ts
const nextConfig = {
  serverExternalPackages: ["@bison-lab/fonts"],
};

3. Name the families. @bison-lab/create-theme writes them into bison.config.json as catalogue ids and puts the @font-face rules at the top of bison-theme.css, so a CLI site is done here. A site that builds its theme at request time declares them itself:

import { fontFaceCss, fontPreloads } from "@bison-lab/fonts";
import { buildThemeCss, themeFontIds } from "@bison-lab/tokens";

const ids = themeFontIds(config);            // ["atkinson-hyperlegible-next", "montserrat"]
const preloads = fontPreloads(ids, "/fonts");

<head>
  {preloads.map((p) => (
    <link key={p.href} rel="preload" as="font" type={p.type} href={p.href} crossOrigin="anonymous" />
  ))}
  <style>{fontFaceCss(ids, "/fonts")}</style>
  <style>{buildThemeCss(config)}</style>
</head>

buildThemeCss(config, { fontsBaseUrl: "/fonts" }) does the @font-face part for you when one stylesheet is simpler.

BisonProvider in @bison-lab/ui takes the same ids (bodyFont="inter") and emits the rules and preloads itself under fontsBaseUrl.

Serving from a different path is one option in each place: serveFont(request, { baseUrl: "/static/type" }) and the same base URL to fontFaceCss / fontPreloads.

API

@bison-lab/fonts (no Node import; safe in the browser):

| Export | What it is | | --- | --- | | catalog, fontIds | Every FontEntry, in the order pickers list them | | isFontId(value) | Whether a value is a catalogue id | | getFont(id) | The entry; throws on an unknown id | | findFont(id), findFontByFamily(name) | The entry or undefined; the second is how a legacy family name finds its id | | fontStack(id) | The font-family value: "Inter", ui-sans-serif, system-ui, sans-serif | | fontFaceCss(ids, baseUrl) | @font-face rules for exactly those families, font-display: swap | | fontPreloads(ids, baseUrl) | { href, type } per family, the normal-style file a first paint needs | | fontFilePaths | Every path serveFont may answer |

@bison-lab/fonts/server (Node):

| Export | What it is | | --- | --- | | serveFont(request, { baseUrl }) | The Fetch handler: GET and HEAD, 404 outside the catalogue, 405 otherwise | | FONT_FILES_DIR | The directory the files live in |

Adding a family

  1. Add the @fontsource-variable/<id> package as a dev dependency here. Fontsource's id is the catalogue id.
  2. Add the entry to src/catalog.ts: family, category, the wght axis from the package's metadata.json, whether it ships an italic, a fallback and a hint.
  3. pnpm --filter @bison-lab/fonts sync copies the latin woff2 files and the licence into files/<id>/. Commit them.
  4. pnpm --filter @bison-lab/fonts test holds the entry to the package's metadata and checks every file is on disk.

A family that is not on Fontsource, or not OFL, is a different conversation: the catalogue is curated on purpose.