Copying Table and Chart Data to the Clipboard
Permalink to "Copying Table and Chart Data to the Clipboard"Copying data is the lightest export there is: one button, and the table or the data behind a chart is ready to paste into a spreadsheet, an email or a document. For screen reader users it is often the quickest route from a chart they cannot see to numbers they can work with. For keyboard users it avoids selecting a table with Shift+arrows, which browsers make awkward across cells. Done well, the pasted result keeps its rows and columns; done badly, it becomes a single line of text with every cell run together.
This guide belongs to data export & print.
Spec reference
Permalink to "Spec reference"- SC 2.4.6 Headings and Labels (AA) — “Copy” is ambiguous; “Copy invoice table data” or “Copy chart data” is not.
- SC 4.1.3 Status Messages (AA) — “Copied 240 rows” is a status message; a silent copy leaves users unsure whether anything happened.
- SC 1.4.13 Content on Hover or Focus (AA) — a “Copied!” tooltip that appears on the button must be dismissible and persistent, or it will vanish before some users notice it; a status region avoids the issue.
- Async Clipboard API —
navigator.clipboard.write()withClipboardItemfor multiple formats; requires a secure context and user activation; permission behaviour differs between browsers.
When to use — and when not to
Permalink to "When to use — and when not to"Add Copy data next to tables that users regularly move into other tools, and to every chart that has a data table alternative — copying the chart’s data is a natural extension of the show-as-table toggle.
Do not replace cell-level copy in interactive grids with a single button; grids that support range selection should also support Ctrl+C, as in keyboard copy and paste in data grids. The button copies “the whole thing”; Ctrl+C copies “what I selected”.
The misapplication: copying element.innerText of the table. It includes visually hidden helper text, button labels and icon names, and separators vary by browser.
Annotated code example
Permalink to "Annotated code example"<button type="button" id="copy-data">Copy invoice table data</button> <!-- SC 2.4.6 -->
<p id="copy-status" role="status" class="visually-hidden"></p> <!-- SC 4.1.3 -->
copyBtn.addEventListener('click', async () => {
const rows = currentRows(); // respects filters, sort, visible columns
const cols = visibleColumns();
const tsv = [cols.map(c => c.header), ...rows.map(r => cols.map(c => r[c.key]))]
.map(line => line.map(v => String(v ?? '').replace(/[\t\n]/g, ' ')).join('\t')).join('\n');
const html = `<table><thead><tr>${cols.map(c => `<th>${escapeHtml(c.header)}</th>`).join('')}</tr></thead>` +
`<tbody>${rows.map(r => `<tr>${cols.map(c => `<td>${escapeHtml(r[c.key])}</td>`).join('')}</tr>`).join('')}</tbody></table>`;
try {
await navigator.clipboard.write([new ClipboardItem({
'text/plain': new Blob([tsv], { type: 'text/plain' }),
'text/html': new Blob([html], { type: 'text/html' }), // pastes as a table in docs and spreadsheets
})]);
status.textContent = `Copied ${rows.length} rows and ${cols.length} columns.`;
} catch {
showFallback(tsv); // selectable textarea
status.textContent = 'Could not copy automatically. The data is selected in a text box; press Control+C.';
}
});
Keyboard & AT behaviour
Permalink to "Keyboard & AT behaviour"| Event | Expected announcement | AT deviations |
|---|---|---|
| Activate Copy data | “Copied 240 rows and 5 columns.” | Some readers also say “clipboard” via OS notification |
| Clipboard blocked | Fallback message; focus moves to the selected textarea | Firefox may require a permission prompt in some contexts |
| Paste into Excel | Rows and columns preserved | Excel prefers the HTML flavour |
| Paste into a plain-text editor | Tab-separated lines | Screen readers read tabs as spaces or “tab” |
Integration context
Permalink to "Integration context"Copy data complements the file-based CSV and Excel exports and shares their rule: serialise from data, not from markup. For charts, it pairs with show-as-table toggles for charts. For selected ranges inside a grid, see keyboard copy and paste in data grids.
Gotchas
Permalink to "Gotchas"Very large copies. Copying 50,000 rows can freeze the page and overwhelm the clipboard. Cap the copy (for example at 10,000 rows) and say so, offering a file export for the rest.
Safari’s ClipboardItem promises. Safari requires the clipboard write to happen synchronously within the user gesture; pass promises of Blobs to ClipboardItem if data must be fetched first.
Transient “Copied!” tooltips. A tooltip that vanishes after a second fails users who look for it slowly. Use the status region, and if you show a visual confirmation, keep it until the next interaction.
Framework notes
Permalink to "Framework notes"AG Grid provides api.copyToClipboard() and copySelectedRowsToClipboard, honouring processCellForClipboard for raw values; add your own status message. MUI X has clipboard copy in Pro and Premium. In React, keep the serialiser outside components so the same function powers Copy data, CSV export and tests.
// One serialiser, three uses
export const serialise = (rows, cols) => ({ tsv: toTSV(rows, cols), html: toHTMLTable(rows, cols), csv: toCSV(rows, cols) });
Testing checklist
Permalink to "Testing checklist"Design system notes
Permalink to "Design system notes"Add a copyData action to the table and chart toolbar components, wired to a shared serialiser and the shared status announcer. Name it from the component’s own label so every instance gets a specific name automatically.
FAQ
Permalink to "FAQ"Should Copy data copy only the visible page or all filtered rows?
All filtered rows, up to a reasonable cap, because that is what “the table” means to the user. Say the scope in the button name or the confirmation.
Is the Clipboard API accessible to screen readers?
The API is invisible; what matters is the confirmation message and a fallback when it fails.
Should chart Copy data include the chart's title?
Put the title in the HTML flavour as a caption; keep the plain-text flavour to headers and values so it pastes cleanly into spreadsheets.
What if the data contains tabs or line breaks?
Replace them with spaces in the plain-text flavour, as shown, and keep them in the HTML flavour where they are harmless.
Can voice control users trigger Copy data?
Yes, if the button has a visible text label they can say, such as “Copy table data”. Icon-only copy buttons need a visible tooltip or label for voice users to know what to say, and the accessible name must contain that label (SC 2.5.3).
Should copying move focus anywhere?
No. Keep focus on the button and confirm in the status region. Only the fallback, which needs the user to press Ctrl+C, moves focus to the selected text box.
Related
Permalink to "Related"- Keyboard copy and paste in data grids — copying selected ranges
- Show-as-table toggles — chart data as a table
- CSV and Excel exports — when a file is better