Examples
These are the starter snippets bundled with the Playground.
Each one is a complete, self-contained program: it imports from
@barrierbreak/a11ydocs-pdf, builds a document, and export defaults it. Paste any of
them into the playground and run it as-is.
The snippets below are embedded directly from the playground's sample files, so the code here and the code you see in the playground are always identical.
Run these
Open the playground and pick a sample from
the dropdown, or copy a snippet into your own project. For non-browser usage,
swap export default doc; for await writeFile("out.pdf", doc.toUint8Array());.
Rich text and layout
Document-wide text defaults, registered font families, mm() units, inline
rich text with underline/strike, justified alignment, vertical alignment,
shape helpers (rect, circle), named colors, and richParagraph template
blocks. The flowing portion is rendered with renderTemplate().
import { createDocument, rgb, mm } from "@barrierbreak/a11ydocs-pdf";
const TABLE_BORDER = rgb(0.72, 0.76, 0.82);
const TABLE_HEADER = rgb(0.92, 0.94, 0.98);
// Document-wide text defaults are inherited by every text call.
const doc = createDocument({
title: "Rich Text & Layout",
language: "en-US",
defaults: { font: "Helvetica", fontSize: 12, color: rgb(0.15, 0.15, 0.2) }
});
// Register a font family so bold/italic resolve by flag. Faces may be built-in
// names (shown here), embedded font handles, or raw font bytes.
const sans = doc.registerFontFamily("Sans", {
regular: "Helvetica",
bold: "Helvetica-Bold",
italic: "Helvetica-Oblique",
boldItalic: "Helvetica-BoldOblique"
});
const page = doc.addPage({ size: "A4" });
// mm() units, chained calls, bold/italic flags, and underline/strike decoration.
page
.text("Rich text & layout", { x: mm(20), y: mm(272), font: sans, bold: true, fontSize: 22 })
.text("Authored with mm() units and chained calls.", { x: mm(20), y: mm(264), italic: true, color: "navy" })
.text("Underlined and struck-through runs.", { x: mm(20), y: mm(257), underline: true, strike: true, fontSize: 11 });
// Inline rich text - mixed styles and an inline link wrapping together.
page.richText(
[
{ text: "Inline rich text mixes " },
{ text: "bold", bold: true },
{ text: ", " },
{ text: "italic", italic: true },
{ text: ", " },
{ text: "color", color: rgb(0.80, 0.10, 0.10) },
{ text: ", " },
{ text: "underline", underline: true },
{ text: ", and a " },
{ text: "clickable link", color: rgb(0.10, 0.30, 0.80), link: "https://a11ydocs.barrierbreak.net" },
{ text: " on the same wrapping lines." }
],
{ x: mm(20), y: mm(247), width: mm(170), fontSize: 13, lineHeight: 18 }
);
// Justified alignment for rich text.
page.richText(
[{ text: "Justify spreads words across the full measure so both edges of the column line up cleanly along its width." }],
{ x: mm(20), y: mm(227), width: mm(170), fontSize: 12, align: "justify" }
);
// Vertical alignment inside a fixed-height box (framed with a 1.3 shape helper).
page.rect(mm(20), mm(165), mm(80), mm(40), { stroke: rgb(0.7, 0.7, 0.7), lineWidth: 0.5 });
page.textBlock("Centered in a 40mm box (verticalAlign: middle).", {
x: mm(22), y: mm(203), width: mm(76), height: mm(36),
verticalAlign: "middle", align: "center", fontSize: 12, lineHeight: 16
});
// Shape helpers: a labelled circle alongside the box.
page.circle(mm(140), mm(185), mm(18), { fill: "teal" });
page.text("circle()", { x: mm(130), y: mm(150), fontSize: 11, color: rgb(0.45, 0.45, 0.45) });
// Per-cell vertical alignment in a table row.
page.table(
[[
{ text: "A long cell that wraps onto a few lines inside its column to make the row tall." },
{ text: "top", verticalAlign: "top", header: true },
{ text: "middle", verticalAlign: "middle", header: true },
{ text: "bottom", verticalAlign: "bottom", header: true }
]],
{
x: mm(20),
y: mm(120),
width: mm(170),
columnWidths: [mm(85), mm(28), mm(28), mm(29)],
fontSize: 11,
cellPadding: 4,
borders: "all",
borderColor: TABLE_BORDER,
headerBackground: TABLE_HEADER
}
);
// richParagraph template block (rendered on a second page).
doc.renderTemplate({
page: { size: "A4", margin: 56 },
styles: { heading1: { fontSize: 22, color: rgb(0.11, 0.22, 0.45) } },
blocks: [
{ type: "heading", text: "richParagraph template block", options: { level: 1 } },
{
type: "richParagraph",
runs: [
{ text: "Status: " },
{ text: "PASSED", bold: true, color: rgb(0, 0.5, 0) },
{ text: " - see the " },
{ text: "report", link: "https://a11ydocs.barrierbreak.net", color: rgb(0.1, 0.3, 0.8) },
{ text: " for details." }
]
}
]
});
export default doc;
Text formatting
Superscript/subscript and baseline rise, render modes (stroke outline,
fillStroke, invisible), horizontalScaling to condense/expand glyphs, and
inline highlight — shown on template paragraphs and rich-text runs. See
Text and Fonts.
import { createDocument, rgb } from "@barrierbreak/a11ydocs-pdf";
// Superscript/subscript, baseline rise, render modes (outline/invisible),
// horizontal scaling, and inline highlight — on both template paragraphs and
// per-run richParagraph content.
const doc = createDocument({
title: "Text Formatting",
language: "en-US",
defaults: { font: "Helvetica", fontSize: 13, color: rgb(0.15, 0.15, 0.2) }
});
doc.renderTemplate({
page: { size: "A4", margin: 56 },
styles: {
heading2: { fontSize: 11, color: "navy", marginTop: 12, marginBottom: 8 }
},
blocks: [
{ type: "heading", text: "Superscript & subscript", options: { level: 1 } },
{
type: "richParagraph",
runs: [
{ text: "Einstein: E = mc" },
{ text: "2", superscript: true },
{ text: " Water: H" },
{ text: "2", subscript: true },
{ text: "O" },
{ text: " Footnote" },
{ text: "12", superscript: true }
],
options: { fontSize: 15, marginBottom: 14 }
},
{ type: "heading", text: "Render modes (fill / stroke / fillStroke)", options: { level: 2 } },
{
type: "richParagraph",
runs: [
{ text: "Filled ", fontSize: 26, bold: true },
{
text: "Outline ",
fontSize: 26,
bold: true,
renderMode: "stroke",
strokeWidth: 0.6,
strokeColor: rgb(0.8, 0.1, 0.1)
},
{
text: "Both",
fontSize: 26,
bold: true,
renderMode: "fillStroke",
strokeWidth: 0.5,
strokeColor: "navy",
color: rgb(0.85, 0.9, 1)
}
],
options: { marginBottom: 14 }
},
{ type: "heading", text: "Horizontal scaling", options: { level: 2 } },
{ type: "paragraph", text: "CONDENSED 70%", options: { fontSize: 16, horizontalScaling: 70, marginBottom: 6 } },
{ type: "paragraph", text: "NORMAL 100%", options: { fontSize: 16, marginBottom: 6 } },
{ type: "paragraph", text: "EXPANDED 140%", options: { fontSize: 16, horizontalScaling: 140, marginBottom: 14 } },
{ type: "heading", text: "Highlight", options: { level: 2 } },
{
type: "paragraph",
text: "Whole line highlighted like a marker.",
options: { highlight: "yellow", marginBottom: 10 }
},
{
type: "richParagraph",
runs: [
{ text: "Review status: " },
{ text: "APPROVED", bold: true, highlight: rgb(0.8, 1, 0.8) },
{ text: " then " },
{ text: "REJECTED", bold: true, color: "white", highlight: rgb(0.85, 0.2, 0.2) }
]
}
]
});
export default doc;
Google Fonts
Fetch several Google Fonts (Inter, Geist, Instrument Serif with an italic face, and Bitcount Grid Single) and embed them as font families. In Node, use loadGoogleFont("Inter", { weight: 700 }); this browser sample fetches ready-made .ttf files from the CORS-enabled @expo-google-fonts packages on jsDelivr instead, because the Google Fonts API serves woff2 to browsers, which the engine cannot decode. See Text and Fonts → Google Fonts.
import { createDocument, rgb } from "@barrierbreak/a11ydocs-pdf";
// In Node, call loadGoogleFont("Geist", { weight: 700 }) and embed the bytes.
// Browsers receive woff2 from the Google Fonts API (which the engine cannot
// decode), so this sample fetches ready-made .ttf files from the CORS-enabled
// @expo-google-fonts packages on jsDelivr instead.
const cdn = "https://cdn.jsdelivr.net/npm/@expo-google-fonts";
const FONTS = {
inter400: `${cdn}/inter/400Regular/Inter_400Regular.ttf`,
inter700: `${cdn}/inter/700Bold/Inter_700Bold.ttf`,
geist400: `${cdn}/geist/400Regular/Geist_400Regular.ttf`,
geist700: `${cdn}/geist/700Bold/Geist_700Bold.ttf`,
instrumentRegular: `${cdn}/instrument-serif/400Regular/InstrumentSerif_400Regular.ttf`,
instrumentItalic: `${cdn}/instrument-serif/400Regular_Italic/InstrumentSerif_400Regular_Italic.ttf`,
bitcount: `${cdn}/bitcount-grid-single/400Regular/BitcountGridSingle_400Regular.ttf`
};
async function ttf(url: string): Promise<Uint8Array> {
return new Uint8Array(await (await fetch(url)).arrayBuffer());
}
const doc = createDocument({ title: "Google Fonts", language: "en-US" });
// Fetch every face in parallel. Multiple faces under one template font family
// let bold/italic resolve by flag.
const [inter400, inter700, geist400, geist700, instReg, instItal, bitcount] = await Promise.all([
ttf(FONTS.inter400),
ttf(FONTS.inter700),
ttf(FONTS.geist400),
ttf(FONTS.geist700),
ttf(FONTS.instrumentRegular),
ttf(FONTS.instrumentItalic),
ttf(FONTS.bitcount)
]);
const specimen = "The quick brown fox jumps 0123456789";
doc.renderTemplate({
fonts: {
Inter: { regular: inter400, bold: inter700 },
Geist: { regular: geist400, bold: geist700 },
"Instrument Serif": { regular: instReg, italic: instItal },
"Bitcount Grid Single": { regular: bitcount }
},
page: { size: "A4", margin: 56 },
blocks: [
{
type: "heading",
text: "Google Fonts, embedded",
options: { level: 1, font: "Geist", bold: true, fontSize: 24 }
},
{
type: "paragraph",
text: "Fetched as .ttf and registered through renderTemplate().",
options: { font: "Geist", fontSize: 11, color: rgb(0.4, 0.4, 0.45), marginBottom: 24 }
},
{ type: "paragraph", text: "Inter", options: { font: "Inter", fontSize: 9, color: rgb(0.5, 0.5, 0.55), marginBottom: 12 } },
{ type: "paragraph", text: specimen, options: { font: "Inter", fontSize: 20, marginBottom: 18 } },
{ type: "paragraph", text: "Inter Bold", options: { font: "Inter", fontSize: 9, color: rgb(0.5, 0.5, 0.55), marginBottom: 12 } },
{ type: "paragraph", text: specimen, options: { font: "Inter", bold: true, fontSize: 20, marginBottom: 18 } },
{ type: "paragraph", text: "Geist", options: { font: "Geist", fontSize: 9, color: rgb(0.5, 0.5, 0.55), marginBottom: 12 } },
{ type: "paragraph", text: specimen, options: { font: "Geist", fontSize: 20, marginBottom: 18 } },
{ type: "paragraph", text: "Instrument Serif", options: { font: "Instrument Serif", fontSize: 9, color: rgb(0.5, 0.5, 0.55), marginBottom: 12 } },
{ type: "paragraph", text: specimen, options: { font: "Instrument Serif", fontSize: 20, marginBottom: 18 } },
{ type: "paragraph", text: "Instrument Serif Italic", options: { font: "Instrument Serif", fontSize: 9, color: rgb(0.5, 0.5, 0.55), marginBottom: 12 } },
{ type: "paragraph", text: specimen, options: { font: "Instrument Serif", italic: true, fontSize: 20, marginBottom: 18 } },
{ type: "paragraph", text: "Bitcount Grid Single", options: { font: "Bitcount Grid Single", fontSize: 9, color: rgb(0.5, 0.5, 0.55), marginBottom: 12 } },
{ type: "paragraph", text: specimen, options: { font: "Bitcount Grid Single", fontSize: 20 } }
]
});
export default doc;
Stylesheets and tables
Define reusable styles once and apply them by block type or class, then render a
header-row table — all through renderTemplate().
import { createDocument, rgb } from "@barrierbreak/a11ydocs-pdf";
// Build a PDF and `export default` the document.
const doc = createDocument({
title: "Stylesheets and Tables Demo",
language: "en-US",
xmpMetadata: {
creator: "a11ydocs playground"
}
});
doc.renderTemplate({
page: { size: "A4", margin: 56 },
styles: {
heading1: { fontSize: 28, color: rgb(0.11, 0.22, 0.45) },
heading2: { fontSize: 16, color: rgb(0.11, 0.22, 0.45) },
paragraph: { fontSize: 12, lineHeight: 18 },
muted: { color: rgb(0.45, 0.45, 0.45) }
},
blocks: [
{ type: "heading", text: "Hello from a11ydocs", options: { level: 1 } },
{ type: "paragraph", text: "Generated in the browser - no server, no dependencies.", class: "muted" },
{ type: "heading", text: "Stylesheets", options: { level: 2 } },
{ type: "paragraph", text: "Define reusable styles once and apply them by block type or via a class." },
{ type: "paragraph", text: "Edit this code on the left and hit Run.", class: "muted" },
{ type: "heading", text: "Tables", options: { level: 2 } },
{ type: "paragraph", text: "Pass `borders` to control which lines draw: \"all\", \"horizontal\", \"outer\", or \"none\". Set `borderColor`/`borderWidth` to style them.", class: "muted" },
{ type: "paragraph", text: "borders: \"all\" - full grid" },
{
type: "table",
rows: [
[{ text: "Item", header: true }, { text: "Qty", header: true }, { text: "Price", header: true }],
["Notebook", "2", "$11.00"],
["Pen", "5", "$7.50"]
],
options: { headerRows: 1, borders: "all", borderColor: rgb(0.6, 0.6, 0.6), headerBackground: rgb(0.92, 0.94, 0.98) }
},
{ type: "paragraph", text: "borders: \"horizontal\" - row rules only" },
{
type: "table",
rows: [
[{ text: "Item", header: true }, { text: "Qty", header: true }, { text: "Price", header: true }],
["Notebook", "2", "$11.00"],
["Pen", "5", "$7.50"]
],
options: { headerRows: 1, borders: "horizontal", borderColor: rgb(0.2, 0.3, 0.5), borderWidth: 1, headerBackground: rgb(0.92, 0.94, 0.98) }
},
{ type: "paragraph", text: "borders: \"outer\" - frame only, with zebra stripes" },
{
type: "table",
rows: [
[{ text: "Item", header: true }, { text: "Qty", header: true }, { text: "Price", header: true }],
["Notebook", "2", "$11.00"],
["Pen", "5", "$7.50"]
],
options: { headerRows: 1, borders: "outer", borderColor: rgb(0.4, 0.4, 0.4), headerBackground: rgb(0.92, 0.94, 0.98), zebra: true }
}
]
});
export default doc;
Single page template
A minimal single-page document rendered from declarative template blocks.
import { createDocument, rgb } from "@barrierbreak/a11ydocs-pdf";
const doc = createDocument({
title: "Single Page Demo",
language: "en-US",
xmpMetadata: {
creator: "a11ydocs playground"
}
});
doc.renderTemplate({
page: { size: "A4", margin: 56 },
blocks: [
{
type: "heading",
text: "Hello from a11ydocs",
options: { level: 1, fontSize: 28, color: rgb(0.11, 0.22, 0.45) }
},
{
type: "paragraph",
text: "Rendered from a declarative template on a single page.",
options: { fontSize: 12 }
}
]
});
export default doc;
Hello world (template)
The renderTemplate() starting point, with document info and XMP metadata.
import { createDocument, hex } from "@barrierbreak/a11ydocs-pdf";
const doc = createDocument({
language: "en-US",
info: {
title: "Hello World",
author: "a11ydocs",
creationDate: "D:20260413213000Z",
modificationDate: "D:20260413213000Z"
}
});
doc.setXmpMetadata({
title: "Hello World",
creator: "a11ydocs",
creatorTool: "a11ydocs",
createDate: "2026-04-13T21:30:00Z",
modifyDate: "2026-04-13T21:30:00Z",
metadataDate: "2026-04-13T21:30:00Z"
});
doc.renderTemplate({
info: { title: "Hello World", author: "a11ydocs" },
page: { size: "A4" },
blocks: [
{
type: "heading",
text: "Hello from a11ydocs",
options: { level: 1, color: hex("1c2f6b") }
},
{
type: "paragraph",
text: "This PDF was generated using the renderTemplate API, which provides a production-grade template system with automatic page breaks, headings, tables, and images.",
options: { fontSize: 12 }
}
]
});
export default doc;
Invoice
A compact invoice built from a styled heading, a muted subtitle, and a totals table.
import { createDocument, rgb } from "@barrierbreak/a11ydocs-pdf";
const TABLE_BORDER = rgb(0.72, 0.76, 0.82);
const TABLE_HEADER = rgb(0.92, 0.94, 0.98);
const doc = createDocument({
title: "Invoice 1042",
language: "en-US",
xmpMetadata: {
creator: "a11ydocs playground"
}
});
doc.renderTemplate({
page: { size: "A4", margin: 56 },
styles: {
heading1: { fontSize: 26, color: rgb(0.1, 0.1, 0.12) },
muted: { color: rgb(0.5, 0.5, 0.5), fontSize: 10 },
paragraph: { fontSize: 11, lineHeight: 16 }
},
blocks: [
{ type: "heading", text: "Invoice #1042", options: { level: 1 } },
{ type: "paragraph", text: "Acme Corp - Issued 2026-05-29", class: "muted" },
{
type: "table",
rows: [
[{ text: "Description", header: true }, { text: "Qty", header: true }, { text: "Amount", header: true }],
["Design work", "10", "$1,000.00"],
["Development", "20", "$3,000.00"],
[{ text: "Total", header: true }, "-", "$4,000.00"]
],
options: {
headerRows: 1,
borders: "all",
borderColor: TABLE_BORDER,
headerBackground: TABLE_HEADER
}
}
]
});
export default doc;
JSON to PDF
An entire document — metadata, a footer with page tokens, a table, and
absolute-positioned blocks — described as one PdfDocumentJson
value and rendered with renderDocumentJson().
/**
* Declarative JSON -> PDF, the full surface in one value.
*
* A tagged (accessible) two-page report described entirely as `PdfDocumentJson`:
* document metadata and language, a tagged structure tree, an auto-generated
* bookmark outline, artifact header/footer with page tokens, headings, rich
* inline text, ordered/unordered lists, a header-row table, a sectioned figure
* with alternate text, a note annotation, a hyperlink, and absolute-positioned
* blocks (an approval stamp and a signature field) placed at exact coordinates.
*
* node json-to-pdf.js
*/
import { writeFile } from "node:fs/promises";
import { renderDocumentJson, type PdfDocumentJson } from "@barrierbreak/a11ydocs-pdf";
/** A 1x1 solid-color 24-bit BMP, base64-encoded — embedded inline as a figure. */
function solidBmpBase64(r: number, g: number, b: number): string {
const bmp = Buffer.alloc(58);
bmp.write("BM", 0, "ascii");
bmp.writeUInt32LE(58, 2); // file size
bmp.writeUInt32LE(54, 10); // pixel-data offset
bmp.writeUInt32LE(40, 14); // DIB header size
bmp.writeInt32LE(1, 18); // width
bmp.writeInt32LE(1, 22); // height
bmp.writeUInt16LE(1, 26); // planes
bmp.writeUInt16LE(24, 28); // bits per pixel
bmp.writeUInt32LE(4, 34); // image size
bmp[54] = b; // BMP stores BGR
bmp[55] = g;
bmp[56] = r;
return bmp.toString("base64");
}
const report: PdfDocumentJson = {
// ── Document options ──────────────────────────────────────────────────────
title: "Accessibility Conformance Report",
info: { author: "a11ydocs", subject: "WCAG 2.2 audit", keywords: "accessibility, pdf, tagged" },
language: "en-US",
tagged: true,
compress: true,
autoOutline: { maxLevel: 3 },
// ── Page, header, footer ──────────────────────────────────────────────────
page: { size: "A4", margin: 56, headerHeight: 40, footerHeight: 40 },
header: [
{ type: "paragraph", text: "{{title}}", options: { fontSize: 8, color: "gray", tag: "Artifact" } },
],
footer: [
{
type: "paragraph",
text: "Page {{pageNumber}} of {{pageCount}}",
options: { fontSize: 8, color: "gray", align: "center", tag: "Artifact" },
},
],
// ── Body ──────────────────────────────────────────────────────────────────
blocks: [
{ type: "heading", text: "Accessibility Conformance Report", options: { level: 1 } },
{
type: "paragraph",
text: "This report records the results of a tagged-PDF audit and is itself produced as a tagged, PDF/UA-style document.",
},
{
type: "richParagraph",
runs: [
{ text: "Overall status: " },
{ text: "PASS", bold: true, color: "green" },
{ text: " - reviewed against " },
{ text: "WCAG 2.2 AA", italic: true },
{ text: "." },
],
},
{ type: "heading", text: "Findings", options: { level: 2 } },
{
type: "list",
items: [
"The document language is declared (en-US).",
"Headings follow a logical, gap-free hierarchy.",
{ body: "Images carry alternate text", children: ["Decorative images are marked as artifacts."] },
],
options: { ordered: false, width: 483 },
},
{ type: "heading", text: "Scores", options: { level: 2 } },
{
type: "table",
rows: [
[
{ text: "Criterion", header: true },
{ text: "Result", header: true },
{ text: "Notes", header: true },
],
["Tagged structure", "Pass", "Complete structure tree"],
["Color contrast", "Pass", "4.8:1 body text"],
["Reading order", "Pass", "Matches visual order"],
],
options: { headerRows: 1 },
},
{
type: "section",
role: "Sect",
blocks: [
{ type: "heading", text: "Evidence", options: { level: 3 } },
{ type: "paragraph", text: "The figure below summarizes the four checks; all passed." },
],
},
// Image blocks use `src` at the top level of `blocks`. A 1x1 BMP is stretched
// into a bar; `altText` + `tag: "Figure"` make it accessible.
{
type: "bmp",
src: { base64: solidBmpBase64(34, 139, 87) },
options: {
width: 160,
height: 36,
altText: "Green bar indicating all four accessibility checks passed",
tag: "Figure",
},
},
{ type: "note", options: { contents: "Re-audit scheduled for the next quarter." } },
{ type: "link", text: "Read the WCAG 2.2 specification", url: "https://www.w3.org/TR/WCAG22/" },
{ type: "pageBreak" },
{ type: "heading", text: "Sign-off", options: { level: 2 } },
{ type: "paragraph", text: "Reviewed and approved by the accessibility team." },
// ── Absolute-positioned blocks (coordinate system) ──────────────────────
{
type: "rect",
width: 150,
height: 54,
style: { paint: "stroke", stroke: { color: "green", width: 2 } },
position: { x: 360, y: 660 },
},
{
type: "paragraph",
text: "APPROVED",
options: { fontSize: 22, color: "green" },
position: { x: 378, y: 678 },
},
{
type: "textField",
name: "reviewerSignature",
options: { description: "Reviewer signature", width: 240, height: 16 },
position: { x: 56, y: 640 },
},
],
};
const bytes = await renderDocumentJson(report);
await writeFile("examples/json-to-pdf.pdf", bytes);
console.log(`Wrote examples/json-to-pdf.pdf (${bytes.length} bytes)`);
Multi-column
A two-column newspaper layout that flows paragraphs across columns.
import { createDocument } from "@barrierbreak/a11ydocs-pdf";
const lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ".repeat(8);
const doc = createDocument({
title: "Newspaper Layout Demo",
language: "en-US",
xmpMetadata: {
creator: "a11ydocs playground"
}
});
doc.renderTemplate({
page: { size: "A4", margin: 48, columns: { count: 2, gap: 24 } },
styles: {
heading1: { fontSize: 22 },
paragraph: { fontSize: 11, lineHeight: 16 }
},
blocks: [
{ type: "heading", text: "Newspaper layout", options: { level: 1 } },
{ type: "paragraph", text: lorem },
{ type: "paragraph", text: lorem }
]
});
export default doc;
Color spaces
Every supported color model: hex, rgb, gray, cmyk, plus separation spot
colors and DeviceN multi-ink.
import { createDocument, hex, rgb, gray, cmyk } from "@barrierbreak/a11ydocs-pdf";
const doc = createDocument({
language: "en-US",
info: {
title: "Color Spaces Demo",
author: "a11ydocs"
}
});
doc.renderTemplate({
info: { title: "Color Spaces Demo", author: "a11ydocs" },
page: { size: "A4" },
blocks: [
{
type: "heading",
text: "PDF Color Spaces",
options: { level: 1, color: hex("#1c2f6b") }
},
{
type: "paragraph",
text: "hex('#1565c0') - DeviceRGB hex color. Useful for web-originated palettes and design tokens.",
options: { color: hex("#1565c0"), fontSize: 12, marginTop: 8 }
},
{
type: "paragraph",
text: "rgb(0.08, 0.40, 0.75) - DeviceRGB float. Programmatic color mixing with 0-1 range per channel.",
options: { color: rgb(0.08, 0.4, 0.75), fontSize: 12, marginTop: 4 }
},
{
type: "paragraph",
text: "rgb(0.09, 0.67, 0.36) - Another RGB example, demonstrating a green tone.",
options: { color: rgb(0.09, 0.67, 0.36), fontSize: 12, marginTop: 4 }
},
{
type: "paragraph",
text: "gray(0.5) - DeviceGray mid-tone. Single-channel lightness, ideal for monochrome content.",
options: { color: gray(0.5), fontSize: 12, marginTop: 8 }
},
{
type: "paragraph",
text: "gray(0.2) - DeviceGray dark. Lower values produce deeper shades.",
options: { color: gray(0.2), fontSize: 12, marginTop: 4 }
},
{
type: "paragraph",
text: "gray(0.8) - DeviceGray light. Higher values produce lighter shades.",
options: { color: gray(0.8), fontSize: 12, marginTop: 4 }
},
{
type: "paragraph",
text: "cmyk(0.80, 0.20, 0.00, 0.15) - DeviceCMYK. Process color for professional print workflows.",
options: { color: cmyk(0.8, 0.2, 0, 0.15), fontSize: 12, marginTop: 8 }
},
{
type: "paragraph",
text: "cmyk(0.00, 1.00, 1.00, 0.00) - Bright red in CMYK process.",
options: { color: cmyk(0, 1, 1, 0), fontSize: 12, marginTop: 4 }
},
{
type: "paragraph",
text: "cmyk(0.00, 0.00, 1.00, 0.00) - Bright yellow in CMYK process.",
options: { color: cmyk(0, 0, 1, 0), fontSize: 12, marginTop: 4 }
},
{
type: "heading",
text: "Advanced Color Spaces",
options: { level: 2, color: hex("#1565c0"), marginTop: 16 }
},
{
type: "paragraph",
text: "Separation spot color - Pantone 286 C. Named ink with a tint value (0-1) and CMYK fallback for on-screen previews and composite printing.",
options: {
fontSize: 12,
color: {
kind: "separation",
name: "Pantone 286 C",
tint: 1,
alternate: cmyk(1, 0.66, 0, 0.04)
}
}
},
{
type: "paragraph",
text: "DeviceN multi-ink - Pantone 286 C + Black. Combines multiple named colorants into a single color specification. Each ink has its own tint, with a CMYK alternate for fallback.",
options: {
fontSize: 12,
marginTop: 4,
color: {
kind: "deviceN",
names: ["Pantone 286 C", "Black"],
tints: [1, 0.8],
alternate: cmyk(1, 0.66, 0, 0.04)
}
}
}
]
});
export default doc;
Graphics state and transforms
Low-level vector graphics: saveState/restoreState, translate, scale,
rotate, arbitrary transform, alpha transparency, and blend modes.
import { createDocument, hex } from "@barrierbreak/a11ydocs-pdf";
const doc = createDocument({
info: {
title: "Graphics State Demo",
author: "a11ydocs"
}
});
doc.renderTemplate({
page: { size: "A4", margin: 72 },
blocks: [
{
type: "heading",
text: "Graphics State",
options: { level: 1, color: hex("#1a1a2e") }
},
{
type: "paragraph",
text: "The document shell is rendered as a template. The following page uses the low-level graphics-state API because transforms and blend modes operate directly on a page canvas."
}
]
});
const page = doc.addPage({ size: "A4" });
page.text("Graphics State: Transforms, Alpha & Blend Modes", {
x: 72, y: 800, fontSize: 16
});
// Save / restore state
page.text("saveState / restoreState", { x: 72, y: 770, fontSize: 12 });
page.saveState();
page.text("Blue text inside saved state", {
x: 72, y: 740, fontSize: 14, color: hex("#1565c0")
});
page.path(
[{ type: "rect", x: 72, y: 718, width: 200, height: 16 }],
{ paint: "stroke", stroke: { width: 1, color: hex("#1565c0") } }
);
page.restoreState();
page.text("Back to default graphics state", {
x: 72, y: 690, fontSize: 11
});
// Translate
page.text("translate(100, 0) - two rectangles side by side", { x: 72, y: 660, fontSize: 12 });
page.saveState();
page.translate(100, 0);
page.path(
[{ type: "rect", x: 72, y: 630, width: 80, height: 20 }],
{ paint: "fill", fill: { color: hex("#e3f2fd") } }
);
page.restoreState();
page.saveState();
page.translate(188, 0);
page.path(
[{ type: "rect", x: 72, y: 630, width: 80, height: 20 }],
{ paint: "fill", fill: { color: hex("#bbdefb") } }
);
page.restoreState();
// Scale
page.text("scale(1.5, 1.5)", { x: 72, y: 600, fontSize: 12 });
page.saveState();
page.scale(1.5, 1.5);
page.text("SCALED TEXT", { x: 72, y: 380, fontSize: 16, color: hex("#c62828") });
page.restoreState();
// Rotate
page.text("rotate(15 degrees)", { x: 72, y: 570, fontSize: 12 });
page.saveState();
page.rotate(15);
page.text("Rotated text at 15 degrees", { x: 72, y: 530, fontSize: 14, color: hex("#2e7d32") });
page.restoreState();
// Alpha transparency with setExtGState
page.text("Transparency (fillAlpha = 0.3)", { x: 72, y: 500, fontSize: 12 });
page.saveState();
page.setExtGState({ fillAlpha: 0.3 });
page.path(
[{ type: "rect", x: 72, y: 460, width: 100, height: 30 }],
{ paint: "fill", fill: { color: hex("#ff5722") } }
);
page.text("Semi-transparent overlay", { x: 66, y: 472, fontSize: 10, color: hex("#ffffff") });
page.restoreState();
// Blend modes
page.text("Blend mode: 'Multiply' on overlapping rects", { x: 72, y: 440, fontSize: 12 });
page.saveState();
page.setExtGState({ blendMode: "Multiply" });
page.path(
[{ type: "rect", x: 72, y: 390, width: 60, height: 30 }],
{ paint: "fill", fill: { color: hex("#ff0000") } }
);
page.path(
[{ type: "rect", x: 86, y: 380, width: 60, height: 30 }],
{ paint: "fill", fill: { color: hex("#0000ff") } }
);
page.restoreState();
// Transform (skew)
page.text("transform() - skewed parallelogram", { x: 72, y: 360, fontSize: 12 });
page.saveState();
page.transform({ a: 1, b: 0, c: 0.3, d: 1, e: 0, f: 0 });
page.path(
[{ type: "rect", x: 72, y: 320, width: 120, height: 25 }],
{ paint: "fill", fill: { color: hex("#7b1fa2") } }
);
page.restoreState();
export default doc;
Outlines (bookmarks)
Build a nested outline (bookmarks) tree automatically from template headings.
import { createDocument, hex } from "@barrierbreak/a11ydocs-pdf";
const doc = createDocument({
info: {
title: "PDF Outlines (Bookmarks) Demo",
author: "a11ydocs"
}
});
doc.renderTemplate({
page: { size: "A4", margin: 72 },
autoOutline: { maxLevel: 2 },
styles: {
heading1: { fontSize: 20, color: hex("#1a1a2e") },
heading2: { fontSize: 14 }
},
blocks: [
{ type: "heading", text: "Chapter 1: Introduction", options: { level: 1 } },
{ type: "heading", text: "Overview", options: { level: 2 } },
{ type: "paragraph", text: "This is the first chapter covering basic concepts.", options: { fontSize: 12 } },
{ type: "pageBreak" },
{ type: "heading", text: "Chapter 2: Advanced Topics", options: { level: 1 } },
{ type: "heading", text: "Section 2.1 - Deep Dive", options: { level: 2 } },
{ type: "heading", text: "Section 2.2 - Examples", options: { level: 2 } },
{ type: "pageBreak" },
{ type: "heading", text: "Appendix A: Reference", options: { level: 1 } }
]
});
export default doc;
Annotations
Highlight, sticky note, free text, URI link, and page-to-page link annotations.
import { createDocument, hex } from "@barrierbreak/a11ydocs-pdf";
const doc = createDocument({
language: "en-US",
info: {
title: "PDF Annotations Demo",
author: "a11ydocs"
}
});
doc.renderTemplate({
info: { title: "PDF Annotations Demo", author: "a11ydocs" },
page: { size: "A4" },
blocks: [
{
type: "heading",
text: "PDF Annotations",
options: { level: 1, marginBottom: 6 }
},
{
type: "paragraph",
text: "This document demonstrates PDF annotation types. The document layout uses renderTemplate, while annotations are placed with the low-level page API for precise positioning.",
options: { fontSize: 11, marginBottom: 14 }
},
{
type: "paragraph",
text: "Annotations below include highlight, sticky note, free text, URI link, and page-to-page link.",
options: { fontSize: 11 }
}
]
});
const page = doc.addPage({ size: "A4" });
page.text("PDF Annotations", { x: 72, y: 800, fontSize: 20 });
// Highlight annotation
page.text("This sentence has a highlight annotation on it.", { x: 72, y: 760, fontSize: 12 });
page.highlight({
x: 72,
y: 760,
width: 350,
height: 14,
color: hex("#ffeb3b")
});
// Note annotation (sticky note)
page.text("Click the note icon to read the comment.", { x: 72, y: 720, fontSize: 12 });
page.note({
x: 72,
y: 720,
width: 20,
height: 20,
contents: "This is a sticky note annotation with reviewer comments.",
open: true
});
// FreeText annotation
page.freeText({
x: 300,
y: 680,
width: 200,
height: 60,
text: "Approved by reviewer on May 8, 2026",
fontSize: 10
});
// URI link
page.text("Visit our website for documentation", { x: 72, y: 640, fontSize: 12 });
page.link("https://example.com/docs", {
x: 72,
y: 640,
width: 220,
height: 14
});
// Internal page link
const page2 = doc.addPage({ size: "A4" });
page2.text("You arrived via the 'Go to Page 2' link on Page 1.", {
x: 72,
y: 800,
fontSize: 14
});
page.text("Go to Page 2", { x: 72, y: 610, fontSize: 12 });
page.linkToPage(page2, {
x: 72,
y: 610,
width: 80,
height: 14
});
export default doc;
Form fields
Interactive AcroForm fields — text, combo/list choice, checkbox, push button,
and signature — laid out with renderTemplate() so the labels and fields flow
down the page without overlapping (the field blocks omit x/y).
import { createDocument, hex } from "@barrierbreak/a11ydocs-pdf";
const doc = createDocument({
language: "en-US",
info: {
title: "Registration Form",
author: "a11ydocs"
}
});
// renderTemplate auto-flows each block down the page, so the labels and form
// fields stack without overlapping. Field blocks (textField, choiceField,
// checkBox, pushButton, signatureField) omit x/y - the template positions them.
//
// Each field sets a `description`, emitted as /TU - the field's accessible name
// (read by screen readers, and required by the "form fields have a description"
// accessibility check). renderTemplate tags the field widgets into the
// structure tree automatically, so no per-field tag is needed.
doc.renderTemplate({
info: { title: "Registration Form", author: "a11ydocs" },
page: { size: "A4" },
pageNumber: { region: "footer", align: "center" },
blocks: [
{
type: "heading",
text: "Registration Form",
options: { level: 1, color: hex("#1c2f6b"), marginBottom: 8 }
},
{
type: "paragraph",
text: "Please fill out the following form to register for the event. All fields marked with an asterisk (*) are required.",
options: { fontSize: 11, marginBottom: 14 }
},
{
type: "paragraph",
text: "Full Name *",
options: { fontSize: 10, marginBottom: 4 }
},
{
type: "textField",
name: "name",
options: { width: 300, height: 24, value: "John Doe", fontSize: 11, required: true, description: "Full name" }
},
{
type: "paragraph",
text: "Email Address *",
options: { fontSize: 10, marginBottom: 4, marginTop: 10 }
},
{
type: "textField",
name: "email",
options: { width: 300, height: 24, fontSize: 11, required: true, description: "Email address" }
},
{
type: "paragraph",
text: "Country",
options: { fontSize: 10, marginBottom: 4, marginTop: 10 }
},
{
type: "choiceField",
name: "country",
options: {
width: 250,
height: 24,
options: ["United States", "Canada", "United Kingdom", "Germany", "Japan", "Australia"],
value: "United States",
mode: "combo",
fontSize: 10,
description: "Country"
}
},
{
type: "paragraph",
text: "Skills (list box)",
options: { fontSize: 10, marginBottom: 4, marginTop: 10 }
},
{
type: "choiceField",
name: "skills",
options: {
width: 250,
height: 60,
options: ["TypeScript", "JavaScript", "Python", "Rust", "Go"],
mode: "list",
fontSize: 10,
description: "Skills"
}
},
{
type: "checkBox",
name: "subscribe",
options: { width: 16, height: 16, checked: true, description: "Subscribe to newsletter" }
},
{
type: "paragraph",
text: "Subscribe to newsletter",
options: { fontSize: 10, marginTop: 8, marginBottom: 16 }
},
{
type: "pushButton",
name: "submit",
options: { width: 140, height: 32, label: "Submit", fontSize: 12, color: hex("#ffffff"), description: "Submit form" }
},
{
type: "paragraph",
text: "Signature *",
options: { fontSize: 10, marginTop: 16, marginBottom: 4 }
},
{
type: "signatureField",
name: "signature",
options: { width: 300, height: 60, required: true, description: "Signature" }
}
]
});
export default doc;
Browser AES encryption
AES-256 encrypt a document entirely client-side — no Node runtime. The library
uses a built-in AES/SHA-2 fallback when node:crypto is unavailable, so the
output opens with the same password in any reader. See
Encryption and Signatures → Browser support.
import { createDocument, rgb } from "@barrierbreak/a11ydocs-pdf";
// AES encryption runs entirely in the browser — no server round-trip. When
// node:crypto is unavailable the library uses a built-in, zero-dependency
// AES/SHA-2 implementation whose output matches Node byte-for-byte, so the
// password works in any reader.
//
// This document is encrypted as the playground serializes it. Open the
// downloaded PDF and you will be prompted for the password below.
const doc = createDocument({
title: "Encrypted in the Browser",
language: "en-US",
encryption: {
algorithm: "aes-256-r6", // PDF 2.0 hardened key derivation (recommended)
userPassword: "reader", // password required to open
ownerPassword: "owner-secret",
permissions: {
printing: true,
copying: false,
modifying: false
}
}
});
doc.renderTemplate({
page: { size: "A4", margin: 56 },
blocks: [
{
type: "heading",
text: "Confidential",
options: { level: 1, fontSize: 28, color: rgb(0.6, 0.1, 0.1) }
},
{
type: "paragraph",
text: "This PDF was AES-256 encrypted client-side.",
options: { fontSize: 12 }
},
{
type: "paragraph",
text: "Open password: reader",
options: { fontSize: 12 }
}
]
});
export default doc;