Linked Chart and Table Highlighting
Permalink to "Linked Chart and Table Highlighting"Dashboards often pair a chart with a table: hover a bar and its row lights up; click a row and the matching point is emphasised. Done well, linked highlighting helps everyone — sighted users connect the picture to the numbers, and screen reader users can use the table as their main view while the chart follows along for sighted colleagues. Done badly, it is hover-only, one-directional and colour-coded, and it actually makes the page noisier for assistive technology.
This guide belongs to interactive chart controls.
Spec reference
Permalink to "Spec reference"- SC 2.1.1 Keyboard (A) — linking triggered by hover must also be triggered by focus.
- SC 1.4.1 Use of Color (A) — the highlighted row or point needs a non-colour indicator (outline, weight, marker).
- SC 1.3.1 Info and Relationships (A) — the linked state should be programmatic (
aria-selectedoraria-current="true"on the row). - SC 3.2.1 On Focus (A) — focusing a row may highlight the chart (a change of content), but must not move focus into the chart.
- SC 4.1.3 Status Messages (AA) — only announce linkage when it adds information the focused element does not already give.
When to use — and when not to
Permalink to "When to use — and when not to"Link chart and table whenever both are visible together and represent the same data. Make the table the primary view for keyboard and screen reader users, with linkage as a convenience.
Do not move focus from table to chart (or back) automatically. Do not announce “Q3 highlighted in chart” on every row focus — the row already says “Q3”. The misapplication: hover-only linking, so keyboard users never see the connection, combined with a colour-only highlight.
Annotated code example
Permalink to "Annotated code example"// One source of truth for the linked item
const selection = new EventTarget(); let current = null;
function select(id, source) { current = id; selection.dispatchEvent(new CustomEvent('change', { detail: { id, source } })); }
// Table → state (focus and hover both)
table.addEventListener('focusin', (e) => { const r = e.target.closest('tr[data-id]'); if (r) select(r.dataset.id, 'table'); });
table.addEventListener('pointerover', (e) => { const r = e.target.closest('tr[data-id]'); if (r) select(r.dataset.id, 'table'); });
// Chart → state (keyboard navigation and hover)
chart.on('pointfocus', (id) => select(id, 'chart'));
// State → both views; never move focus (SC 3.2.1)
selection.addEventListener('change', ({ detail: { id, source } }) => {
chart.highlight(id); // outline + marker (SC 1.4.1)
table.querySelectorAll('tr[aria-current]').forEach(r => r.removeAttribute('aria-current'));
table.querySelector(`tr[data-id="${id}"]`)?.setAttribute('aria-current', 'true'); // SC 1.3.1
});
// Explicit cross-view commands
chart.on('keydown', (e) => { if (e.key === 'Enter') table.querySelector(`tr[data-id="${current}"] th`)?.focus(); }); // "Go to row"
tr[aria-current="true"] { outline: 2px solid var(--brand-strong); font-weight: 600; } /* not colour alone */
.point.highlight { r: 6; stroke: var(--ink); stroke-width: 2; }
Keyboard & AT behaviour
Permalink to "Keyboard & AT behaviour"| Event | Expected result | Announcement |
|---|---|---|
| Arrow to row Q3 in table | Chart highlights Q3 | Row as usual; no extra message |
| Arrow to Q4 in chart | Table row Q4 marked current | Point announcement (from the chart) |
| Enter in chart | Focus moves to Q4 row | Row read |
| “Show in chart” on a row | Focus moves to chart at Q4 | Point announcement |
| Mouse hover | Same linkage | — |
Integration context
Permalink to "Integration context"The chart side uses keyboard navigation from canvas & SVG chart keyboard interaction; the table side follows sortable & filterable data grids. Range selections that filter linked views are covered in brush selection alternatives for range filtering. Dashboard-wide filters are discussed in dashboard filters that apply across widgets.
Gotchas
Permalink to "Gotchas"Highlight versus filter. Highlighting emphasises; filtering removes. If clicking a row filters other charts, that is a filter and needs announcement and a clear way to undo.
Scroll jumps. Highlighting a row far down a long table should not scroll the table while the user is in the chart; show an indicator (“Row 214”) instead.
Multiple tables. If several tables link to one chart, keep one selection state and mark the row in each.
Common mistakes
Permalink to "Common mistakes"- Linking on hover only.
- Highlighting with colour only.
- Moving focus between views automatically.
- Announcing linkage that adds nothing.
Framework notes
Permalink to "Framework notes"Vega-Lite selections (point, interval) can drive both chart encodings and table views through shared signals. ECharts connect links charts; for tables, listen to highlight and downplay actions. In React, keep the selection in context or a store; in Angular, a service with a BehaviorSubject; avoid component-local state that only one view can see.
// ECharts: table row focus highlights a bar
row.addEventListener('focus', () => chart.dispatchAction({ type: 'highlight', seriesIndex: 0, dataIndex: i }));
Worked example
Permalink to "Worked example"A sales dashboard linked a bar chart of regions to a table, but only on mouse hover and only in one direction. A screen reader user working in the table could not show colleagues which bar they were discussing. The team moved to a shared selection: table focus highlights the bar with an outline and a marker, chart focus marks the row with aria-current and bold text, Enter in the chart jumps to the row, and “Show in chart” on each row jumps the other way. During screen-shared meetings, the chart now follows the screen reader user’s position in the table.
Testing checklist
Permalink to "Testing checklist"Design system notes
Permalink to "Design system notes"Provide a shared useLinkedSelection (or service) and require chart and table components to accept selectedId and emit onSelect for both focus and hover. Document highlight versus filter semantics.
FAQ
Permalink to "FAQ"Should screen reader users hear that the chart highlighted something?
Usually not; it duplicates the row they are on. It helps sighted colleagues and keyboard users who can see both.
aria-selected or aria-current for the linked row?
aria-current=“true” suits “the item currently linked”; aria-selected suits grids with selection. Do not overload selection if the grid already uses it.
Can linking go chart → table → other charts?
Yes, through the shared state. Announce only when other views are filtered, not merely highlighted.
How should touch users trigger linking?
Tap on a row or point selects it; the other view highlights. Tap again to clear.
Should the chart scroll to show a highlighted point?
If the chart is zoomed and the point is off-screen, show a cue or pan with an announcement; do not pan silently.
What if the table is paginated?
Highlighting a point on another page should show “Row on page 3” with a link, rather than jumping pages.
Related
Permalink to "Related"- Brush selection alternatives — range selections that filter
- Dashboard filters across widgets — global filters
- Show-as-table toggles — when chart and table share space