Show-as-Table Toggles for Charts

Permalink to "Show-as-Table Toggles for Charts"

A show-as-table toggle is a control beside a chart that reveals the chart’s data as an HTML table — either replacing the chart or appearing beneath it. It is the most dependable chart alternative there is: tables are fully supported by every screen reader, sortable, copyable, and readable at any zoom level. It is also useful far beyond screen reader users; analysts often want the numbers, not the picture.

The implementation details decide whether the toggle helps or confuses: what kind of control it is, where the table appears in the DOM, where focus goes, and whether the table and chart can drift apart. This page covers each. It belongs to data visualization & chart alternatives.

Spec reference

Permalink to "Spec reference"

Two HTML/ARIA patterns fit:

  • Toggle button — <button aria-pressed="true|false">Show as table</button> when the table replaces the chart. The pressed state says which view is active.
  • Disclosure — <button aria-expanded aria-controls> or <details>/<summary> when the table appears alongside the chart.

The table is an ordinary data table with a caption matching the chart’s title (SC 1.3.1). Hidden views use the hidden attribute, so the inactive view leaves the accessibility tree.

Criteria: SC 1.1.1 Non-text Content (the table is the chart’s equivalent), SC 1.3.1, SC 4.1.2 for the toggle’s state, SC 1.3.2 Meaningful Sequence for placement, and SC 3.2.2 On Input — toggling should not move focus unexpectedly.

Replace the chart, or reveal beneath it? Comparison of a toggle that swaps the chart for its table against a disclosure that shows the table under the chart. Replace the chart, or reveal beneath it?✓ Swap (aria-pressed)One view at a time; same footprint"Show as table, toggle button, pressed"Good on crowded dashboardsRemember the preference per user✓ Reveal (aria-expanded or details)Chart and table visible together"Show data table, expanded"Good in reports and articlesNative details needs no script
Both are good; choose by whether users want both views at once.

When to add a toggle — and when to show the table outright

Permalink to "When to add a toggle — and when to show the table outright"

Add a toggle when space is tight and the chart is the default view most users want: dashboards, embedded analytics.

Show the table outright when the chart is small and the data is the point — a report page with one chart and a twelve-row table underneath costs nothing and needs no interaction. A toggle is a convenience, not a requirement; the requirement is that the data is available.

The misapplication to name is a “View data” link that opens the table in a new window or downloads a CSV as the only alternative. It works, but it takes users away from the page and its context. Keep the table in place; offer a download as an extra.

Annotated code example

Permalink to "Annotated code example"
<section aria-labelledby="orders-title" class="chart-card">
  <div class="card-head">
    <h3 id="orders-title">Orders per month, 2026</h3>
    <!-- SC 4.1.2: toggle state is the active view -->
    <button type="button" class="view-toggle" aria-pressed="false" aria-controls="orders-chart orders-table">
      Show as table
    </button>
  </div>
  <figure id="orders-chart">
    <canvas role="img" aria-label="Orders per month, 2026" aria-describedby="orders-sum"></canvas>
    <figcaption id="orders-sum">Orders rose from 820 in January to 1,410 in November.</figcaption>
  </figure>
  <!-- SC 1.3.2: immediately after the chart in DOM order -->
  <div id="orders-table" class="table-scroll" hidden>
    <table>
      <caption>Orders per month, 2026</caption>     <!-- SC 1.3.1: same name as the chart -->
      <thead><tr><th scope="col">Month</th><th scope="col">Orders</th></tr></thead>
      <tbody><!-- generated from the same array as the chart --></tbody>
    </table>
  </div>
</section>
const PREF = 'chart-view';
document.querySelectorAll('.view-toggle').forEach((btn) => {
  const [chartId, tableId] = btn.getAttribute('aria-controls').split(' ');
  const apply = (asTable) => {
    btn.setAttribute('aria-pressed', String(asTable));
    document.getElementById(chartId).hidden = asTable;
    document.getElementById(tableId).hidden = !asTable;
  };
  apply(safeGet(PREF) === 'table');                // remembered choice, try/catch inside
  btn.addEventListener('click', () => {
    const asTable = btn.getAttribute('aria-pressed') !== 'true';
    apply(asTable);                                // SC 3.2.2: focus stays on the toggle
    safeSet(PREF, asTable ? 'table' : 'chart');
  });
});

// One data source for both views
function renderOrders(rows) {
  chart.data.labels = rows.map((r) => r.month);
  chart.data.datasets[0].data = rows.map((r) => r.orders);
  chart.update();
  tbody.replaceChildren(...rows.map((r) => tr(r.month, r.orders)));
  summary.textContent = summarise(rows);
}

Keeping the label constant (“Show as table”) and letting aria-pressed carry the state is deliberate. A label that flips between “Show as table” and “Show as chart” combined with a pressed state produces contradictory announcements (“Show as chart, pressed”). Pick one: constant label with aria-pressed, or changing label without it.

Keyboard & AT behaviour

Permalink to "Keyboard & AT behaviour"
Event Expected behaviour Announcement
Tab to toggle Focus on the button “Show as table, toggle button, not pressed”
Enter / Space Chart hidden, table shown “pressed”
Down Arrow (browse mode) Reaches the table next “Orders per month, 2026, table with 2 columns and 13 rows”
T (NVDA/JAWS) Jumps to the table Same
Revisit page Table view restored Toggle reads “pressed”
One data source, two views Flow from a single data array to both the chart rendering and the table rendering, with the toggle choosing which is visible and the summary generated from the same data. One data source, two viewsData rowsmonth, ordersChartcanvas or SVGTablecaptioned, sameorderSummarygenerated sentenceTogglewhich view isvisible
Chart, table and summary are three renderings of one array — they cannot disagree.

Integration context

Permalink to "Integration context"

On a dashboard with many charts, a single “Show all charts as tables” preference is kinder than a toggle per card; apply it globally and still offer per-card toggles. The dashboard structure that makes those cards navigable is in landmarks and headings for dashboards.

The table itself follows the normal table rules — caption, headers, formatted numbers — from writing table captions and summaries and formatting numbers and units in table cells.

Which toggle pattern? Decision tree for a chart's table alternative based on available space and whether users need both views at once. Which toggle pattern?Is there room to show the table beside or below thechart?Yes, alwaysNo togglerender the table under the chartYes, on demandDisclosuredetails or aria-expandedNo — one view fitsToggle buttonaria-pressed swaps the views
When there is room for both, the simplest toggle is none at all.

Gotchas

Permalink to "Gotchas"

Hiding the chart with visibility: hidden. It keeps the chart’s space and, in some browsers, its accessibility node. Use hidden.

Large data sets. A chart of 10,000 points should not produce a 10,000-row table in place. Aggregate to the chart’s resolution (daily, monthly) and offer a download for the raw data.

Storage failures. localStorage can throw in private windows; wrap reads and writes, and default to the chart when unavailable.

Design system notes

Permalink to "Design system notes"

Make the toggle part of the chart card component, fed by the same data model as the chart, with a defaultView prop and a global user preference. The component renders the table with the chart’s title as caption, formats numbers with the chart’s formatters, and never lets product teams supply a separate table that could drift.

Testing checklist

Permalink to "Testing checklist"

FAQ

Permalink to "FAQ"
Should a chart have a "view as table" option?

Every chart should make its data available as a table. A toggle is a good way to do that when space is limited; on report pages, showing the table under the chart may be simpler.

Should the toggle be a button with aria-pressed or aria-expanded?

aria-pressed when the table replaces the chart, because the state is which view is active. aria-expanded, or a native details element, when the table appears in addition to the chart.

Where should focus go after toggling to the table?

It should stay on the toggle. The table appears immediately after the chart in reading order, so users reach it with their next navigation step.

Should the table offer a download as well?

It is a useful extra — many users want the data in a spreadsheet — but it should supplement the in-page table, not replace it, so the data stays available in context.

How do I keep the chart and table consistent?

Render both, and the text summary, from the same data array in the same function, so any update changes all three together.

Permalink to "Related"

← Back to Data Visualization & Chart Alternatives