Escape
HomeDocsThe HTML target

The HTML target

Intent

@quario/html serialises the event stream into a fragment you drop into your own page. It’s not a document. There is no <html>, no <head>, and no presentation beyond what a definition declared. The fragment goes into a template, an email body or a server-rendered response.

Design decisions

This target is a fragment, not a painter, and not a flow target. It has no pages of its own to break or to hand to a reader’s application, which is why page bands go unread below. Painters, flow targets, and a fragment sets out the family.

The fragment carries no pagination, and leaves page bands unread. Page furniture belongs to the host’s print CSS. A group’s break: "page" maps to a class instead of vanishing, so your print stylesheet can act on it.

This target escapes every interpolated value, and leaves literal text alone. Escaping happens here, at the markup edge, because the event stream carries unescaped data. Literal template text is author-controlled markup, which is the same split every markup consumer has to make.

It escapes generated attributes too. Data attributes, inline styles, src, alt and colspan all carry data. Class names are constants this target owns.

The q-* selectors are the contract, and the stylesheet carries no look of its own. It holds the band-role defaults, the table’s structure, and the honor and pagination rules. They’re ordinary rules a later host sheet overrides on source order. Since 0.7.0 it draws no hairline under a table’s header row, and no rule or bold weight on its total rows. It draws no rule above the report footer either, because no other target ever drew them. A report that declares no borders now reads the same on screen as in the PDF and the worksheet.

Styled runs render as spans. One <span style="…"> per styled run, carrying its whole resolved inline style. Unstyled runs stay bare, so a cell without runs emits the markup it always did.

A fragment without the sheet still carries the classes and the text. It doesn’t occupy, break or paginate until some stylesheet says so.

The report default rides the root as an inline style. That’s the one place the rest of the document can inherit it from.

The unlicensed marking sits outside the report root. Inside it, an author’s declared size would reach it. It’s the fragment’s first element and the root’s sibling, holding only the escaped wording.

API walkthrough

Rendering

render.js
import { html } from '@quario/html'

const fragment = await report.render(html(), data)

Rendering is asynchronous and hands the event loop back between batches, so a large report doesn’t block the host. Compilation stays synchronous, and render errors reject with their location attached.

The same definition, data, and options produce an identical fragment. The target stamps nothing, and an unformatted Date writes as ISO 8601 UTC rather than in the host's zone.

Styling the output

page.css
@import '@quario/html/style.css';

Two rules in that sheet are worth knowing, because a fragment without them behaves differently. .q-item carries min-height: 1lh and white-space: pre-line, so a visible text item occupies a line and a literal newline is a break. Table cells carry pre-line too, and empty cells have no min-height, so they don’t occupy.

The classes are yours to restyle. Override on source order, the way you would any vendor sheet.

A host that wants the look the sheet carried before 0.7.0 adds three rules after the shipped one.

page.css
.q-table thead th { border-bottom: 0.5pt solid #000; }
.q-table tfoot td { border-top: 0.5pt solid #000; font-weight: bold; }
.q-item.q-report-footer { border-top: 1pt solid #000; padding-top: 4pt; }

Mapping a font family

render.js
import { html } from '@quario/html'

const target = html({ fonts: { Inter: 'var(--font-inter)' } })

A declared family name maps to a CSS font-family value. Names match case-insensitively, and a mapping may cover the generics like any other name. A value containing ; or } throws from html() rather than reaching the page.

This mapping resolves names a report declared, and never the face of text that declared nothing. That baseline lives in the reference stylesheet, which is where a host moves it.

Mapping output back to the definition

render.js
import { html } from '@quario/html'

const target = html({ paths: true })

Each element whose event carries a schema path gets data-q-path. It’s off by default, and it’s the selection seam the editor builds on.

Printing

A fragment plus a print stylesheet is a legitimate route to paper, and it’s the one the browser already knows how to drive. The trade is that pagination becomes the browser’s, so page bands, repeated table headers, and deterministic bytes aren’t part of the bargain. Where those matter, render through the PDF target instead, from the same definition.

A note on Content Security Policy

Conditional formatting emits inline style attributes, so a restrictive style-src has to account for them. An image item embeds bytes as a data: URI, which needs img-src data:. Neither is a hole, and neither is the no-string-to-code guarantee, which covers script execution. Trust and CSP has the whole picture.

A fragment, and the classes to style it.

npm install quario
Getting started
© 2026 quario · KvK 61815977