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

@quario/html

v0.11.1

Published

Tiny, escape-by-default HTML render target for quario. Semantic tables and a stable class contract.

Readme

@quario/html

The HTML render target for quario. Renders a report definition to an HTML fragment with semantic tables, a stable class contract, and every interpolated value escaped.

Contents

Install

npm install quario @quario/html

The engine is a peer dependency, installed beside the target. ESM-only, Node 22+, and browser-ready through any standards-based ESM bundler.

Quick start

import { quario } from "quario";
import { html } from "@quario/html";

const schema = {
  data: "$.orders[*]",
  aggregates: { total: "sum:[email protected] * @.qty" },
  groups: [
    {
      name: "region",
      by: "[email protected]",
      aggregates: { subtotal: "sum:[email protected] * @.qty" },
      header: [{ type: "text", value: "{{ region.key }}", style: { bold: true } }],
      footer: [{ type: "text", value: "Subtotal {{ money(region.subtotal) }}" }],
    },
  ],
  detail: {
    columns: [
      { header: "Product", value: "{{ @.product }}" },
      { header: "Amount", value: "{{ money(@.price * @.qty) }}", style: { align: "right" } },
    ],
  },
  footer: [{ type: "text", value: "Grand total {{ money($.total) }}" }],
};

const data = { orders: [{ region: "North", product: "Desk", price: 250, qty: 2 }] };

const report = quario().report(schema, { money: (n) => "$" + n.toFixed(2) });
const page = await report.render(html(), data);
<div class="q-report">
  <div class="q-group" data-group="region">
    <div class="q-item q-group-header" style="font-weight:bold">North</div>
    <table class="q-table">
      <colgroup><col><col></colgroup>
      <thead><tr><th>Product</th><th>Amount</th></tr></thead>
      <tbody><tr><td>Desk</td><td style="text-align:right">$500.00</td></tr></tbody>
    </table>
    <div class="q-item q-group-footer">Subtotal $500.00</div>
  </div>
  <div class="q-item q-report-footer">Grand total $500.00</div>
</div>

The output is a fragment: no <html>, no <head>, no stylesheet of its own — the one <style> it writes is an @page { margin } rule when the schema declares page.margin. Wrap it in your own page shell and stylesheet.

API

html(options?)

The target factory takes this target's host options, validates them at the call, and returns the target you pass to render. The factory refuses an option it does not know. It throws a TypeError at the call for an unknown key. It throws one also for a key with a value of the wrong type. report() compiles once and report.render(html(), data) resolves the fragment. Compile at startup and render per request. Definition problems throw at report(), at compile time.

Two options. { paths: true } stamps data-q-path="<schema path>" on each element whose event carries one (items, images, group containers, the table and its cells), mapping rendered output back to the definition behind it. Off by default. { fonts } maps a declared family name to the CSS font-family value it should emit — a custom property, a font stack, a quoted name — consulted before the three built-in generics. Names match case-insensitively, and the factory rejects a value holding ; or } when it constructs the target:

html({ fonts: { "Instrument Sans": "var(--font-instrument-sans)", mono: "var(--font-ibm-plex-mono)" } });
const report = quario().report(schema, funcs);
const page = await report.render(html(), data);

report.names; // free variable names the expressions read
report.functions; // registry function names the definition calls
report.paths; // the data query's frozen dependency topology
report.stream(data); // the raw event generator, if you want events instead

Engine-level options (query budgets, the license key) live on the instance (quario({ query, license })). See the engine README.

Rendering is asynchronous and returns the loop between batches, so a large report never blocks the host. Compilation stays synchronous. Render-time failures reject with the same located errors.

Output contract

These classes are the contract host CSS targets. They are stable, and changing them is a breaking change.

| Emits | For | | -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | <div class="q-report"> | The fragment's root, holding every band | | <a href> | A styled run that declares href, inside that run's own span. The URL is escaped like every other interpolated value, and the engine admitted it against the host's scheme allowlist before this target saw it | | <div class="q-item q-<role>"> | Every item. Roles: q-report-header, q-empty, q-group-header, q-detail, q-group-footer, q-report-footer | | <div class="q-group" data-group="name"> | Each group instance, wrapping its header, nested content, and footer. Adds q-break where the group's break turns a page before the instance, and q-break-after where one turns after it | | <table class="q-table"> | Each table, with a real colgroup, thead, tbody, and a tfoot around the total rows the walk emits. No tfoot when total is absent or every row of it is hidden. A spanning cell carries colspan | | <div class="q-item q-image q-<role>"> | Each image item, holding one <img>. Its src is a base64 data: URI of the event's bytes, and its width/height are the picture's natural size. fit sets max-width:100% or width:100% on it, paired with height:auto. The target escapes the rendered alt | | <div class="q-columns" style="column-count:<n>"> | The content between a node's own bands when it declares columns. On the root, that is the body between report header and footer. On a group, it is inside that instance's q-group | | <div class="q-split q-<role>"> | Each split, carrying inline display:flex. Its slots are <div class="q-slot"> carrying inline display:grid and their share (a slot's valign as align-content), each holding the slot item's ordinary container |

A column width becomes an inline width:<n>% on its <col>. A row's style lands on its <tr>, except the box, which the engine has already resolved onto the cells (each <td> carries box-sizing:border-box). The classes above belong to this target. The schema never speaks in CSS. Displaying a fragment that contains images under a Content Security Policy needs img-src data: (see Trust and CSP).

Style declarations map to inline CSS (bold → font-weight:bold or font-weight:normal, size → font-size:<n>pt, family: "mono" → font-family:monospace, …). Occupy-a-line and newline-as-break are not inline: they hang off .q-item and .q-table th, td in @quario/html/style.css, the same way .q-break honors a leading page break. The class is the hook. The rule is a reference default a host overrides on source order. A fragment without that sheet still carries the classes and the text, including newlines. It does not occupy or break until some stylesheet says so. This target supplies no defaults in the markup: no weight or size per band role, no leading, no padding, no spacing between bands, no borders. An inline style attribute would beat yours in the cascade and force !important on you. The defaults and the honor rules ship as an ordinary stylesheet instead. The PDF and XLSX targets carry theirs built in, because their consumers have no stylesheet. An empty row set still emits the table, its header, and an empty <tbody>. A hidden table cell keeps its <td>, empty.

This target paginates nothing: it ignores schema page bands (page furniture belongs to your print CSS) and maps each edge a group's break turns to its own class. Page columns are laid out: a node declaring columns wraps the content between its own bands in the q-columns container above, and the browser flows it.

The full contract is The HTML target, with each declaration's fate in the support matrix.

Unlicensed marking

An unlicensed render opens the fragment with <div class="q-unlicensed"> holding the wording from report-start.marking as escaped text. A licensed render emits no badge. The element and its position are normative. Visual treatment belongs to the host. The shipped stylesheet leaves the badge unstyled, because a watermark would escape the fragment's box onto the host page.

Escaping

This target escapes every interpolated value. {{{ }}} is a definition error, so no schema syntax can exempt a data value from escaping. It escapes every generated attribute value too — inline styles, data- attributes, src, alt and colspan. Class names are constants the target owns and never carry data.

Literal template text passes through verbatim as author-controlled markup. A definition is trusted configuration. Its data is not, and data can never reach the document unescaped.

Printing to PDF

For a browserless, deterministic document, use @quario/pdf, same schema, same API, a Uint8Array out. Use the HTML print route below when you want full CSS typography and already have a browser or Paged-CSS engine in your pipeline.

Host CSS targets these classes:

  1. Render the fragment and wrap it in a document with two stylesheets inlined, in order: @quario/html/style.css below, then your own.
  2. The shipped sheet already puts band behavior on the emitted classes: .q-item { min-height: 1lh; white-space: pre-line } occupies a line and breaks on newlines, .q-group { break-inside: avoid } keeps a group header with its rows, .q-table tr { break-inside: avoid } keeps a row whole, .q-break { break-before: page } and .q-break-after { break-after: page } honor the schema's own break hints, and thead repeats per printed page. Yours adds the page geometry in @page, which only you can decide.
  3. Print with a headless browser (page.pdf() in Playwright or Puppeteer, page numbers via the footer template) or a Paged-CSS engine (WeasyPrint or Prince, with page numbers via @page margin boxes).

The reference stylesheet

The package ships the default look as a real stylesheet, @quario/html/style.css: the band-role weights and sizes, the table's structure (full width, collapsed borders, cell padding, an unstyled cell pinned to the top of its row), occupy and newline-as-break on .q-item and table cells, and the four pagination rules above. It carries no look beyond that — no rule under the header row, no bold totals, no stroke above the report footer — because no other target has one either. Add them to your own sheet if you want them. Link it, import it through a bundler, or read and inline it. Plain Node cannot import a .css file, so on the server:

import { readFileSync } from "node:fs";

const css = readFileSync(new URL(import.meta.resolve("@quario/html/style.css")), "utf8");
const page = `<!doctype html><html><head><style>${css}</style></head><body>${fragment}</body></html>`;

Its selectors are contract. Its rules are not. It is a reference default meant to be overridden. Every rule is ordinary specificity, so your own stylesheet loaded after it wins without !important anywhere. Link nothing at all and you get unstyled markup.

Two things it leaves to you: @page geometry and body type, which only a host can decide, and any watermark treatment of .q-unlicensed, left as plain text here because position: fixed would paint over your whole page. example/print.css in the repository is a host's half that does both, and example/print.js is the complete pipeline.

Documentation

The quario documentation is the reference. The report schema is the normative specification of what a report may declare, and @quario/html is this package's own API.

License

Commercial software with readable source. Evaluation is free, unlimited, and watermarked. Per-developer licenses at getquario.com. See the bundled LICENSE.

Pass your license key once, on the instance. quario verifies it offline:

const q = quario({ license: "quario_..." });
await q.license; // { licensed: true, licensee: "Acme BV", id: "1-ACME" }