Documentation

Tables

A Table is the heart of most invoices and reports. You give it the column widths and the rows, and it aligns every cell, repeats the header across pages, and breaks between rows so a row never gets cut in half.

import { Table, Text } from "@jasy/pdf";

Table(
  {
    columns: ["1fr", 50, 80, 90],
    header: [
      Text("Description", { bold: true }),
      Text("Qty", { bold: true, align: "right" }),
      Text("Unit", { bold: true, align: "right" }),
      Text("Amount", { bold: true, align: "right" }),
    ],
    cellPadding: { x: 8, y: 6 },
    rule: "#c2ccdb",
  },
  [
    [
      Text("Website design & build"),
      Text("1", { align: "right" }),
      Text("9600.00", { align: "right" }),
      Text("9600.00", { align: "right" }),
    ],
    [
      Text("Strategy consulting"),
      Text("12", { align: "right" }),
      Text("140.00", { align: "right" }),
      Text("1680.00", { align: "right" }),
    ],
  ],
);

Columns

columns has one entry per column, and decides how wide each one is. Mix the three kinds freely.

WidthMeaning
90a fixed width in points
"1fr", "2fr"a fraction of the leftover space, shared in proportion (like CSS fr)
"auto"as wide as the widest cell in that column, measured at layout time

A typical invoice uses one "1fr" description column that absorbs the slack, and fixed-width number columns on the right.

Cells and rows

rows is an array of rows, each an array of cells. A cell is any element, or a plain string (which is wrapped in Text for you). Right-align number columns with Text(value, { align: "right" }).

[
  ["Apples", "Granny Smith", Text("12", { align: "right" })],
  ["Oranges", "Valencia", Text("8", { align: "right" })],
];

Options

PropTypeWhat it does
columnsColumnWidth[]one width per column (required)
headerCell[]a header row that repeats at the top of every page the table spills onto
cellPaddingInsetspadding inside every cell
ruleColorInputa thin horizontal line under the header and along the foot
cellBorderColorInputa full grid line around every cell
gapnumberspace between rows and columns
rowGap colGapnumberoverride gap for one axis

Rules or a full grid

rule draws two thin lines, under the header and at the foot - the clean look most invoices use. cellBorder instead draws the complete grid around every cell.

// invoice style: thin separators
{ columns: ["1fr", 80], rule: "#c2ccdb", cellPadding: { x: 8, y: 6 } }

// spreadsheet style: full grid
{ columns: ["1fr", 80], cellBorder: "#c2ccdb", cellPadding: 8 }

Use one or the other. cellBorder already draws the outer edges, so do not wrap the table in a Box border as well, or the edges double up.

Across pages

A Table is just a Column of atomic rows, so it paginates for free: when a row does not fit on the current page it moves to the next one whole, and the header row reprints at the top. You write the rows once and long tables simply flow.

See it all in one file

import { writeFileSync } from "node:fs";
import { Document, Page, Table, Text, renderToBytes } from "@jasy/pdf";

const money = (n: number) => Text(n.toFixed(2), { align: "right" });

async function build() {
  const doc = Document([
    Page({ size: "A4", margin: 56 }, [
      Table(
        {
          columns: ["1fr", 50, 80, 90],
          header: [
            Text("Description", { bold: true }),
            Text("Qty", { bold: true, align: "right" }),
            Text("Unit", { bold: true, align: "right" }),
            Text("Amount", { bold: true, align: "right" }),
          ],
          cellPadding: { x: 8, y: 6 },
          rule: "#c2ccdb",
        },
        [
          [Text("Website design & build"), Text("1", { align: "right" }), money(9600), money(9600)],
          [Text("Strategy consulting"), Text("12", { align: "right" }), money(140), money(1680)],
          [Text("Printed brand book"), Text("25", { align: "right" }), money(28), money(700)],
        ],
      ),
    ]),
  ]);

  writeFileSync("table.pdf", await renderToBytes(doc));
}

build();
jasypdf

Declarative PDFs in pure TypeScript. ZUGFeRD & XRechnung compliant, with no headless browser and no Java.

Resources

© 2026 Florian Heuberger · MIT License

Built with Nuxt · self-hosted fonts · no trackers