§HOW-TO GUIDE · C# · .NET 8 & 10

Accessible PDF/UA from HTML in C# (tagged PDF)

Set AccessibilityStandard on the converter, give the document a title and a language, and convert well-structured HTML: the PDF is tagged and declares PDF/UA. Accessible documents matter more since the European Accessibility Act applies in the EU from June 2025.

Compiled and run: Linux ARM64 · Linux x64 · macOS ARM64 · Windows x64

Validated by veraPDF: PDF/UA-1 · PDF/UA-2 · PDF/A-4 · PDF/A-3A

Video walkthrough · 3:33 · Watch on YouTube · Short · 0:55
§STEP BY STEP

Build it step by step.

  1. 1Install the NuGet packages

    SelectPdf.Universal is the library. The native package carries the Chromium engine for one platform: add the one for the platform you run on.

    terminal
    dotnet add package SelectPdf.Universal
    dotnet add package SelectPdf.Universal.Native.win-x64
    terminal
    dotnet add package SelectPdf.Universal
    dotnet add package SelectPdf.Universal.Native.linux-x64
    terminal
    dotnet add package SelectPdf.Universal
    dotnet add package SelectPdf.Universal.Native.linux-arm64
    terminal
    dotnet add package SelectPdf.Universal
    dotnet add package SelectPdf.Universal.Native.osx-arm64
  2. 2Create a console app

    Any .NET 8 or .NET 10 project works. To follow along, create a console app, run the install commands above inside its folder, then replace Program.cs with the code below.

    terminal
    dotnet new console -n PdfSample
    cd PdfSample
  3. 3Write accessible HTML

    Program.cs · lines 3–21
    // HTML written for accessibility: a language, headings in order, alt text, a real table
    string html = """
        <html lang="en">
        <head><title>Quarterly report</title></head>
        <body>
          <h1>Quarterly report</h1>
          <p>Revenue grew in every region this quarter.</p>
          <img src="https://selectpdf.com/img/selectpdf-logo-h128.png" alt="SelectPdf logo" height="40">
          <h2>Revenue by region</h2>
          <table>
            <thead><tr><th scope="col">Region</th><th scope="col">Revenue</th></tr></thead>
            <tbody>
              <tr><td>Europe</td><td>1,200,000</td></tr>
              <tr><td>Americas</td><td>900,000</td></tr>
            </tbody>
          </table>
        </body>
        </html>
        """;

    The PDF's structure tree is built from the HTML, so the HTML decides how accessible the PDF is: lang on the html element, a title, headings in order (h1, then h2), alt text on the image, and a table with thead and th scope="col" header cells.

  4. 4Declare PDF/UA-1

    Program.cs · lines 23–26
    HtmlToPdf converter = new HtmlToPdf();
    
    // declare PDF/UA-1: tags the document and writes the PDF/UA identifier
    converter.Options.AccessibilityStandard = PdfUAStandard.UA1;

    AccessibilityStandard = PdfUAStandard.UA1 tags the document and writes the PDF/UA identifier. A document that is only tagged (Options.Tagged) is accessible to a screen reader but makes no formal claim; a compliance requirement usually means the declared standard.

  5. 5Set the title and language

    Program.cs · lines 28–30
    // PDF/UA requires a document title and a language
    converter.Options.Title = "Quarterly report";
    converter.Options.Language = "en-US";

    PDF/UA requires both: Title is shown instead of the file name, and Language tells a screen reader how to pronounce the text.

  6. 6Convert and save

    Program.cs · lines 32–34
    PdfDocument doc = converter.ConvertHtmlString(html);
    doc.Save("report-ua1.pdf");
    doc.Close();

    ConvertHtmlString takes the structure from the rendering engine's accessible output: headings, paragraphs, the figure with its alternate text, the table and its header cells, in reading order.

  7. 7Run it

    terminal
    dotnet run

    The program writes report-ua1.pdf to the working folder. Without a license key, every page carries a trial watermark; set GlobalProperties.LicenseKey before the first conversion to remove it.

§COMPLETE PROGRAM

The complete Program.cs.

This is the exact file our samples pipeline compiled and ran on Linux ARM64 and Linux x64 and macOS ARM64 and Windows x64 to produce the output on this page.

Program.cs
using SelectPdf.Universal;

// HTML written for accessibility: a language, headings in order, alt text, a real table
string html = """
    <html lang="en">
    <head><title>Quarterly report</title></head>
    <body>
      <h1>Quarterly report</h1>
      <p>Revenue grew in every region this quarter.</p>
      <img src="https://selectpdf.com/img/selectpdf-logo-h128.png" alt="SelectPdf logo" height="40">
      <h2>Revenue by region</h2>
      <table>
        <thead><tr><th scope="col">Region</th><th scope="col">Revenue</th></tr></thead>
        <tbody>
          <tr><td>Europe</td><td>1,200,000</td></tr>
          <tr><td>Americas</td><td>900,000</td></tr>
        </tbody>
      </table>
    </body>
    </html>
    """;

HtmlToPdf converter = new HtmlToPdf();

// declare PDF/UA-1: tags the document and writes the PDF/UA identifier
converter.Options.AccessibilityStandard = PdfUAStandard.UA1;

// PDF/UA requires a document title and a language
converter.Options.Title = "Quarterly report";
converter.Options.Language = "en-US";

PdfDocument doc = converter.ConvertHtmlString(html);
doc.Save("report-ua1.pdf");
doc.Close();

Open the project on GitHub → this program and the variations below, ready to dotnet run

§OUTPUT

The PDF it produces.

First page of report-ua1.pdf, the PDF this sample produces
report-ua1.pdf, page 1

Download report-ua1.pdf ↓ the file this sample produced, unedited

§VARIATIONS

Variations you will need next.

Each variation is a separate file in the same project, compiled and run with the program above.

PDF/UA-2 and PDF/A-4 in one file

PdfUAStandard.UA2 is the newer standard (ISO 14289-2), defined on PDF 2.0. For a document that is both accessible and archival, combine it with PdfStandard.PdfA4: PDF/A-4 is the archival level built on PDF 2.0, so it is the one that goes with PDF/UA-2.

PdfUA2.cs
using SelectPdf.Universal;

static class PdfUA2
{
    public static void Run()
    {
        string html = """
            <html lang="en">
            <head><title>Annual report</title></head>
            <body>
              <h1>Annual report</h1>
              <p>Accessible and archived: PDF/UA-2 and PDF/A-4 in one file.</p>
            </body>
            </html>
            """;

        HtmlToPdf converter = new HtmlToPdf();

        // PDF/UA-2 is defined on PDF 2.0, so the only archival level it combines with is PDF/A-4
        converter.Options.AccessibilityStandard = PdfUAStandard.UA2;
        converter.Options.PdfStandard = PdfStandard.PdfA4;
        converter.Options.Title = "Annual report";
        converter.Options.Language = "en-US";

        PdfDocument doc = converter.ConvertHtmlString(html);
        doc.Save("report-ua2-pdfa4.pdf");
        doc.Close();
    }
}

An archived, accessible invoice (PDF/A-3A and PDF/UA-1)

Archiving and accessibility are separate standards, so PdfStandard and AccessibilityStandard combine. PDF/A-3A is the accessible level of PDF/A-3, and pairs with PDF/UA-1.

ArchivedInvoice.cs
using SelectPdf.Universal;

static class ArchivedInvoice
{
    public static void Run()
    {
        string html = """
            <html lang="en">
            <head><title>Invoice 2026-0042</title></head>
            <body>
              <h1>Invoice 2026-0042</h1>
              <p>Billed to Contoso Ltd.</p>
            </body>
            </html>
            """;

        HtmlToPdf converter = new HtmlToPdf();

        // PDF/A-3A (archival, level A = accessible) together with PDF/UA-1
        converter.Options.PdfStandard = PdfStandard.PdfA3A;
        converter.Options.AccessibilityStandard = PdfUAStandard.UA1;
        converter.Options.Title = "Invoice 2026-0042";
        converter.Options.Language = "en-US";

        PdfDocument doc = converter.ConvertHtmlString(html);
        doc.Save("invoice-pdfa3a-ua1.pdf");
        doc.Close();
    }
}
§TROUBLESHOOTING

Common problems and fixes.

The validator reports missing alternate text

Every informative image needs an alt attribute in the HTML; the PDF's figure takes its alternate text from it.

Headings are out of order

The heading levels come straight from the HTML. Go from h1 to h2 to h3 without skipping levels, and use headings for structure, not for size.

Saving fails and names a page (documents built in code)

When you build a PDF with the creation API, set AccessibilityStandard before adding pages: content drawn before the document is tagged is never marked, so the save refuses a claim the file would not meet.

PDF/UA-2 with PDF/A-2 or PDF/A-3

PDF/UA-2 is defined on PDF 2.0, and PDF/A-2 and PDF/A-3 are not. Use PDF/A-4 with PDF/UA-2, or PDF/A-3A with PDF/UA-1.

§GOOD TO KNOW

Good to know.

  • AccessibilityStandard declares PDF/UA-1 or PDF/UA-2: it turns tagging on and writes the PDF/UA identifier that validators and assistive software look for. Source
  • Both standards require a document title and a language. Source
  • PDF/UA-2 is written as a PDF 2.0 document; internal links and outline entries also carry a structure destination, so a reader lands at the right place in the reading order. Source
  • The structure comes from the HTML: images need alt text, headings should be in order, and tables need real table markup. Source
  • Tagged PDF is part of the commercial library; it is not in the free Community Edition. Source
?FAQ

Questions, answered.

Short answers about this guide. The SelectPdf.Universal documentation covers every class and option.

Set converter.Options.AccessibilityStandard to PdfUAStandard.UA1 or UA2, set Options.Title and Options.Language, and convert the HTML. SelectPdf.Universal tags the PDF from the page's structure and declares the standard.
Yes. The files this guide produces pass veraPDF 1.30.2 with the PDF/UA-1, PDF/UA-2, PDF/A-4 and PDF/A-3A profiles. A validator checks structure and metadata; it cannot judge whether your alternate text and headings make sense, so review the content as well.
PDF/UA-1 is the established standard and pairs with PDF/A-1a, PDF/A-2 and PDF/A-3, such as PDF/A-3A. PDF/UA-2 is the newer ISO 14289-2 standard on PDF 2.0 and pairs with PDF/A-4. Choose what your requirement names; if it names none, PDF/UA-1 is the most widely checked.
Since June 2025 the Act requires many digital products and services sold in the EU to be accessible, documents included. Tagged PDF/UA is the standard for accessible PDF, and this guide produces it; check the Act's scope and exemptions for your own product.