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() with ClipboardItem for multiple formats; requires a secure context and user activation; permission behaviour differs between browsers.
What happens on Copy data Flow: the button is activated; data is serialised as tab-separated text and as an HTML table; the Clipboard API writes both formats; a status message confirms; on failure, a fallback textarea is shown with its content selected. What happens on Copy dataActivate "Copy invoice table data"user activation for clipboardbuttonSerialiseTSV for plain text; HTML table for rich pastefrom the data modelWritenavigator.clipboard.write with two formatssecure contextConfirm"Copied 240 rows and 5 columns"status regionFallbacktextarea with data selected; "Press Ctrl+C"when write fails

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.

innerText versus serialising the data Comparison: copying innerText includes hidden helper text and button labels and loses structure in some apps; serialising data gives clean tab-separated text and a real HTML table. innerText versus serialising the data✗ Copy innerText"Sort ascending" and "Row actions" in thepasteHidden helper text includedFormatted strings ("€1,240.00")Structure varies by browser✓ Serialise from dataOnly headers and valuesNo hidden textRaw values; units in headersTSV and HTML table every time

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”
What each paste target uses Matrix of paste targets — Excel, Google Sheets, Word, plain-text editor, email client — against whether they use the HTML or plain-text flavour and whether structure is kept. What each paste target usesUses HTMLUses plain textStructure keptExcelYesFallbackRows and columnsGoogle SheetsYesFallbackRows and columnsWord / DocsYesNoAs a tableText editorNoYesTab-separatedEmail clientUsuallyFallbackAs a table
Writing both flavours covers every target.

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.

Permalink to "Related"

← Back to Data Export & Print