---
title: "The HTML target — quario"
description: "Rendering a quario definition to an HTML fragment, the class contract, the reference stylesheet and the font mapping."
url: "https://getquario.com/docs/diving-deeper/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](https://getquario.com/docs.md#painters-flow-targets-and-a-fragment)
describes 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` maps to a class
instead of vanishing, so your print stylesheet can act on it. `q-break` marks
an instance whose leading edge turns, and `q-break-after` one whose trailing
edge does.

**This target escapes everything a definition or its data contributes.**
Escaping happens here, at the markup edge, because the event stream carries
unescaped data. The target escapes literal template text and header cells as
well as interpolated values. A definition’s own `<h2>` reaches the reader as the
characters it’s made of.

**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

```ts title="render.ts"
import { html } from '@quario/html'

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

Rendering is asynchronous and returns control to the event loop 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

```css title="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.

```css title="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; }
```

The report header’s own rule is a factor rather than a point size:
`.q-item.q-report-header` is `font-size: 1.4em`. An unstyled fragment renders
its headline at 1.4× the host page’s body type rather than at a fixed 14pt. A
host whose body isn’t 10pt sees that move. A host rule on the class still
wins, the way it always did.

### Linking a run

The target wraps a run carrying an `href` in an `<a>` inside its own span, so
the style the run declared paints the link. It’s a real anchor rather than a
styled span, so a keyboard reaches it the way it reaches any link. The target
escapes the URL at the one markup edge like every other interpolated value. The
engine admitted it before it crossed, so this target decides nothing about it.

A cell’s own `href` links the cell’s text. The target asks the style a run
wears, which is the cell’s wherever the run declares none. A run declaring its
own replaces the cell’s rather than nesting inside it.

### Mapping a font family

```ts title="render.ts"
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

```ts title="render.ts"
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](https://getquario.com/docs/diving-deeper/pdf-target.md) instead, from the same
definition.

A document that declares its paper gets one `<style>` in the fragment. It
holds an `@page` rule with that size and each margin, so the print dialog opens
on the right sheet. It’s the only `<style>` this target writes.

### 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 (`data:image/svg+xml` for an SVG), which needs `img-src data:`. Neither is a
hole, and neither affects the no-string-to-code guarantee, which covers script
execution.
[Trust and CSP](https://getquario.com/docs/diving-deeper/security.md) has the whole picture.

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://getquario.com/"},{"@type":"ListItem","position":2,"name":"Docs","item":"https://getquario.com/docs"},{"@type":"ListItem","position":3,"name":"The HTML target","item":"https://getquario.com/docs/diving-deeper/html-target"}]}
```
