Tests Custom Reports

Print & PDF Layout

Wrap each A4 page of a custom report in a div.ti-page of 210 × 297 mm; Print then puts every .ti-page on exactly one sheet, and Export as PDF follows your @media print rules, so each page's content must really fit.

Published 2026/10/15

On screen a custom report has no page size — it can be any HTML. For printing and PDF export, split it into A4 pages by wrapping each page in a div.ti-page. This class is the contract between your template and the app.

OutputWhat happens to .ti-page
Report view (screen)Nothing special — your own CSS gives each page its sheet look (white background, shadow, gap).
PrintThe app prints the report in a clean frame and puts each .ti-page on exactly one A4 sheet. Content taller than a sheet is cut off.
Export as PDFThe report is rendered on the server in print mode; your template's own @media print rules control page breaks.

A template without .ti-page still prints — it simply flows freely across as many sheets as it needs.

Recommended page CSS

.ti-page {
  width: 210mm;            /* A4 width  ≈ 794 px */
  min-height: 297mm;       /* A4 height ≈ 1123 px */
  padding: 16mm;
  box-sizing: border-box;
  background: #fff;
}
@media print {
  .ti-page { min-height: unset; box-shadow: none; margin: 0; }   /* no trailing blank page */
  .ti-page:not(:last-child) { break-after: page; page-break-after: always; }
}
  • Breaking after every page except the last avoids an empty final sheet.
  • Unsetting min-height in print avoids an extra blank sheet when content ends exactly at a page boundary.
  • If the last page pins a footer or logo to the bottom (for example with margin-top: auto in a flex column), keep min-height: 297mm on .ti-page:last-child so it has room to sit at the bottom.

Make sure each page really fits

min-height is a minimum, not a limit. A page whose content is taller than A4 looks perfectly normal on screen — the box just grows — but in the PDF it spills onto a second sheet (often splitting a table), and in Print it is cut off.

  • Test with the longest content your report can produce, not whichever session is at hand: the longest candidate name, and the longest band or level description. If several such texts can appear on the same page, test the longest combination.
  • If your texts come from a lookup object in the function, the worst case is easy to find: take the longest entry.
  • Leave spare room. Don't trim spacing until it fits exactly; real data will push it over.
  • Check with Export as PDF, not by eye: one PDF page per .ti-page means it fits.

Footers and page numbers

CSS @page margin boxes and running footers are not supported. Put the footer inside each page as ordinary HTML:

<style>
  .ti-page { position: relative; padding-bottom: 24mm; }
  .ti-page-footer {
    position: absolute; left: 16mm; right: 16mm; bottom: 10mm;
    display: flex; justify-content: space-between;
    font-size: 10px; color: #888; border-top: 1px solid #e0e0e0; padding-top: 6px;
  }
</style>

<div class="ti-page">
  … page content …
  <div class="ti-page-footer"><span>{{ candidate.name }}</span><span>Page 1 of 2</span></div>
</div>

Colours, fonts and images

  • Add -webkit-print-color-adjust: exact; print-color-adjust: exact; to elements whose background colours must print, such as score bars and chips.
  • Load fonts inside the template (@import in its <style>). A font that isn't loaded falls back to a different one, which changes text width — and can push a page over A4.
  • Use absolute https:// image URLs so images load in the server-side PDF renderer.

Embedded in the Standard Template

When a step extends the Standard Template with your custom report (top or bottom), the custom part is placed next to the standard report, not inside it, and separated from it by a page break in print. A template that uses .ti-page paginates exactly as it does on its own — you don't need to change anything.