---
title: "Create PDF/UA"
url: https://recursiveai.org/onepdf/cookbook/create-pdf-ua/
description: "Generate a tagged PDF in C# that is PDF/UA-1 and PDF/A-2a in one file, and see what OnePDF refuses when a title or alternate text is missing."
version: "1.11.0"
---

# Create PDF/UA

Generate a tagged PDF in C# that is PDF/UA-1 and PDF/A-2a in one file, and see what OnePDF refuses when a title or alternate text is missing.

```sh
dotnet add package RecursiveAI.OnePdf.Layout --version 1.11.0
```

```csharp
using OnePdf;
using OnePdf.Layout;

byte[] logo = File.ReadAllBytes("logo.png");

LayoutDocument summary = LayoutDocument.Compose(doc =>
{
    doc.Title("Harbor trial summary").Author("Port office").Language("en-US");
    doc.Page(page =>
    {
        page.Size(PageSize.Letter);
        page.Margin(54);
        page.Header().Text("Port office").FontSize(9);              // headers and footers are tagged as artifacts
        page.Content().Column(col =>
        {
            col.Spacing(10);
            col.Item().Width(48).Image(logo).AltText("Port office logo");
            col.Item().Heading(1).Text("Harbor trial summary").FontSize(22).Bold();
            col.Item().Text("Six crews took part. Catches by quay:");
            col.Item().Table(table =>
            {
                table.Columns(c => { c.Relative(); c.Constant(100); });
                table.CellPadding(4);
                table.CellBorder(0.5, Colors.Grey);
                table.HeaderRow(r => { r.Cell().Text("Quay").Bold(); r.Cell().AlignEnd().Text("Catch (kg)").Bold(); });
                table.Row(r => { r.Cell().Text("North"); r.Cell().AlignEnd().Text("1,204"); });
                table.Row(r => { r.Cell().Text("South"); r.Cell().AlignEnd().Text("987"); });
            });
            col.Item().Heading(2).Text("Next steps").FontSize(14).Bold();
            col.Item().List(list =>
            {
                list.Bulleted();
                list.Item().Text("Repeat the trial in spring.");
                list.Item().Hyperlink("https://example.com/harbor").Text("Read the full report online.");
            });
        });
    });
});

// PDF/UA-1 and PDF/A-2a in one file.
using (FileStream file = File.Create("summary-ua.pdf"))
{
    summary.Save(file, new PdfSaveOptions
    {
        PdfUA = new PdfUAOptions(),
        PdfA = new PdfAOptions { Part = PdfAPart.Part2, Conformance = PdfAConformance.A },
    });
}

// What OnePDF cannot decide for you is refused, with every reason listed.
LayoutDocument untitled = LayoutDocument.Compose(doc =>
{
    doc.Language("en-US");                                          // no title
    doc.Page(page => page.Content().Column(col =>
    {
        col.Item().Width(48).Image(logo);                           // no alternate text
        col.Item().Heading(2).Text("Starts at level 2");            // heading levels must start at 1
    }));
});
try
{
    untitled.Save(new MemoryStream(), new PdfSaveOptions { PdfUA = new PdfUAOptions() });
}
catch (PdfUAException e)
{
    foreach (PdfUAViolation v in e.Violations)
        Console.WriteLine($"checkpoint {v.Checkpoint}: {v.Message}");
}
```

```text
checkpoint 06: The XMP packet has no dc:title: a PDF/UA file needs a title.
checkpoint 07: The catalog's /ViewerPreferences does not set /DisplayDocTitle true, so viewers would show the file name instead of the title.
checkpoint 13: A Figure structure element has neither alternate text (/Alt) nor replacement text (/ActualText).
checkpoint 14: The first heading is H2; the heading hierarchy has to start at H1.
```

`summary-ua.pdf` passes the veraPDF validator as PDF/UA-1 and as PDF/A-2a. The second document was refused and nothing was written.

## How it works

OnePdf.Layout tags everything it lays out: headings become `H1` to `H6`, tables `Table` with `TH` header cells, lists `L` with `LI` items, images `Figure` with your alternate text, links `Link`, and headers, footers, borders and backgrounds are marked as artifacts. A save with `PdfSaveOptions.PdfUA` then:

- **repairs the clerical parts:** the document language (from `PdfUAOptions.Language` when the document has none), showing the title instead of the file name, tab order, link descriptions and the XMP identification;
- **refuses what only you can provide**, listing every violation with its Matterhorn Protocol checkpoint: a title, alternate text for each image (or `Artifact()` for decoration), headings that start at level 1 without skipping levels, header cells in every table.

`PdfUAOptions { Part = PdfUAPart.Part2 }` writes PDF/UA-2 on PDF 2.0 instead. PDF/UA-2 can’t be combined with PDF/A-2 or PDF/A-3, which are PDF 1.7 formats.

## Good to know

- OnePDF never guesses alternate text, a title, a language or the role of content. Existing untagged files are refused. You can tag one with roles you supply through the tagging API: see [Tagging](https://recursiveai.org/onepdf/docs/tagging/).
- A clean save is not proof of conformance. PDF/UA also asks for things only a person can judge: a sensible reading order, meaningful alternate text, enough contrast. Check with a validator and a screen reader.
- `CheckPdfUA()` reports the violations of any document, including read-only ones, without changing it.
- The PDF/UA save checks the finished file, so the whole document is held in memory while it is written. Plain `Save(path)` streams page by page.

The full lists of repairs and checks are in [PDF/UA](https://recursiveai.org/onepdf/docs/pdfua/), and the tagging rules of the layout package in [Layout](https://recursiveai.org/onepdf/docs/layout/).

A longer walk-through with PDF/UA-2 and tagging an existing file: [PDF/UA in C#](https://recursiveai.org/onepdf/articles/pdf-ua-csharp/).
