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

@svelte-plugin/font

v0.0.6

Published

Zero-config SvelteKit/Vite font plugin: auto-detect, self-host, and metric-matched fallbacks that eliminate layout shift.

Readme

@svelte-plugin/font

Every Google Font at your fingertips — type a name, and it's loaded, self-hostable, and layout-shift-free. Automatically.

Just use a font in your code. The plugin finds it across ~1,900 Google Fonts (or your own local files), loads it, generates a metric-matched fallback that all but eliminates layout shift, wires up CSS variables, and injects everything — no @font-face, no <link>, no config.

SvelteKit Vite Tailwind TypeScript License


It's a Vite port of the ideas in @nuxt/fonts, built on unifont (font resolution) and fontaine (CLS-reducing metric fallbacks), tailored for SvelteKit and Tailwind v4.

Features

  • 🪄 Zero configfont() and you're done. It detects the fonts you actually use.
  • 🔤 Type-safe — autocomplete across 1,900+ Google fonts; a typo is a compile error.
  • 📉 No layout shift — auto-generates metric-matched @font-face fallbacks (size-adjust + ascent/descent/line-gap), so swapping in the web font barely moves a pixel.
  • 🎨 Tailwind v4 aware — sets --font-sans / --font-serif / --font-mono by category; font-['Inter'] arbitrary values get the fallback too.
  • 🏠 Local fonts — drop files anywhere in static/; family/weight/style are read from the binary, no naming convention.
  • ☁️ CDN or self-host — keep Google's CDN, or download the woff2 into your static/ folder with one option.
  • 🔡 Variable fonts — loaded as a single variable face across the full wght range, metrics included.
  • 🚫 No imports — the stylesheet is injected automatically; works with or without a +layout.svelte.
  • Dev HMR — edit your code, fonts update; no full reload.

Setup CLI

The fastest way to add the plugin to an existing SvelteKit/Vite project:

npx @svelte-plugin/font

It locates your vite.config.{ts,js,mjs}, installs the package with your detected package manager, asks whether to self-host or use the CDN, lets you search/pick Google fonts (optional — auto-detect handles the rest), and idempotently inserts font() into your plugins array (before tailwindcss() / sveltekit()). Re-running is safe — it never duplicates the import or the plugin.

Non-interactive (CI-friendly) flags — a flag overrides its prompt, and nothing prompts when not a TTY:

npx @svelte-plugin/font --fonts=Inter,Roboto --source=download

| Flag | Description | |---|---| | --fonts=A,B,C | Comma-separated families. Empty (--fonts=) relies on auto-detect. | | --source=cdn\|download | Use the Google CDN (default) or self-host woff2 into static/hosted_fonts. | | --pm=npm\|pnpm\|yarn\|bun | Package manager (default: detected from the lockfile). | | -y, --yes | Accept defaults and skip confirmations. | | --skip-install | Print the install command instead of running it. | | --cwd=PATH | Project directory (default: current directory). | | --dry-run | Print the edited config without writing or installing. | | -h, --help | Show usage. |

Installation

Or add it manually:

npm install -D @svelte-plugin/font

Requires SvelteKit + Vite. Tailwind v4 is optional (auto-detected). unifont and fontaine come along as dependencies.

Quick start

Add the plugin to vite.config.tsbefore tailwindcss() and sveltekit():

// vite.config.ts
import { sveltekit } from '@sveltejs/kit/vite';
import tailwindcss from '@tailwindcss/vite';
import font from '@svelte-plugin/font';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [
    font(),          // 👈 zero config: detects the fonts you use
    tailwindcss(),
    sveltekit(),
  ],
});

That's it. Use a font anywhere and it just works:

<h1 class="font-sans">Hello</h1>          <!-- Tailwind utility -->
<p style="font-family: 'Inter'">Hello</p>  <!-- raw declaration -->
<style>
  h1 { font-family: 'Playfair Display', serif; }
</style>

No @font-face, no imports, no <link> tags. The plugin finds Inter / Playfair Display, loads them, builds metric-matched fallbacks, and rewrites your declarations to use them.

Or declare fonts explicitly

You only need a fonts list to pin weights/subsets or set per-font options:

font({
  fonts: [
    'Inter',
    'JetBrains Mono',
    { family: 'Roboto', weights: [400, 700], subsets: ['latin', 'latin-ext'] },
  ],
});

Usage

In your styles

Reference fonts however you like — all three get the metric fallback:

| You write | Resolves to | |---|---| | class="font-sans" (Tailwind) | var(--font-sans) → web font + fallback chain | | class="font-['Inter']" (Tailwind arbitrary) | rewritten to var(--font-inter) | | font-family: 'Inter' (CSS) | rewritten to var(--font-inter), … | | font: 700 2rem Inter (shorthand) | rewritten to font: 700 2rem var(--font-inter) | | var(--font-inter) (manual) | web font + "Inter fallback" + system fonts |

Tailwind @theme

Define a theme font and it gets the fallback chain automatically:

/* app.css */
@import 'tailwindcss';

@theme {
  --font-serif: 'Playfair Display', serif;
}

font-serif now resolves to "Playfair Display", "Playfair Display fallback", "Times New Roman", ….

A custom CSS variable

font({ fonts: [{ family: 'Playfair Display', cssVariable: '--font-display' }] });
<h1 style="font-family: var(--font-display)">…</h1>

Self-host Google fonts

Don't want to depend on Google's CDN? Download the files into your project:

font({ fonts: ['Inter'], source: 'download' }); // → static/hosted_fonts/*.woff2

Local fonts

Drop font files anywhere under static/ and they're picked up automatically — no config, no naming convention, no required folder. Family, weight, style, and variable axes are read straight from each binary (via fontkit):

static/
  brand.woff2
  fonts/Cal Sans/cal-sans.woff2
  whatever/you/like/Geist[wght].woff2

Each file is served from its path under static/ (so static/brand.woff2/brand.woff2). The download-mode dir (static/hosted_fonts/) is skipped, so self-hosted Google fonts aren't re-scanned as local.

Local families get the same treatment as Google ones (CLS fallback, CSS variables, rewriting, auto-detection). The category is detected from the binary — monospace reliably (so a self-hosted IBM Plex Mono gets a mono fallback), serif when the font declares it, else sans. Override category/variable per family (keyed by the binary family name) when needed:

font({ local: { families: { 'Cal Sans': { category: 'serif', cssVariable: '--font-brand' } } } });

A local font wins over a Google family of the same name. woff2 is recommended, but any of woff / ttf / otf work; multiple formats of the same face merge into one @font-face.

Heads-up: every parseable font under static/ becomes a published webfont. If you keep fonts there for other reasons (OG-image rendering, PDF/canvas, asset samples), put them in a dedicated folder and skip it:

font({ local: { exclude: ['og', 'assets'] } }); // skips static/og/ and static/assets/

Configuration

font(options?: FontPluginOptions)

| Option | Type | Default | Description | |---|---|---|---| | fonts | FontEntry[] | [] | Fonts to load. A GoogleFontName string, or { family, weights?, styles?, subsets?, category?, cssVariable? }. Omit for pure auto-detect. | | provider | 'google' | 'google' | unifont provider. | | source | 'cdn' \| 'download' | 'cdn' | cdn keeps Google URLs; download self-hosts woff2 into static/hosted_fonts/. | | autoDetect | boolean | true | Scan src/** for font-family, Tailwind font-[…], and @theme --font-* usages and add any registered Google font. | | local | boolean \| { families, exclude } | true | Recursively scan static/ (excluding hosted_fonts/) for self-hosted fonts. false disables; { families } for per-family overrides; { exclude: ['dir'] } to skip top-level dirs. | | tailwind | boolean \| 'auto' | 'auto' | Detect Tailwind and set --font-sans/serif/mono by category. | | display | FontDisplay | 'swap' | font-display descriptor. Use 'optional' to avoid the visible swap entirely. | | rewrite | boolean | true | Rewrite font-family: Xvar(--font-x) so raw usages get the fallback. | | inject | boolean | true | Auto-inject the stylesheet (no manual import). Set false to import virtual:font.css yourself. | | staticDir | string | 'static' | Static assets directory. |

Variable fonts are supported out of the box — declared or detected variable fonts load as a single face spanning their full wght range.

FAQ

No. The plugin injects its stylesheet into SvelteKit's root component, so it lands in <head> on every route — with or without a +layout.svelte. Set inject: false and import 'virtual:font.css' yourself if you prefer.

That's FOUT (the glyphs repainting), not layout shift — the metric fallback keeps the box size stable so content doesn't move (CLS ≈ 0). If you want to avoid the visible swap entirely, use display: 'optional' (trade-off: the web font may not appear on a slow first load).

Yes. Tailwind just lets font-sans/serif/mono utilities resolve to the plugin's variables. Without it you still get --font-<family> variables and font-family rewriting.

Yes. The font shorthand (e.g. font: 700 2rem Inter, sans-serif) is both detected and rewritten — the family is taken from the part after the font-size. The only thing not handled is shorthands with no recognizable size (system keywords like font: menu), which have no family to load anyway.

How it works

The plugin runs entirely at build/dev time inside Vite. It resolves your fonts, asks fontaine to compute a metric-matched fallback @font-face for each, assembles one stylesheet (virtual:font.css) with the @font-face rules and :root variables, and injects it into SvelteKit's head — then rewrites your font-family usages to point at the variables.

flowchart LR
    A["your code<br/>+ static/ fonts"] --> B["detect + resolve<br/>unifont"]
    B --> C["metric fallback<br/>fontaine"]
    C --> D["virtual:font.css<br/>@font-face + :root vars"]
    D --> E["auto-injected<br/>into &lt;head&gt;"]

Acknowledgements

Stands on the shoulders of unifont, fontaine, capsize, fontkit, and @nuxt/fonts.

License

MIT