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 = trueThe 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.
ManualAutoTagDocument = falseNo 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 /LangDocumentMetadata.Language not setSet Language = "en-US" (or your document's language)
Missing /DisplayDocTitleDocumentMetadata.Title not setSet Title to a non-empty string
Content not tagged or marked as ArtifactContent in header/footer not markedWrap header/footer content with .SemanticIgnore()
Figure without alternative textImage missing alt parameterUse .SemanticFigure("description") or .SemanticImage("description")
Heading level skippedH1 followed by H3 (missing H2)Ensure heading levels are sequential and never skip a level