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
Build it step by step.
-
1Install the NuGet packages
SelectPdf.Universalis the library. The native package carries the Chromium engine for one platform: add the one for the platform you run on.dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.win-x64
dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.linux-x64
dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.linux-arm64
dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.osx-arm64
-
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.cswith the code below.dotnet new console -n PdfSample cd PdfSample
-
3Write accessible HTML
// 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:
langon thehtmlelement, atitle, headings in order (h1, thenh2),alttext on the image, and a table withtheadandth scope="col"header cells. -
4Declare PDF/UA-1
HtmlToPdf converter = new HtmlToPdf(); // declare PDF/UA-1: tags the document and writes the PDF/UA identifier converter.Options.AccessibilityStandard = PdfUAStandard.UA1;
AccessibilityStandard = PdfUAStandard.UA1tags 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. -
5Set the title and language
// PDF/UA requires a document title and a language converter.Options.Title = "Quarterly report"; converter.Options.Language = "en-US";
PDF/UA requires both:
Titleis shown instead of the file name, andLanguagetells a screen reader how to pronounce the text. -
6Convert and save
PdfDocument doc = converter.ConvertHtmlString(html); doc.Save("report-ua1.pdf"); doc.Close();ConvertHtmlStringtakes 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. -
7Run it
dotnet run
The program writes
report-ua1.pdfto the working folder. Without a license key, every page carries a trial watermark; setGlobalProperties.LicenseKeybefore the first conversion to remove it.
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.
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
The PDF it produces.
Download report-ua1.pdf ↓ the file this sample produced, unedited
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.
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.
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();
}
}
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.
- 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
Questions, answered.
Short answers about this guide. The SelectPdf.Universal documentation covers every class and option.