PDF/UA Accessibility
Generate universally accessible PDFs that conform to ISO 14289-1 (PDF/UA-1) using semantic structure tags, alternative text, and automatic tagging.
Enabling PDF/UA-1
Set the conformance level in DocumentSettings and provide the required Title and Language in DocumentMetadata. PDF/UA-1 validators reject documents missing either field.
Document.Create(doc =>
{
doc.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(50);
page.Content().Column(col =>
{
col.Item().SemanticHeader1().Text("Accessible Report").FontSize(24).Bold();
col.Item().SemanticParagraph().Text("This document conforms to PDF/UA-1.");
});
});
})
.WithMetadata(new DocumentMetadata
{
Title = "Accessible Report",
Language = "en-US"
})
.WithSettings(new DocumentSettings
{
PdfUaConformance = PdfUaConformance.PdfUA_1
})
.GeneratePdf("accessible-report.pdf");
Required metadata. PDF/UA-1 (§7.1) mandates that the document catalogue contains a /Lang entry and /MarkInfo << /Marked true >>, and that /ViewerPreferences includes /DisplayDocTitle true. FolioPDF sets all three automatically when PdfUaConformance is PdfUA_1 — you only need to supply the Title and Language values.
Automatic vs Manual Tagging
FolioPDF supports two tagging modes controlled by DocumentSettings.AutoTagDocument:
| Mode | Setting | Behaviour |
|---|---|---|
| Automatic (default) | AutoTagDocument = true |
The layout engine walks the element tree and wraps content elements in structure tags: text blocks become <P>, images become <Figure>, tables become <Table>. Headings are not auto-detected — use SemanticHeader1–SemanticHeader6 explicitly. |
| Manual | AutoTagDocument = false |
No automatic tags are inserted. You must call Semantic* extension methods on every piece of content. Useful when you need full control over the tag tree. |
Explicit Semantic* calls always take precedence over auto-tagging. For example, calling .SemanticHeader1() on a text block prevents the auto-tagger from wrapping it in a <P> tag.
// Auto-tagging on (default) — headings still need explicit tags
doc.Page(page =>
{
page.Content().Column(col =>
{
// Explicit H1 tag — takes precedence over auto-tagger
col.Item().SemanticHeader1().Text("Chapter 1").FontSize(20).Bold();
// Auto-tagged as <P> — no explicit tag needed
col.Item().Text("This paragraph is tagged automatically.");
});
});
Headers and footers. Auto-tagging skips header and footer regions because they typically contain page chrome (page numbers, dates, confidentiality notices) that PDF/UA classifies as Artifacts. Use SemanticIgnore() or SemanticArtifact for decorative header/footer content if you tag them manually.
Semantic Tags Reference
FolioPDF exposes 42 semantic tag extension methods on IContainer. Each maps to a standard PDF structure element type.
Headings
| Method | PDF Tag | Description |
|---|---|---|
.SemanticHeader1() | H1 | Level 1 heading (document title, main heading) |
.SemanticHeader2() | H2 | Level 2 heading (section heading) |
.SemanticHeader3() | H3 | Level 3 heading (subsection heading) |
.SemanticHeader4() | H4 | Level 4 heading |
.SemanticHeader5() | H5 | Level 5 heading |
.SemanticHeader6() | H6 | Level 6 heading (deepest nesting) |
Text & Block Content
| Method | PDF Tag | Description |
|---|---|---|
.SemanticParagraph() | P | A paragraph of text |
.SemanticSpan(alt?) | Span | Generic inline text span; optional alternative text |
.SemanticBlockQuotation() | BlockQuote | A block of quoted text (one or more paragraphs) |
.SemanticQuote() | Quote | Inline quotation |
.SemanticCode() | Code | Fragment of computer code |
Lists
| Method | PDF Tag | Description |
|---|---|---|
.SemanticList() | L | Container for list items |
.SemanticListItem() | LI | Individual list item |
.SemanticListLabel() | Lbl | Label of a list item (bullet, number, letter) |
.SemanticListItemBody() | LBody | Body content of a list item |
Tables
| Method | PDF Tag | Description |
|---|---|---|
.SemanticTable() | Table | Table container |
.SemanticTableRow() | TR | Table row |
.SemanticTableHeaderCell() | TH | Table header cell |
.SemanticTableCell() | TD | Table data cell |
.SemanticTableHeaderGroup() | THead | Group of header rows |
.SemanticTableBodyGroup() | TBody | Group of body rows |
.SemanticTableFooterGroup() | TFoot | Group of footer rows |
Figures & Illustrations
| Method | PDF Tag | Description |
|---|---|---|
.SemanticFigure(alt) | Figure | Chart, diagram, or photograph — requires alternative text |
.SemanticImage(alt) | Figure | Alias for SemanticFigure |
.SemanticCaption() | Caption | Caption or description for a table or figure |
.SemanticFormula(alt) | Formula | Mathematical formula — requires alternative text |
Navigation
| Method | PDF Tag | Description |
|---|---|---|
.SemanticTableOfContents() | TOC | Table of contents container |
.SemanticTableOfContentsItem() | TOCI | Individual entry within a table of contents |
Structure & Grouping
| Method | PDF Tag | Description |
|---|---|---|
.SemanticArticle() | Art | Self-contained body of text (article, blog post, news story) |
.SemanticSection() | Sect | Group of related content, typically with a heading |
.SemanticDivision() | Div | Generic block-level container (like HTML <div>) |
.SemanticIndex() | Index | Document index section |
.SemanticLanguage(lang) | NonStruct | Specifies the natural language of content (e.g. "fr-FR") |
Links & References
| Method | PDF Tag | Description |
|---|---|---|
.SemanticLink(alt) | Link | Hyperlink — requires alternative text describing the destination |
.SemanticReference() | Reference | Cross-reference or citation |
.SemanticNote() | Note | Footnote, endnote, or annotation |
.SemanticBibliographyEntry() | BibEntry | Bibliography entry |
CJK Annotations
| Method | PDF Tag | Description |
|---|---|---|
.SemanticRuby() | Ruby | Ruby annotation container (CJK pronunciation guide) |
.SemanticRubyBase() | RB | Base text in a Ruby annotation |
.SemanticRubyAnnotation() | RT | Annotation text in a Ruby annotation |
.SemanticWarichu() | Warichu | Warichu inline annotation (CJK) |
Artifacts
| Method | PDF Tag | Description |
|---|---|---|
.SemanticIgnore() | Artifact | Excludes content from the structure tree (decorative borders, backgrounds, page chrome) |
Complete Example: Accessible Report
This example demonstrates a fully tagged document with headings, paragraphs, a data table, an image with alt text, and a bulleted list.
using FolioPDF;
using FolioPDF.Fluent;
using FolioPDF.Helpers;
using FolioPDF.Infrastructure;
Document.Create(doc =>
{
doc.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(50);
page.Header().SemanticIgnore().AlignCenter()
.Text("Company Confidential").FontSize(9).FontColor(Colors.Grey.Medium);
page.Content().Column(col =>
{
col.Spacing(12);
// Document title
col.Item().SemanticHeader1()
.Text("Q1 2026 Financial Summary").FontSize(24).Bold();
// Introduction section
col.Item().SemanticSection().Column(section =>
{
section.Spacing(8);
section.Item().SemanticHeader2()
.Text("Overview").FontSize(18).SemiBold();
section.Item().SemanticParagraph()
.Text("Revenue grew 12% year-over-year, driven by " +
"strong performance in the enterprise segment.");
});
// Figure with alt text
col.Item().SemanticFigure("Bar chart showing quarterly revenue from Q1 2025 to Q1 2026")
.Image(File.ReadAllBytes("revenue-chart.png"));
col.Item().SemanticCaption()
.Text("Figure 1: Quarterly Revenue Trend").Italic().FontSize(10);
// Data table
col.Item().SemanticTable().Table(table =>
{
table.ColumnsDefinition(c =>
{
c.RelativeColumn(2);
c.RelativeColumn();
c.RelativeColumn();
c.RelativeColumn();
});
// Header row
table.Cell().SemanticTableHeaderCell()
.Text("Region").Bold();
table.Cell().SemanticTableHeaderCell()
.Text("Q1 2025").Bold();
table.Cell().SemanticTableHeaderCell()
.Text("Q1 2026").Bold();
table.Cell().SemanticTableHeaderCell()
.Text("Growth").Bold();
// Data rows
table.Cell().SemanticTableCell().Text("North America");
table.Cell().SemanticTableCell().Text("$4.2M");
table.Cell().SemanticTableCell().Text("$4.8M");
table.Cell().SemanticTableCell().Text("+14%");
table.Cell().SemanticTableCell().Text("Europe");
table.Cell().SemanticTableCell().Text("$2.1M");
table.Cell().SemanticTableCell().Text("$2.3M");
table.Cell().SemanticTableCell().Text("+10%");
});
// Bulleted list
col.Item().SemanticHeader2()
.Text("Key Highlights").FontSize(18).SemiBold();
col.Item().SemanticList().Column(list =>
{
list.Spacing(4);
foreach (var highlight in new[]
{
"Enterprise segment grew 18% QoQ",
"Customer retention rate at 96%",
"Launched in 3 new markets"
})
{
list.Item().SemanticListItem().Row(row =>
{
row.AutoItem().SemanticListLabel()
.Text("\u2022 ").FontSize(12);
row.RelativeItem().SemanticListItemBody()
.Text(highlight);
});
}
});
});
page.Footer().SemanticIgnore().AlignCenter().Text(t =>
{
t.Span("Page ");
t.CurrentPageNumber();
t.Span(" of ");
t.TotalPages();
});
});
})
.WithMetadata(new DocumentMetadata
{
Title = "Q1 2026 Financial Summary",
Author = "Finance Team",
Language = "en-US"
})
.WithSettings(new DocumentSettings
{
PdfUaConformance = PdfUaConformance.PdfUA_1
})
.GeneratePdf("q1-report-accessible.pdf");
Combining PDF/UA with PDF/A
PDF/UA-1 can be combined with PDF/A-2a or PDF/A-3a for documents that must be both accessible and archival-grade. Set both conformance levels:
.WithSettings(new DocumentSettings
{
PdfUaConformance = PdfUaConformance.PdfUA_1,
PdfAConformance = PdfAConformance.PdfA_2A
})
PDF/A-2a requires full tagging. The "a" (accessibility) sub-levels of PDF/A require all content to be tagged — the same requirement as PDF/UA-1. When you enable both, FolioPDF satisfies both standards with a single structure tree.
Validation
Validate your PDF/UA-1 output with veraPDF (the industry-standard open-source validator) or Adobe Acrobat's built-in accessibility checker:
# veraPDF CLI validation
verapdf --format mrr --flavour ua1 q1-report-accessible.pdf
Common validation failures and their fixes:
| Failure | Cause | Fix |
|---|---|---|
Missing /Lang |
DocumentMetadata.Language not set |
Set Language = "en-US" (or your document's language) |
Missing /DisplayDocTitle |
DocumentMetadata.Title not set |
Set Title to a non-empty string |
| Content not tagged or marked as Artifact | Content in header/footer not marked | Wrap header/footer content with .SemanticIgnore() |
| Figure without alternative text | Image missing alt parameter |
Use .SemanticFigure("description") or .SemanticImage("description") |
| Heading level skipped | H1 followed by H3 (missing H2) | Ensure heading levels are sequential and never skip a level |