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

@itsy/html

v1.1.0

Published

tiny isomorphic HTML renderer — tagged templates in, escaped strings out; context-aware escaping, URL scheme guard, one attribute helper, no hydration

Readme

@itsy/html

Tagged-template HTML renderer, zero dependencies, just enough features.

Documentation · Playground · Error codes

pnpm add @itsy/html
import { html, attrs } from '@itsy/html';

const Link = ({ href, label, active }: LinkData) =>
  html`<a href="${href}" class="link ${active && 'is-active'}">${label}</a>`;

const Menu = ({ groups }: MenuData) => html`
  <nav ${attrs({ 'aria-label': 'Main', hidden: groups.length === 0 })}>
    ${groups.map(
      (g) => html`
        <h2>${g.title}</h2>
        <ul>${g.links.map((l) => html`<li>${Link(l)}</li>`)}</ul>`,
    )}
  </nav>`;

res.send(Menu(data).markup); // server
el.innerHTML = Menu(data).markup; // client: same function, same string, no hydration

Why

Most template renderers escape every value the same way, wherever it lands, and none of them look at URLs. One escape cannot be right everywhere: text needs entities, an unquoted attribute needs quotes before anything else helps, and javascript: in an href contains nothing worth escaping and still runs.

This one reads the static markup around each value, works out which context the value landed in, and applies what that context needs. Where no escaping would make a context safe — inside a tag, inside <script>, in any on* attribute — it throws instead of guessing. The result is an Html wrapper, so a template nested in another is not escaped twice, and the same function renders on a server and in a browser.

Features

  • Escaping by context — text, a quoted attribute, a URL, the inside of a tag, a script body and a comment are six different jobs, and the renderer picks per value.
  • A URL scheme guard — anything outside http https mailto tel data blob renders as about:blank#blocked, however it is spelled.
  • A markup check in development — unclosed tags, stray end tags and nesting a browser would rewrite, reported the first time a template runs.
  • attrs() and cx() — booleans, class lists, style objects, aria and data groups, with the URL guard applied to attributes that hold URLs.
  • frame() — a whole document, with a head that merges so a page can override one entry of a shared layout without reordering the rest.
  • check() — the same checks over a rendered page, plus duplicate ids, id references pointing nowhere, and any URL the guard blocked. 28 bytes in production.
  • Twenty-five accessibility rules, running inside check() by default, in the same pass and the same list: the unlabelled icon button, the misspelled aria- attribute, the image with no alt, the role that does not exist. { a11y: { without: [...] } } turns any of them off, with the names type-checked. They cost nothing — the production build compiles them away with the rest of check().
  • Custom rules — check(page, { rules }) runs a project's rules in that same pass, so house style and design-system constraints report like everything else, and ship like everything else: not at all.

Before starting

[!WARNING] Html is an object, not a primitive. typeof reports 'object', an empty one is truthy, and any framework that serializes objects will JSON-encode it rather than send the markup. Use view.markup at that boundary.

[!WARNING] Prettier and oxfmt reformat the HTML inside html templates by default. <br> becomes <br />, which the markup check then reports. Set "embeddedLanguageFormatting": "off".

Size

Minified and brotli-compressed, with the listed imports and nothing else.

| import | | production | development | | ------------------- | -------------------------- | ----------- | ----------- | | @itsy/html | html, attrs, raw | 1.79 kB | 5.79 kB | | @itsy/html/attrs | attrs, cx | 929 B | — | | @itsy/html/check | check | 28 B | 8.91 kB | | @itsy/html/frame | frame, head, element | 2.6 kB | 6.64 kB | | @itsy/html/util | all seven helpers | 1.21 kB | — | | @itsy/html/create | createHtml | 1.84 kB | — |

The import map has every entry point and what each one exports.

For coding agents

AGENTS.md is a task-oriented map of the library, and it ships inside the package. The site also publishes llms.txt and llms-full.txt.

License

MIT