Appearance
Examples
These are the starter snippets bundled with the live 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 (or a local script) 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 & 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.
ts
import { createDocument, rgb, mm } from "@barrierbreak/a11ydocs-pdf";
// 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" },
{ text: "middle", verticalAlign: "middle" },
{ text: "bottom", verticalAlign: "bottom" }
]],
{ x: mm(20), y: mm(120), width: mm(170), columnWidths: [mm(85), mm(28), mm(28), mm(29)], fontSize: 11, cellPadding: 4 }
);
// 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;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.
ts
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, then register each family. Multiple faces under
// one 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)
]);
doc.registerFontFamily("Inter", { regular: inter400, bold: inter700 });
doc.registerFontFamily("Geist", { regular: geist400, bold: geist700 });
doc.registerFontFamily("Instrument Serif", { regular: instReg, italic: instItal });
doc.registerFontFamily("Bitcount Grid Single", { regular: bitcount });
const specimen = "The quick brown fox jumps 0123456789";
const page = doc.addPage({ size: "A4" });
page.text("Google Fonts, embedded", { x: 56, y: 790, font: "Geist", bold: true, fontSize: 24 });
page.text("Fetched as .ttf and embedded with registerFontFamily().", { x: 56, y: 764, font: "Geist", fontSize: 11, color: rgb(0.4, 0.4, 0.45) });
let y = 720;
const line = (label: string, font: string, opts: Record<string, unknown> = {}) => {
page.text(label, { x: 56, y, font, fontSize: 9, color: rgb(0.5, 0.5, 0.55) });
page.text(specimen, { x: 56, y: y - 22, font, fontSize: 20, ...opts });
y -= 64;
};
line("Inter", "Inter");
line("Inter Bold", "Inter", { bold: true });
line("Geist", "Geist");
line("Instrument Serif", "Instrument Serif");
line("Instrument Serif Italic", "Instrument Serif", { italic: true });
line("Bitcount Grid Single", "Bitcount Grid Single");
export default doc;Stylesheets & tables
Define reusable styles once and apply them by block type or class, then render a header-row table — all through renderTemplate().
ts
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: "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 }
}
]
});
export default doc;Single page (low level)
The lowest-level API: add a page and draw text at absolute coordinates.
ts
import { createDocument, rgb } from "@barrierbreak/a11ydocs-pdf";
const doc = createDocument({
title: "Single Page Demo",
language: "en-US",
xmpMetadata: {
creator: "a11ydocs playground"
}
});
const page = doc.addPage({ size: "A4" });
page.text("Hello from a11ydocs", { x: 56, y: 760, fontSize: 28, color: rgb(0.11, 0.22, 0.45) });
page.text("Drawn at absolute coordinates on a single page.", { x: 56, y: 730, fontSize: 12 });
export default doc;Hello world (template)
The renderTemplate() starting point, with document info and XMP metadata.
ts
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.
ts
import { createDocument, rgb } from "@barrierbreak/a11ydocs-pdf";
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 }
}
]
});
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().
ts
/**
* 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.
*
* npm run build && node dist/examples/json-to-pdf.js
*/
import { writeFile } from "node:fs/promises";
import { renderDocumentJson, type PdfDocumentJson } from "../src/index.js";
/** 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.
ts
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.
ts
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 & transforms
Low-level vector graphics: saveState/restoreState, translate, scale, rotate, arbitrary transform, alpha transparency, and blend modes.
ts
import { createDocument, hex } from "@barrierbreak/a11ydocs-pdf";
const doc = createDocument({
info: {
title: "Graphics State Demo",
author: "a11ydocs"
}
});
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 across several pages.
ts
import { createDocument, hex } from "@barrierbreak/a11ydocs-pdf";
const doc = createDocument({
info: {
title: "PDF Outlines (Bookmarks) Demo",
author: "a11ydocs"
}
});
// Page 1: Chapter 1
const page1 = doc.addPage({ size: "A4" });
page1.text("Chapter 1: Introduction", { x: 72, y: 780, fontSize: 20, color: hex("#1a1a2e") });
page1.text("This is the first chapter covering basic concepts.", {
x: 72, y: 740, fontSize: 12
});
// Page 2: Chapter 2
const page2 = doc.addPage({ size: "A4" });
page2.text("Chapter 2: Advanced Topics", { x: 72, y: 780, fontSize: 20, color: hex("#1a1a2e") });
page2.text("Section 2.1 - Deep Dive", { x: 72, y: 740, fontSize: 14 });
page2.text("Section 2.2 - Examples", { x: 72, y: 710, fontSize: 14 });
// Page 3: Appendices
const page3 = doc.addPage({ size: "A4" });
page3.text("Appendix A: Reference", { x: 72, y: 780, fontSize: 20, color: hex("#1a1a2e") });
// Build outline tree manually (viewable in a reader's bookmarks pane).
const ch1Outline = doc.addOutline("Chapter 1: Introduction", page1);
ch1Outline.addChild("Overview", page1);
const ch2Outline = doc.addOutline("Chapter 2: Advanced Topics", page2);
ch2Outline.addChild("Section 2.1 - Deep Dive", page2);
ch2Outline.addChild("Section 2.2 - Examples", page2);
doc.addOutline("Appendix A: Reference", page3);
export default doc;Annotations
Highlight, sticky note, free text, URI link, and page-to-page link annotations.
ts
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).
ts
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;Incremental editing
Re-open generated bytes with editDocument() and add, insert, and remove pages, update a field value, and patch document info — all as an incremental update.
ts
import { createDocument, editDocument } from "@barrierbreak/a11ydocs-pdf";
// Step 1: Create a base document with form fields.
const baseDoc = createDocument({
info: { title: "Base Document", author: "a11ydocs" }
});
const page = baseDoc.addPage({ size: "A4" });
page.text("Original content - this document will be edited incrementally.", {
x: 72, y: 780, fontSize: 14
});
page.textField("name", { x: 72, y: 730, width: 300, height: 22, value: "Original", fontSize: 11 });
page.checkBox("agree", { x: 72, y: 690, width: 16, height: 16 });
const baseBytes = baseDoc.toUint8Array();
// Step 2: Re-open the saved bytes and edit them as an incremental update.
const editable = editDocument(baseBytes);
// Add a new page.
const newPage = editable.addPage({ size: "A4" });
newPage.text("Added via incremental editing", { x: 72, y: 780, fontSize: 16 });
newPage.text("This page did not exist in the original document.", { x: 72, y: 750, fontSize: 12 });
// Insert a page at index 1.
const insertedPage = editable.insertPage(1, { size: "A4" });
insertedPage.text("Inserted between pages 1 and 2", { x: 72, y: 780, fontSize: 16 });
insertedPage.text("Incremental editing preserves the original content.", { x: 72, y: 750, fontSize: 12 });
// Update a form field value.
editable.setFieldValue("name", "Updated via incremental edit");
// Update document info.
editable.updateInfo({ title: "Edited Document", subject: "Demonstrates incremental editing" });
// Remove the last page.
editable.removePage(editable.getPageCount() - 1);
// The edited document is what we render.
export default editable;