ltn-print-labels is a standalone vanilla-JavaScript module (ESM, zero runtime dependencies) that turns a class roster into a printable label grid of student names — the kind you cut out and stick on trays, cubbies and notebooks. It handles the fiddly parts a teacher actually hits: two children called Léa become Léa M. / Léa C. (shortest last-name prefix that separates the group), the class is repeated a whole number of times to fill an A4 page, labels are butted together so the 1 px borders double as cut lines, and every label gets its own font size — the nominal size for names that fit, shrunk only for the ones that would overflow, so no column is ever widened and no name is ever hyphenated.

I built it for Le Tableau Noir — where it powers the Étiquettes export — and released it as a reusable package.

Get the code

npm install ltn-print-labels
import { computeLabelLayout, createLabelPreview } from "ltn-print-labels";
import "ltn-print-labels/style.css"; // preview decoration only

const roster = [
  { firstname: "Ada", lastname: "Lovelace", level: "Grade 5" },
  { firstname: "Alan", lastname: "Turing", level: "Grade 5" },
];

// A ready-to-render model: cell grid + mm geometry, the same object the
// consumer POSTs to its PDF backend.
const layout = computeLabelLayout(roster, {
  orient: "P", // "P" | "L"
  cols: 4, // labels per row (1–7 portrait, 1–9 landscape)
  fields: "first", // "first" | "last" | "both"
  showLevel: true,
  // fontMm omitted → auto: the largest size at which ~85 % of the labels fit
});

// Live WYSIWYG A4 preview, scaled to its container, no CSS transform.
const preview = createLabelPreview("#preview", roster, { orient: "P", cols: 4 });
preview.update(roster, { orient: "L", cols: 6 });

The module does not parse CSV — it takes plain { firstname, lastname, level? } objects, however you assembled them. computeLabelLayout also returns fontMmAuto / fontMmMin / fontMmMax so a UI can drive the max-font slider, and autoNominalFontMm(texts, labelWmm, ceilingMm) is exported on its own.

Live demo

This demo loads ltn-print-labels@0.1.15 — the current npm release — straight from the jsDelivr CDN; the PDF export pulls jsPDF the same way, only when you click. Nothing is sent anywhere: the roster, the layout maths and the PDF are all built in your browser. On the left, the input — the built-in Solvay 1927 roster (the 29 people in the 1927 Solvay Conference photograph, with Solvay 1927 as the level) or your own CSV — plus every layout option; on the right, the live A4 sheet the module renders, exactly as it would print.