Data Export & Print
Permalink to "Data Export & Print"Every data interface eventually lets data leave it — as a CSV, an Excel workbook, a PDF report, a printout or a paste into another tool. These routes are often treated as afterthoughts, built by whoever had time, and tested by nobody with assistive technology. Yet for many disabled users they are the main route. A screen reader user working through 3,000 rows will often export to Excel, where table navigation is mature and fast. A low-vision user may print a report at a large font size. A user with a cognitive disability may prefer a single PDF summary to an interactive dashboard. When exports are broken — headers missing, values as formatted strings, PDFs as images — those users lose not one feature but their workaround for everything else.
The failure mode this topic prevents is an export that looks fine to the developer who opened it once in Excel and is unusable to the people who depend on it: merged title rows above the headers, colour-only status, untagged PDFs, a silent export button that users press three times. The fixes are mostly about discipline — serialise from data, name controls precisely, announce long operations — rather than about new ARIA patterns.
This topic belongs to accessible data tables & grid systems.
WCAG criteria in scope
Permalink to "WCAG criteria in scope"| Criterion | Level | Relevance to export and print |
|---|---|---|
| 1.3.1 Info and Relationships | A | Headers identifiable in files, printouts and PDFs |
| 1.3.2 Meaningful Sequence | A | PDF tag order matches reading order |
| 1.4.1 Use of Color | A | Status as text; background colours vanish in print |
| 2.4.6 Headings and Labels | AA | Export controls name format and scope |
| 4.1.2 Name, Role, Value | A | Busy buttons and progress elements expose state |
| 4.1.3 Status Messages | AA | Export start, progress, completion and failure |
| EN 301 549 clause 10 | — | Non-web documents (exports, PDFs) in procurement |
Prerequisites
Permalink to "Prerequisites"- Semantic HTML table construction, especially formatting numbers and units in table cells — the same rules shape clean exports.
- ARIA live regions for dynamic data for progress and completion messages.
- Notifications & toasts for data operations for background jobs.
- For charts, data visualization & chart alternatives, because chart data is often what users copy or export.
ARIA & HTML spec reference
Permalink to "ARIA & HTML spec reference"| Element / attribute | Valid values | When to apply | Common misuse |
|---|---|---|---|
button name |
“Export 240 invoices as CSV” | Every export control | “Export” or a download icon alone |
a download |
File name | Completed export links | Auto-downloading without notice |
progress |
value, max |
Determinate export progress | Live region with every percent |
progress (no value) |
— | Indeterminate progress | Spinner with no text |
aria-disabled="true" |
— | Busy export button | disabled, which drops focus |
role="status" |
— | Start, milestones, completion | Toasts that vanish |
role="alert" |
— | Failures | Silent failure |
thead { display: table-header-group } |
CSS | Repeating print headers | Sticky headers in print |
Step-by-step implementation
Permalink to "Step-by-step implementation"Step 1 — Name controls with format and scope (SC 2.4.6)
Permalink to "Step 1 — Name controls with format and scope (SC 2.4.6)"<button type="button">Export 240 filtered invoices as Excel</button>
Step 2 — Serialise from data (SC 1.3.1)
Permalink to "Step 2 — Serialise from data (SC 1.3.1)"Every column gets an export header and an export value; nothing is scraped from the DOM. See accessible CSV and Excel exports.
const header = cols.map(c => c.exportHeader ?? c.header);
const row = (r) => cols.map(c => c.exportValue ? c.exportValue(r) : r[c.key]);
Step 3 — Structured spreadsheets
Permalink to "Step 3 — Structured spreadsheets"Named sheet, frozen header, defined table, typed numbers and dates.
ws.addTable({ name: 'Invoices', ref: 'A1', headerRow: true, columns, rows });
Step 4 — Print styles (SC 1.4.1)
Permalink to "Step 4 — Print styles (SC 1.4.1)"Repeat headers, hide chrome, print status as text. See print stylesheets for data tables.
@media print { thead { display: table-header-group; } .toolbar { display: none; } }
Step 5 — Tagged PDFs (SC 1.3.1, 1.3.2)
Permalink to "Step 5 — Tagged PDFs (SC 1.3.1, 1.3.2)"Render semantic report HTML with a tagging engine and validate. See generating accessible PDF reports from data views.
await page.pdf({ format: 'A4', tagged: true, outline: true });
Step 6 — Progress and completion (SC 4.1.3)
Permalink to "Step 6 — Progress and completion (SC 4.1.3)"Milestones, a download link and assertive failures. See announcing export progress and downloads.
if (m > lastMilestone) status.textContent = `Export ${m} percent complete`;
Keyboard interaction contract
Permalink to "Keyboard interaction contract"| Key / action | Result | Expected AT announcement | Failure indicator |
|---|---|---|---|
| Enter on an export button | Export starts | “Preparing Excel export of 240 rows…” | Silence; repeated presses start duplicates |
| Enter on Copy data | Clipboard written | “Copied 240 rows and 5 columns.” | Silence |
| Enter on a ready link | Download | Link name with file and size | Link named “Download” only |
| Ctrl+P | Browser print | Browser dialog | Custom handler blocks it |
| Enter on Print button | Print dialog | Browser dialog; focus returns to button | Focus lost after dialog |
Screen reader compatibility matrix
Permalink to "Screen reader compatibility matrix"| AT | Export status messages | Downloaded XLSX in Excel | Tagged PDF |
|---|---|---|---|
| NVDA | Announced | Headers read with defined tables | Good in Acrobat |
| JAWS | Announced | Headers read; “set column titles” otherwise | Best in Acrobat |
| VoiceOver (macOS) | Announced | Numbers/Excel for Mac support headers | Preview ignores many tags |
| Narrator | Announced | Headers read in Excel | Edge PDF viewer reads tags |
| TalkBack | Announced | Mobile Excel limited | Limited |
Edge cases & failure modes
Permalink to "Edge cases & failure modes"1. Export scraped from the DOM
Permalink to "1. Export scraped from the DOM"Diagnosis: files contain “Sort ascending”, icon names and formatted strings. Fix: serialise from the data model.
2. Colour-only status in files and printouts
Permalink to "2. Colour-only status in files and printouts"Diagnosis: red fills for overdue; lost in print and to many users. Fix: a text status column.
3. Image-only PDFs
Permalink to "3. Image-only PDFs"Diagnosis: html2canvas plus jsPDF. Fix: tagged PDF from semantic HTML.
4. Silent long exports
Permalink to "4. Silent long exports"Diagnosis: the button disables with a spinner; users press again. Fix: busy button with aria-disabled, milestones, completion link.
5. Ambiguous scope
Permalink to "5. Ambiguous scope"Diagnosis: “Export” exports only the visible page. Fix: name the scope; offer selected, filtered and all.
Accessible CSV and Excel exports
Permalink to "Accessible CSV and Excel exports"Headers in row 1, raw typed values, units in headers, a named sheet with a defined table and a frozen header row. Excel’s Accessibility Checker should report nothing. See accessible CSV and Excel exports.
'' + [header, ...body].map(l => l.join(',')).join('\r\n'); // UTF-8 BOM for Excel
Print stylesheets for data tables
Permalink to "Print stylesheets for data tables"Repeat thead, avoid row breaks, hide controls, print status as text and add a caption with filters and date. See print stylesheets for data tables.
tr { break-inside: avoid; } .status-badge { background: none; border: .5pt solid #000; }
Generating accessible PDF reports
Permalink to "Generating accessible PDF reports"Build a semantic report view, render it with a tagging engine, and validate with PAC or veraPDF and a screen reader. Keep large data in spreadsheets. See generating accessible PDF reports from data views.
<html lang="en"><head><title>Overdue invoices — October 2026</title></head>
Copying table and chart data
Permalink to "Copying table and chart data"One button writes TSV and an HTML table to the clipboard and confirms the copy; a selectable fallback covers blocked clipboards. See copying table and chart data to the clipboard.
new ClipboardItem({ 'text/plain': tsvBlob, 'text/html': htmlBlob });
Announcing export progress and downloads
Permalink to "Announcing export progress and downloads"Milestone announcements, a progress element users can check, a named download link on completion and background jobs for long exports. See announcing export progress and downloads.
status.textContent = 'Export ready. Download invoices.xlsx, 14 MB, is after the export button.';
Cross-cutting concerns
Permalink to "Cross-cutting concerns"One serialiser. CSV, XLSX, clipboard and print should share a single function that turns rows and columns into headers and values. When the grid adds a column, every route updates together, and accessibility fixes (raw values, text status) apply everywhere.
Scope and state. Every route should respect the same notion of scope — selection, filtered set or everything — and say which it used. Filters and sort should be recorded in the output (file name, print caption, PDF summary paragraph), because a file without context is easy to misread later.
Locale. Number formats, date formats and CSV delimiters vary by locale; typed values in XLSX sidestep most of this, which is another reason to prefer XLSX for screen reader users.
Security and privacy. Exports often contain more data than the screen. Honour column permissions in the serialiser, and say when columns were omitted (“2 restricted columns not exported”).
Worked example: month-end reconciliation
Permalink to "Worked example: month-end reconciliation"An accounts team reconciles 4,800 transactions at month end. One analyst uses JAWS; another uses ZoomText at 300%; a third prints the exceptions list to annotate by hand.
The JAWS user filters the grid to unmatched transactions and activates “Export 312 unmatched transactions as Excel”. The button stays focused and JAWS says “Preparing Excel export of 312 rows”; a second later, “Export ready. Download unmatched-2026-10.xlsx, 48 KB”. In Excel, the workbook opens on a sheet named “Unmatched” with a frozen header row and a defined table, so JAWS announces “Amount (EUR)” and “Counterparty” as the analyst moves across cells. Status is a text column (“Missing receipt”, “Duplicate”), so sorting and filtering by it works.
The ZoomText user prints nothing but copies the totals table with “Copy totals table data” and pastes it into an email; it arrives as a real table, not a run-on line. The third analyst prints the exceptions view: every page repeats the header row, status prints as words, and the caption at the top says which filters were applied and when.
None of these routes needed special accessibility features beyond the patterns above — just exports built from data, named precisely and announced.
Who is affected, and how
Permalink to "Who is affected, and how"| User group | What breaks | What fixes it |
|---|---|---|
| Screen reader users | Files without headers; image-only PDFs; silent exports | Defined tables, tagged PDFs, status messages |
| Low-vision users | Printouts with lost colours and clipped columns | Print styles; text status; landscape |
| Keyboard users | Repeated exports from silent buttons | Busy state and confirmations |
| Users with cognitive disabilities | Files without context | Filters and dates in names and captions |
| Everyone | Formatted strings that do not sort | Raw typed values |
Design system integration
Permalink to "Design system integration"Treat export as part of the data grid’s contract. Column definitions carry exportHeader and exportValue; the grid exposes getExportRows(scope); an ExportMenu builds items with counts and formats; an ExportJob handles busy state, milestones, completion and hand-off to the notification centre; print styles and a printCaption prop ship with the table. Status badges always contain text so they survive print and export. Document each piece against its criterion and include exports in accessibility testing — open the generated files with a screen reader as part of release checks.
Measuring export quality
Permalink to "Measuring export quality"Exports drift quietly: a new column is added with a display renderer but no export value, and the file fills with “[object Object]”. Guard against it with three automated checks in CI. First, generate each export from a fixture dataset and compare headers to the grid’s column names. Second, assert that numeric and date columns are typed in the XLSX output. Third, run veraPDF on generated PDFs. Add one manual check per release: open the main export with a screen reader and move across the header row.
Testing checklist
Permalink to "Testing checklist"Automated
Permalink to "Automated"Keyboard
Permalink to "Keyboard"Screen reader
Permalink to "Screen reader"FAQ
Permalink to "FAQ"Does offering an export make an inaccessible grid acceptable?
No. The grid must be accessible in its own right. Exports are a valuable complement, not a substitute.
Which export format should I build first?
XLSX with a defined table, because it serves screen reader users best and most business users expect it. Add CSV for compatibility.
Are PDFs ever the best choice for data?
For short, fixed reports and records, yes. For anything users will analyse, a spreadsheet is better.
How do I test exports with a screen reader?
Open the file in Excel with NVDA or JAWS, move across the first data row and listen for column headers, then move down a column and listen for values. For PDFs, open in Acrobat Reader and use table navigation.
Should exports include hidden columns?
Ask users, and make it explicit either way. A common pattern is an export menu with “Visible columns” and “All columns” options, each with its count in the name.
How do exports interact with virtualization?
Virtualized grids render only some rows, which is another reason never to export from the DOM. The data model has every row; serialise from it.
Who should own export accessibility?
The team that owns the grid component. When exports are built per product, each one makes its own mistakes; when the grid owns serialisation and naming, fixes reach every product at once.
Related
Permalink to "Related"- Semantic HTML table construction — the structure exports inherit
- Cell selection & clipboard — copying ranges inside the grid
- Data visualization & chart alternatives — chart data out of the chart
- Notifications & toasts for data operations — background export notifications
- Responsive data tables — the screen-side layout print builds on