Skip to content

Supported CSS

Style templates with the CSS you would write for Chrome’s print view; the renderer lays the HTML out as Chrome prints it. This page lists what works and the rules specific to typefold. For a walk-through of paper, header, footer, fonts and a logo, see build a template.

@page {
size: A4 landscape;
margin: 15mm 12mm;
}
.totals {
break-inside: avoid;
}
  • Block and inline layout, flexbox and grid, tables (including border-collapse, <colgroup> and <col> widths), floats, and relative and absolute positioning.
  • Box model: margins, padding, borders, border-radius, box-sizing, overflow.
  • Text: font properties, line-height, letter-spacing, text-align, text-transform, text-decoration, white-space, word-break, overflow-wrap, text-overflow.
  • Colours and backgrounds, including linear, radial and conic gradients, opacity, box-shadow and text-shadow.
  • transform, CSS custom properties (var(--x)) and calc().
  • Lists, links (clickable in the PDF) and SVG images.
  • @page size and margins, and the page break properties.
Feature What happens Instead
@page :first and other page selectors, named pages, margin boxes Ignored Put page numbers in the header or footer. For content that changes per page, such as a first-page letterhead, combine templates.
filter: blur() and filter: drop-shadow() The render fails Prepare the effect as an image.
backdrop-filter Ignored Prepare the effect as an image.

JavaScript in the document and fonts or images at external URLs are not loaded either; see what’s not supported.

The paper and margins come from the template’s page.json (see templates). A Reports API request or a preview can override them for one render with its pdf options (see the API reference), and CSS @page rules override both; see which value wins.

Property Effect
margin, margin-top, margin-right, margin-bottom, margin-left Replace the margins side by side. A side the rule leaves out keeps its value. Each 0 to 500 mm.
size Turns the page to the rule’s orientation. The paper itself changes only when page.json or the request sets "preferCssPageSize": true.

size accepts:

  • auto.
  • One or two lengths, such as 80mm 200mm. One length makes a square page. Each side above 0 and up to 2,000 mm. The orientation is the one the width and height imply.
  • A3, A4, A5, B4, B5, JIS-B4, JIS-B5, letter, legal or ledger, optionally with portrait or landscape, or the orientation alone.

Lengths can use mm, cm, Q, in, pt, pc or px, or be a bare 0. A value that cannot be read, such as a percentage or a size over the limits, is skipped. !important makes no difference.

Which rules count:

  • Rules in the template’s <style> elements and its stylesheets, at the top level or inside @media that print matches (print, all, or no media type). Later rules win.
  • Only unnamed @page rules without a page selector.
  • Not @page rules in header.html and footer.html.

Values apply in this order, each replacing the one before:

  1. The template’s page.json.
  2. The request’s pdf options (Reports API and previews), field by field.
  3. The document’s @page rules.

When an @page rule sets an orientation, the paper (pageSize, or the rule’s own size with preferCssPageSize) is turned to it, and the orientation setting no longer applies.

header.html and footer.html (the Page header and Page footer tabs in Studio) are Handlebars, with the same data, helpers, partials and translations as the body. They work in HTML and component templates, not in spreadsheet templates. Leave the file empty for no band.

An element with the class pageNumber or totalPages, anywhere in a header or footer, is filled with the current page number or the total page count:

<span class="pageNumber"></span> / <span class="totalPages"></span>

With pageRanges, the numbers still count the whole document.

  • The band is drawn inside the top or bottom margin, which does not grow to fit it. If it is cut off, make the margin larger.
  • It spans the full page width.
  • Its top-level elements sit in a flex row, aligned to the top of the margin in a header and the bottom in a footer. The band’s own html and body tags are dropped.
  • Backgrounds are not printed unless header.html or footer.html sets print-color-adjust: exact.
  • Every page gets the same band. Only the page numbers change from page to page.
  • Images resolve by path as in the body.
  • The template’s stylesheets apply to the bands, and a <style> element in a band applies to the whole document. Style the band with inline style attributes to keep it separate.

Supported: break-before: page, break-after: page, break-inside: avoid, and page-break-before: always, page-break-after: always and page-break-inside: avoid.

A table that runs over several pages repeats its <thead> at the top of each page and prints its <tfoot> once, after the last row. Add break-inside: avoid to tr to keep each row on one page.

Text is set in fonts bundled with Typefold and the fonts you upload, never in fonts installed on the server or loaded from a URL.

font-family in your CSS What is used
Arial GNU FreeSans (regular and bold).
A family you uploaded and declared Your font.
Anything else, including sans-serif, serif, Helvetica Geist, the built-in sans-serif font.
  • Formats. TTF, OTF and WOFF2.
  • Declaring. An @font-face rule for each file, with font-family, src: url(...), font-weight and font-style. url() paths start from the folder of the file they are written in; see how paths resolve.
  • Embedding. src: url({#asset ../fonts/Inter-Regular.ttf @encoding=dataURI}) embeds the font in the CSS.
  • Fallback. Characters the chosen font lacks fall back to the bundled fonts: Geist, FreeSans, FreeSerif Bold for symbols such as ★, and Noto Color Emoji for emoji. Uploaded fonts are never used as fallbacks for other families, so a monospace font you uploaded cannot replace text set in Arial.
  • Failures. A character no font covers fails the render with ASSET_INVALID (font_glyphs_missing): upload a font that covers it and use it in your CSS. A font file that cannot be read fails with ASSET_INVALID (font_invalid). See errors.
  • Formats. PNG, JPEG and SVG.
  • Referencing. Upload the image to the workspace and name it by path from src or CSS url(), or embed it with {#asset path @encoding=dataURI}.
  • No network. Images at external https:// URLs are not fetched. Upload them, or send them in your data as data: URLs.
  • Failures. An image that cannot be decoded fails with ASSET_INVALID (image_invalid).

The renderer lays out HTML as Chrome prints it, including in these cases:

  • HTML parsing follows Chrome, which repairs unclosed tags and the like. Links are blue and underlined, and tables without a width shrink to their content, as in Chrome.
  • Wide documents shrink to fit. A document wider than the page area is laid out on a larger page, up to twice the size, and scaled down onto the paper, header and footer included.
  • Full-page sections. With html, body { height: 100% }, a percentage height on a child of body is a share of the page.
  • Grids with a fixed column. In a grid that mixes auto and fixed-size tracks, the auto track may shrink so the fixed track stays on the page. A min-width on the grid’s items opts out.
  • Images. An image with width: 100% is laid out as a block. An image alone in its container with max-width (and max-height) is scaled down with its aspect ratio kept.
  • Percentage widths on inline blocks and floats keep their size.
  • Row heights. A height on a table row applies to its cells.
  • Line breaks. Spaces around <br> and at the start or end of a block add no indentation.

When the PDF of an HTML template looks different from what you expect, request a preview, POST /v1/templates/{id}/preview, with "format": "html". It returns the filled HTML, with its stylesheets, header and footer, as one page you can open in a browser. See the API reference.