---
title: "Reports have bands, not pages — quario"
description: "Drawing a report is the easy part. The hard part is deciding what repeats, what totals, and where page two starts. Report writers answered that with bands, and HTML-to-PDF dropped the answer."
url: "https://getquario.com/blog/reports-have-bands-not-pages"
---

# Reports have bands, not pages

Drawing a report is the easy part. The hard part is deciding what repeats, what totals, and where page two starts. Report writers settled that decades ago with bands. HTML-to-PDF dropped the answer, and [every PDF generator](https://getquario.com/guides/javascript-pdf-generator.md) since has had to find it again.

## The problem every PDF generator solves again

Take a customer statement that runs to three pages. The column labels have to come back at the top of page two. Each customer needs a subtotal under their own rows. The grand total must not land alone at the top of an otherwise empty page three.

Not one of those is about drawing. They’re decisions about _when_ something prints. Render a statement to HTML and print it through a headless browser, and those decisions get scattered. The template computes subtotals in a loop. CSS hints at where breaks may fall. A page number lives in a header template with its own rules. Each one is a small fix, and each fix is local to one report.

## What a band is

A band is a strip of layout attached to one moment in the walk over the data. Each band answers a single question: when does this print?

- The **report header** prints once, before any row. The **report footer** prints once, after the last one, which is where totals for the whole document belong.
- A **group header** prints when a new value of the grouping key starts, and its **group footer** prints when that value ends. A subtotal per customer lives there.
- The **detail** band prints once per row.
- The **empty** band prints once when no rows remain, and takes the place of every group and detail row.
- The **page header** and **page footer** print once per page, on targets that have pages.

Crystal Reports and JasperReports both lay a report out in bands like these, under their own names. The idea has outlived both tools because it puts the decision where it belongs. You don’t say “draw the subtotal at y = 640.” You say “the subtotal closes the customer,” and the renderer works out the rest.

In quario that’s a group with an aggregate and a footer. Here’s the week group from the [timesheet template](https://getquario.com/templates/timesheet.md), cut down to the parts that matter:

```json title="timesheet.report.json"
{ "groups": [{
  "name": "week",
  "by": "=@.week",
  "aggregates": { "hours": "sum:=@.hours", "from": "first:=@.date", "to": "last:=@.date" },
  "header": [{ "type": "text", "value": "Week {{ week.key }}", "style": { "size": 11, "bold": true } }],
  "footer": [{ "type": "text", "value": "Week {{ week.key }} total: {{ week.hours }}", "style": { "size": 8 } }]
}] }
```

Nothing in it mentions a page. The definition says when the week opens, when it closes and what it adds up. The engine computes the aggregates from the data, so the total is never arithmetic someone typed into a template.

## Why it works

Once pagination and totals belong to the model, they stop being tricks played on one output. The same structure lands on three very different surfaces.

On a **PDF**, `@quario/pdf` paginates the document itself, with no headless browser. A group’s header travels with its first row, and repeats at the top of every page the group continues onto. The last data row keeps its total block, so a total can’t land alone. The definition declares none of this. A definition that described its own page breaks would stop working on Letter the moment it worked on A4.

On a **Word document**, `@quario/docx` is a flow target. It writes real Word tables and lets Word turn the pages, with the table’s header row repeating after every break. Each group header opens with a heading, so the navigation pane mirrors the report’s own grouping.

On a **workbook**, `@quario/xlsx` flattens the walk onto a grid of native cells. Amounts stay numbers a reader can sort, filter, and sum. A group `break` that turns a page in the PDF starts a new worksheet here. The page bands become the sheet’s print header and footer, since a worksheet still has paper someone may print it on.

Three outputs, one definition, and no target-specific branch in it. That’s only possible because the definition describes the report’s structure rather than one surface’s geometry.

## What quario changes from the classics

Bands aren’t new. Two things about how quario carries them are.

**The definition is JSON you keep in the repository.** Crystal Reports stores a report as a binary `.rpt` file. JasperReports uses an XML file that a visual designer usually writes for you. Either way the layout tends to live in a tool rather than in review. A quario definition is a plain file you diff, version and [read in a pull request](https://getquario.com/blog/reports-in-code-review.md), next to the code that feeds it.

**Expressions parse into closures.** JasperReports compiles a report’s expressions into Java classes before it fills the report. quario has no `eval`, no `new Function` and no code generation anywhere in the render path. An expression like `{{ round(region.subtotal / $.revenue * 100, 1) }}` becomes a tree of closures when the definition compiles. It’s never source text that something runs.

That’s what makes a definition safe to accept from somewhere you don’t control, including an AI agent. The guarantee is narrow, and it should stay narrow. Nothing an agent writes into a definition can execute. Whether its numbers are the right ones is still a question for whoever reviews it.

## Where bands are the wrong tool

Bands describe documents that run from top to bottom over rows of data. Plenty of layouts aren’t that.

A **dashboard** is a two-dimensional arrangement that someone filters and refreshes. Its tiles have no “when does this print” answer, because nothing prints. A chart library and a grid of components serve it better.

A **free-form layout**, like a brochure, a poster or a landing page, places elements by eye. Text wraps around a photo, and a heading overlaps an image. Nothing repeats per row, and there are no groups to total. A design tool or plain HTML and CSS is the honest choice there.

The test is simple. If the document has rows, and the rows decide what repeats and what adds up, it has bands whether you name them or not. Naming them is what lets one definition become a PDF, a Word document and a workbook without anyone redrawing it.

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://getquario.com/"},{"@type":"ListItem","position":2,"name":"Blog","item":"https://getquario.com/blog"},{"@type":"ListItem","position":3,"name":"Bands, not pages","item":"https://getquario.com/blog/reports-have-bands-not-pages"}]}
```
