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:

ModeSettingBehaviour
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 SemanticHeader1SemanticHeader6 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

MethodPDF TagDescription
.SemanticHeader1()H1Level 1 heading (document title, main heading)
.SemanticHeader2()H2Level 2 heading (section heading)
.SemanticHeader3()H3Level 3 heading (subsection heading)
.SemanticHeader4()H4Level 4 heading
.SemanticHeader5()H5Level 5 heading
.SemanticHeader6()H6Level 6 heading (deepest nesting)

Text & Block Content

MethodPDF TagDescription
.SemanticParagraph()PA paragraph of text
.SemanticSpan(alt?)SpanGeneric inline text span; optional alternative text
.SemanticBlockQuotation()BlockQuoteA block of quoted text (one or more paragraphs)
.SemanticQuote()QuoteInline quotation
.SemanticCode()CodeFragment of computer code

Lists

MethodPDF TagDescription
.SemanticList()LContainer for list items
.SemanticListItem()LIIndividual list item
.SemanticListLabel()LblLabel of a list item (bullet, number, letter)
.SemanticListItemBody()LBodyBody content of a list item

Tables

MethodPDF TagDescription
.SemanticTable()TableTable container
.SemanticTableRow()TRTable row
.SemanticTableHeaderCell()THTable header cell
.SemanticTableCell()TDTable data cell
.SemanticTableHeaderGroup()THeadGroup of header rows
.SemanticTableBodyGroup()TBodyGroup of body rows
.SemanticTableFooterGroup()TFootGroup of footer rows

Figures & Illustrations

MethodPDF TagDescription
.SemanticFigure(alt)FigureChart, diagram, or photograph — requires alternative text
.SemanticImage(alt)FigureAlias for SemanticFigure
.SemanticCaption()CaptionCaption or description for a table or figure
.SemanticFormula(alt)FormulaMathematical formula — requires alternative text

Navigation

MethodPDF TagDescription
.SemanticTableOfContents()TOCTable of contents container
.SemanticTableOfContentsItem()TOCIIndividual entry within a table of contents

Structure & Grouping

MethodPDF TagDescription
.SemanticArticle()ArtSelf-contained body of text (article, blog post, news story)
.SemanticSection()SectGroup of related content, typically with a heading
.SemanticDivision()DivGeneric block-level container (like HTML <div>)
.SemanticIndex()IndexDocument index section
.SemanticLanguage(lang)NonStructSpecifies the natural language of content (e.g. "fr-FR")

Links & References

MethodPDF TagDescription
.SemanticLink(alt)LinkHyperlink — requires alternative text describing the destination
.SemanticReference()ReferenceCross-reference or citation
.SemanticNote()NoteFootnote, endnote, or annotation
.SemanticBibliographyEntry()BibEntryBibliography entry

CJK Annotations

MethodPDF TagDescription
.SemanticRuby()RubyRuby annotation container (CJK pronunciation guide)
.SemanticRubyBase()RBBase text in a Ruby annotation
.SemanticRubyAnnotation()RTAnnotation text in a Ruby annotation
.SemanticWarichu()WarichuWarichu inline annotation (CJK)

Artifacts

MethodPDF TagDescription
.SemanticIgnore()ArtifactExcludes 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:

FailureCauseFix
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