Accessible Network & Graph Diagrams

Permalink to "Accessible Network & Graph Diagrams"

Network and graph diagrams show relationships: services that call each other, accounts that share devices, money flowing between budget lines, people reporting to managers, concepts linked in a knowledge base. They are some of the most information-dense visualisations in data products, and some of the least accessible. A bar chart has axes and values that translate naturally into a table; a graph’s meaning is in its topology — hubs, clusters, bridges, paths — which a single alt text cannot carry and which a mouse-only, pan-and-zoom canvas hides from keyboard and screen reader users entirely.

The approach that works combines three representations, all generated from the same data: a summary that gives the shape at a glance, a table (or tree, for hierarchies) that is complete and filterable, and keyboard exploration that lets users move from node to neighbour the way sighted users follow lines. The users most affected are screen reader users, keyboard users, colour-blind users (edge and node types are often colour-coded), and magnifier users (large graphs need constant panning). The failure this topic prevents is a relationship diagram that only sighted mouse users can read.

This topic belongs to virtualization, charts & dynamic data displays.

WCAG criteria in scope

Permalink to "WCAG criteria in scope"
Criterion Level Relevance to graph diagrams
1.1.1 Non-text Content A Summary plus complete equivalent
1.3.1 Info and Relationships A Edges, flows and hierarchy available as data
1.4.1 Use of Color A Node and edge types in words or patterns
1.4.10 Reflow AA Tree and table equivalents reflow at 400%
1.4.11 Non-text Contrast AA Nodes, edges and focus rings at 3:1
2.1.1 Keyboard A Exploration and inspection without a pointer
2.4.7 Focus Visible AA Focused node highlighted in the drawing
4.1.3 Status Messages AA Traversal and filter updates announced

Prerequisites

Permalink to "Prerequisites"
Three representations of one graph Layers: the data model at the base; above it a generated summary, a complete table or tree, and keyboard exploration; the visual drawing on top, synchronised with exploration. Three representations of one graphVisual drawingSVG or canvas; focus highlight; stable layoutKeyboard explorationone tab stop; neighbours; history; searchTable or treeedges, flows or hierarchy; filters; completeSummarycounts, hubs, groups, outliers; generatedData modelnodes, edges, relations, attributes
All views come from one model, so they cannot disagree.

ARIA & HTML spec reference

Permalink to "ARIA & HTML spec reference"
Element / attribute Valid values When to apply Common misuse
figure + heading + aria-describedby — Wrap the graph, title and summary Unrelated paragraphs nearby
role="application" — Graph regions with a custom key model Large regions or undocumented keys
role="img" + aria-label — Focusable bands or nodes with full names Unnamed focusable shapes
role="tree" / treeitem — Hierarchy equivalents Edge tables for strict hierarchies
table with th scope — Edge and flow tables Adjacency matrices for large graphs
role="status" — Traversal and filter messages Announcing every hover
aria-hidden="true" — Decorative canvas when a full equivalent is present Hiding the only representation

Step-by-step implementation

Permalink to "Step-by-step implementation"

Step 1 — Summarise (SC 1.1.1)

Permalink to "Step 1 — Summarise (SC 1.1.1)"
const s = summarise(graph); // headline + bullets: counts, hubs, groups, bridges, isolated nodes

See announcing graph structure summaries.

Step 2 — Provide the complete equivalent (SC 1.3.1)

Permalink to "Step 2 — Provide the complete equivalent (SC 1.3.1)"
<table><caption>Service connections: 96 directed edges</caption>…</table>

See adjacency tables as network graph alternatives and, for hierarchies, accessible org charts and hierarchy diagrams.

Step 3 — Explore by keyboard (SC 2.1.1)

Permalink to "Step 3 — Explore by keyboard (SC 2.1.1)"
if (e.key === 'ArrowRight') openNeighbourMenu(current.neighbours);

See keyboard traversal of node-link graphs.

Step 4 — Words for types and directions (SC 1.4.1)

Permalink to "Step 4 — Words for types and directions (SC 1.4.1)"
`${nb.node.name}, ${nb.relation}`   // "Ledger DB, writes to"

Step 5 — Flows with values (SC 1.3.1)

Permalink to "Step 5 — Flows with values (SC 1.3.1)"
`${l.source.name} to ${l.target.name}: ${fmt(l.value)}, ${pct} of ${l.source.name}`

See accessible Sankey diagrams.

Step 6 — Visible, stable focus (SC 2.4.7)

Permalink to "Step 6 — Visible, stable focus (SC 2.4.7)"
simulation.stop(); highlight(current); centerOn(current);   // freeze layout before exploration
Diagram types and their best equivalents Matrix of diagram types — service or dependency graph, link analysis, Sankey, org chart, decision tree — against summary, table type and exploration model. Diagram types and their best equivalentsSummaryComplete equivalentExplorationDependency graphHubs and groupsEdge tableNeighbour traversalLink analysisClustersEdge table with relationtypesNeighbour traversalSankey / flowsLargest flowsFlow table with sharesFocusable bandsOrg chartHeadcountTreeTree navigationDecision treeNumber of outcomesTree with branch labelsTree navigation
Hierarchies become trees; general graphs become edge tables; flows become flow tables.

Keyboard interaction contract

Permalink to "Keyboard interaction contract"
Context Key Action Expected announcement Failure indicator
Graph Tab Focus graph at current node Graph name, node name, type, degree Many tab stops
Graph Right List neighbours “Ledger DB, writes to, 1 of 4” Arrow pans the canvas only
Graph Enter (in list) Move to neighbour Node and relation Nothing
Graph Backspace Back along path “Back to …” Lost position
Graph / Search Search field —
Sankey Tab or arrows Next band Full flow name with value Hover-only values
Org tree Arrows Tree navigation Name, role, team size, level Indentation only

Screen reader compatibility matrix

Permalink to "Screen reader compatibility matrix"
AT + browser role=application graphs Focusable SVG paths Trees Notes
NVDA + Chrome Keys passed; status read Names read Levels read Document keys in the description
NVDA + Firefox Same Names read Levels read —
JAWS + Chrome Keys passed Names read Levels read Application mode announced
VoiceOver + Safari Keys passed with interaction Names read in most cases Levels on change Test path focus in Safari
TalkBack + Chrome Limited custom keys Swipe to elements Partial Tables are the primary route

Edge cases & failure modes

Permalink to "Edge cases & failure modes"

1. Hundreds of tab stops

Permalink to "1. Hundreds of tab stops"

Diagnosis: every node focusable in layout order. Fix: one tab stop and traversal.

2. Colour-only relation types

Permalink to "2. Colour-only relation types"

Diagnosis: red edges for “fraud signal”, blue for “payment”. Fix: a Relation column and relation names in traversal.

3. Moving layouts

Permalink to "3. Moving layouts"

Diagnosis: force simulation keeps nudging nodes during exploration. Fix: stop the simulation before interaction.

4. Matrix tables for large graphs

Permalink to "4. Matrix tables for large graphs"

Diagnosis: a 200 × 200 adjacency matrix. Fix: edge table with filters.

5. Org charts as images

Permalink to "5. Org charts as images"

Diagnosis: canvas chart with a one-line alt. Fix: a tree equivalent with search.

Permalink to "Keyboard traversal of node-link graphs"

Explore from a node to its neighbours, hear relations and degrees, step back with Backspace and jump with search — one tab stop for the whole graph. See keyboard traversal of node-link graphs.

say(`${describe(node)}. Reached via ${relation}.`);

Adjacency tables as network graph alternatives

Permalink to "Adjacency tables as network graph alternatives"

Edge tables with From, Relation and To columns, filters on either end and attributes as columns are complete and practical. See adjacency tables as network graph alternatives.

<tr><th scope="row">Checkout</th><td>calls</td><td>Auth service</td></tr>

Announcing graph structure summaries

Permalink to "Announcing graph structure summaries"

Counts, hubs, named groups, bridges and isolated nodes, generated from the model and placed before the graph. See announcing graph structure summaries.

`${g.nodes.length} services with ${g.edges.length} connections, in ${groups.length} groups.`

Accessible Sankey diagrams

Permalink to "Accessible Sankey diagrams"

Flow tables with shares, summaries of the largest flows, labelled nodes and focusable bands with full names. See accessible Sankey diagrams.

<path tabindex="0" role="img" aria-label="Engineering to Salaries: 4.2 million EUR, 70 percent of Engineering"/>

Accessible org charts and hierarchy diagrams

Permalink to "Accessible org charts and hierarchy diagrams"

A tree equivalent with roles and team sizes, described dotted lines, search and focus synchronised with the visual chart. See accessible org charts and hierarchy diagrams.

tree.addEventListener('focusin', e => chart.centerOn(idOf(e.target)));

Cross-cutting concerns

Permalink to "Cross-cutting concerns"

One model, many views. Summaries, tables, trees, traversal and drawings must come from one data model. Hand-maintained alternatives drift within weeks.

Scale. Graphs with thousands of nodes cannot be drawn usefully for anyone. Filter first (by team, time range, relation type), then draw; the summary and table should state what is filtered out.

Performance. Focusable SVG elements for every node and edge add to the accessibility tree. For large graphs, keep one focusable region and manage virtual focus, as discussed in DOM size limits and accessible performance tradeoffs.

Motion. Animated layouts and physics simulations should stop, and respect prefers-reduced-motion.

Answering "what depends on Auth?" three ways Sequence across summary, table and traversal: the summary names Auth as the top hub; the table filtered on To equals Auth lists eleven callers; traversal from Auth lists its neighbours with directions. Answering "what depends on Auth?" three waysSummaryTableTraversal"Most connected: Auth service (11)"follow link; filter To = Auth"11 connections involving Auth"search "auth"; Right lists neighbours"Checkout, callsAuth, 1 of 11"
Different users choose different routes to the same answer.

Worked example: an incident service map

Permalink to "Worked example: an incident service map"

During incidents, an operations team relies on a service map. Before the redesign, it was a canvas with coloured nodes and hover tooltips; an on-call engineer who uses NVDA could not use it at all and depended on colleagues reading it to her. After the redesign: the map’s caption summarises the graph and currently failing services (“3 services failing: Payments API, Ledger DB, Queue; Payments API is called by 6 services”); an edge table below can be filtered to any service; and the map itself is one tab stop with neighbour traversal, Backspace and search. Failing state is a word in every announcement and a pattern in the drawing, not only a red fill. She now leads incident calls with the same information as everyone else.

Who is affected, and how

Permalink to "Who is affected, and how"
User group Typical failure Fix
Screen reader users Graph is an image or anonymous shapes Summary, table and traversal
Keyboard users Pan, zoom and hover only One tab stop with traversal and search
Colour-blind users Node and edge types by colour Words, patterns and labels
Magnifier users Constant panning of large graphs Tables and trees that reflow
Users with cognitive disabilities Dense, moving layouts Summaries; stable layouts

Common mistakes in graph libraries

Permalink to "Common mistakes in graph libraries"

Graph libraries focus on layout and rendering and leave accessibility almost entirely to the application:

  • Canvas renderers with no DOM. Sigma.js and some Cytoscape configurations draw to canvas; nothing is exposed to assistive technology unless you add a parallel structure.
  • Hover-only tooltips. Node details appear on hover and never on focus.
  • Colour-coded categories. Default styles map categories to hues with no labels or patterns.
  • Physics that never stops. Force layouts keep running, moving nodes under the user’s focus.
  • Zoom and pan as the only navigation. No way to jump to a node by name.

All five are fixed in the application layer: a parallel model-driven structure, focus-triggered details, labels and patterns, a stopped simulation and search.

Design system integration

Permalink to "Design system integration"

Build a graph accessibility layer independent of the renderer: a model adapter (nodes, edges, relations, hierarchy), a GraphSummary, a GraphTable or HierarchyView, and a GraphNavigator for traversal with message templates. Renderers (D3, Cytoscape, Sigma, ECharts) only need to draw a focus highlight and accept “center on node” commands. Tokens for focus halos, highlighted edges and pattern fills must meet 3:1 in light, dark and forced-colours modes.

Measuring graph accessibility

Permalink to "Measuring graph accessibility"

Ask three questions of every graph and time the answers with a screen reader: “How many nodes and connections are there?” (should be immediate from the summary), “What connects to X?” (under a minute via table filter or traversal) and “What is the path from A to B?” (possible via traversal or a path-finding command). If any question cannot be answered, one of the three representations is missing.

Testing checklist

Permalink to "Testing checklist"

Automated

Permalink to "Automated"

Keyboard

Permalink to "Keyboard"

Screen reader

Permalink to "Screen reader"

FAQ

Permalink to "FAQ"
Is a table enough to make a graph accessible?

A good edge or flow table is a complete equivalent. A summary and traversal make it much easier to understand the graph’s shape and local structure.

Should I use role="application" for graphs?

For a small region with a documented custom key model, it is reasonable. Keep it limited to the graph itself.

How do I handle very large graphs?

Filter before drawing, summarise what is shown and hidden, and rely on tables and search for completeness.

Are Sankey diagrams accessible?

With a flow table, a summary and focusable bands, yes. Without them, no.

Should org charts be graphs or trees?

Trees, with secondary relationships described in text.

Do I need to describe the visual layout?

No. Describe structure in domain terms — hubs, groups, flows — not positions and colours.

Can screen reader users find paths between nodes?

Offer a “path from A to B” command that lists the steps; it is often more useful than manual traversal for everyone.

What is the first fix for an inaccessible graph?

Add a generated summary and an edge or flow table. They are quick to build and provide a complete equivalent while you work on traversal.

How do I make graph filters accessible?

Treat them like any data filter: labelled controls, a status message with the resulting counts (“Showing 14 services, 31 connections”), and an updated summary. Filters are often the most effective way to make a large graph usable for everyone.

Should edges have their own focus stops?

Usually not. Edges are reached through their nodes as relations. If edges carry important attributes, list them in the neighbour menu and the edge table.

Permalink to "Related"

← Back to Virtualization, Charts & Dynamic Data Displays