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

zerolith

v1.5.0

Published

A standalone frontend framework: fine-grained signals and HTML templates that run from an import map or a bundle with no second-class path

Readme

zerolith

A standalone frontend framework: fine-grained signals, HTML templates that need no compiler, and a runtime that loads from an import map or a bundle with no second-class path. It works with any backend over plain HTTP.

Install with npm install zerolith. The API is frozen at 1.0: a breaking change comes only in a major, after a deprecation notice, and the support policy says how long a line gets fixes. The guides, the API reference, and a page for every diagnostic code are on the docs site, and the direction is in the roadmap.

A first component

import { computed, html, render, signal } from "zerolith";

function Counter({ start = 0 }: { start?: number })
{
    const count = signal(start);
    const doubled = computed(() => count.get() * 2);
    return html`
        <p>Count: ${count}, doubled: ${doubled}</p>
        <button @click=${() => count.set(count.get() + 1)}>Add one</button>`;
}

render(Counter({ start: 1 }), "#app");

render mounts a view into an element, named by a CSS selector or passed as the element itself, and returns a root whose dispose() takes it down again. Given a function that returns the view, as render(() => App(), "#app"), it calls the function inside the root's owner, so the effects and onDispose cleanups a component registers end with the root; an application's entry mounts its layout that way.

The one rule to learn first

Bind the signal, not its value.

html`<p>${count}</p>`                              // live: updates when count changes
html`<p>${computed(() => item.get().name)}</p>`    // live: derived value
html`<p>${count.get()}</p>`                        // frozen: read once at creation, never updates

Both forms type-check, and the runtime cannot tell a frozen read from a deliberate one-time value, so it does not warn; the Vite plugin and zerolith doctor read the source and report template_frozen_read. Call .get() only inside computed, effect, resource.params, and event handlers.

Styles

A component's styles are scoped to it by the browser's @scope rule, with no shadow DOM, so global styles and design tokens still reach in. Write them in a css literal beside the markup, or keep a longer stylesheet in a file of its own and import it with ?scoped:

import { css, html } from "zerolith";
import card from "./card.css?scoped";

const badge = css`:scope { padding: 4px 8px; border: 2px solid; }`;

function Card({ title }: { title: string })
{
    return html`
        <article class=${card}>
            <h2>${title}</h2>
            <span class=${badge}>New</span>
        </article>`;
}

Both give the same value, bound to a root element's class. A ?scoped import needs zerolith dev and zerolith build from @zerolith/cli, or the Vite plugin, and /// <reference types="zerolith/imports" /> in the project's env.d.ts types it.

Principles

  • One reactivity model: signal, computed, effect, batch, resource, with the TC39 Signals vocabulary (.get(), .set()).
  • One authoring form: a plain TypeScript function returning an html tagged template. No compiler is required; the compiler is an optimizer.
  • No virtual DOM: templates are instantiated once and bound part by part.
  • Zero runtime dependencies and no install scripts, enforced by tests.
  • Works under a strict script-src 'self' CSP and with Trusted Types enforced.

Documentation

  • The introduction starts a project, and the signals and templates guides explain the core, every example run as a test.
  • The reference lists every export, generated from the source.
  • New to zerolith? The tutorial builds an app step by step, and the playground runs code in the browser.

License

Apache-2.0. See LICENSE.