https://tink.uk/accessible-svg-line-graphs/
SVG is often used for data visualisation, but because SVG lacks the semantics to express structures like bar charts, line graphs, and scatter plots, the content is difficult for screen reader users to interpret. The solution is to use the technique for creating accessible SVG tables as your starting point.
Let's take a simple SVG line graph as a working example. In this graph the y axis represents usage by percent, and the x axis represents time. The three lines represent the different screen readers:
- Jaws is a solid blue line;
- NVDA is a dotted black line;
- VoiceOver is a smaller dotted green line.
Using the graph you can tell what percent of usage each screen reader had at a particular point in time. For example, in January 2014 74% of screen reader users used Jaws, in May 2012 43% used NVDA, and in July 2014 30.9% used Voiceover.
This data can be represented as a table: where the rows represent the screen readers, the columns represent the points in time, with the usage percentage data in the table cells.
It's a good idea to give people choices about the way they consume content, so letting people choose between a graphical and tabular view of the data would be a good thing to do in any case. But in the interests of making your primary content as accessible as it can be, you can use the following ARIA to add table semantics directly in the SVG:
Adding ARIA
Use a g
element with role="table"
to represent the table:
-
Code language
svg
COPY TO CLIPBOARD
<g role="table">...</g>
Add role="row"
to each of the g
elements that represent the table rows:
-
Code language
svg
COPY TO CLIPBOARD
<g role="row">...</g>
Add role="columnheader"
to each of the text
elements that represent the column headers:
-
Code language
svg
COPY TO CLIPBOARD
<g role="row" class="grey small">
<text role="columnheader" x="334.5" text-anchor="middle" class="grey" y="306">Time</text>
<text role="columnheader" x="130" text-anchor="middle" y="269">Jan 2009</text>
<text role="columnheader" x="232" text-anchor="middle" y="269">Dec 2010</text>
<text role="columnheader" x="334.5" text-anchor="middle" y="269">May 2012</text>
<text role="columnheader" x="436.5" text-anchor="middle" y="269">Jan 2014</text>
<text role="columnheader" x="539" text-anchor="middle" y="269">Jul 2015</text>
</g>
Use a g
element with role="rowheader"
to represent the header for each row in the table. Move the title
element that is the first child of the rowheader
, so it is the child of the path
element, and give the path
element role="img"
.
-
Code language
svg
COPY TO CLIPBOARD
<g role="row">
<g role="rowheader">
<path role="img" transform="translate(79,63)" d="M 51 48.5 C 51 48.5 112.5 53 153.3 56.8 C 194 61 214 68 255.5 68 C 296 68 317 67.5 357.7 67.5 C 398.5 67.5 460 105 460 105" class="s1" stroke-linecap="round">
<title>Jaws</title>
</path>
</g>
...
</g>
It's necessary to move the title element inside the path element, and to give the path element role="img" in order to fool browsers into thinking there is content inside the left-most column of the table. IE doesn't need this, but it's a workaround for other browsers.
Use a g
element with role="cell"
, to represent each of the table cells. Add role="img"
to each of the use
elements that represent the content of the cells:
-
Code language
svg
COPY TO CLIPBOARD
<g role="cell">
<use role="img" href="#points-0" x="130" y="112">
<title>74%</title>
</use>
</g>
The img
role is again used to expose the content as a graphic in the browser, with the title
element providing its accessible name.
Use the aria-hidden
attribute to hide the y axis values, the y axis label ("Percentage usage"), and the legend, from screen readers. The same information is available to screen reader users through the row and column headers, so this avoids duplication of information without removing it from the visual presentation.
A working demo of the chart has all of these changes applied.
The state of SVG accessibility support in browsers and screen readers is still highly inconsistent, even when ARIA is used to polyfill semantic information. This solution works with Jaws in Chrome and IE, but only partially in Firefox (because Jaws doesn't recognise the contents of the table cells in that browser); NVDA struggles with the ARIA table semantics in Chrome, Firefox, and IE; whilst this solution works well with VoiceOver in Safari.
The upshot is that this technique is enough to make the primary SVG content more screen reader accessible, and support is likely to improve as browsers and screen readers improve their support for SVG and ARIA, but it isn't enough to make it completely so. For this (and many other good reasons), it's a good idea to provide an alternative view of the information, for example by providing both a graphical and a tabular view of the content (as noted above).
Thanks to WebAIM for the data (taken from their screen reader surveys) used to create this line graph, and to Chaals McCathie Nevile who borrowed the original SVG chart from HighChart and started thinking about its accessibility.
https://tink.uk/accessible-svg-tables/
SVG has no native semantics for representing structures like tables, but ARIA1.1 introduces a number of roles that can be used to polyfill the necessary semantic information.
If you're using HTML, use the relevant HTML elements to create tables. The table
, thead
, tfooter
, tbody
, th
, and td
elements are purpose built to do the job. Don't make things harder for yourself by reinventing the wheel.
That said, if you're working with SVG (which doesn't have elements for representing tables), then polyfilling the missing semantics with ARIA makes the SVG content more accessible for screen reader users.
Basic SVG table
Let's take a simple SVG table as a working example.
Basic SVG table code
The SVG code looks like this:
-
Code language
HTML
COPY TO CLIPBOARD
<svg width="100%" height="100%" viewBox="0 0 370 160">
<title>Expenses</title>
<g id='rowGroup' transform='translate(0, 0)'>
<rect x='25' y='40' width='310' height='20' fill='gainsboro'/>
<rect x='25' y='76' width='310' height='20' fill='gainsboro'/>
<text x='30' y='30' font-size='18px' font-weight='bold' fill='crimson' text-anchor='middle' role="row">
<tspan x='100'>Sales</tspan>
<tspan x='200'>Expenses</tspan>
<tspan x='300'>Net</tspan>
</text>
<text x='30' y='30' font-size='18px' text-anchor='middle' role="row">
<tspan x='30' dy='1.5em' font-weight='bold' fill='crimson' text-anchor='start'>Q1</tspan>
<tspan x='100'>$ 223</tspan>
<tspan x='200'>$ 195</tspan>
<tspan x='300'>$ 28</tspan>
</text>
<text x='30' y='30' font-size='18px' text-anchor='middle' role="row">
<tspan x='30' dy='2.5em' font-weight='bold' fill='crimson' text-anchor='start'>Q2</tspan>
<tspan x='100'>$ 183</tspan>
<tspan x='200'>$ 70</tspan>
<tspan x='300'>$ 113</tspan>
</text>
<text x='30' y='30' font-size='18px' text-anchor='middle' role="row">
<tspan x='30' dy='3.5em' font-weight='bold' fill='crimson' text-anchor='start'>Q3</tspan>
<tspan x='100'>$ 277</tspan>
<tspan x='200'>$ 88</tspan>
<tspan x='300'>$ 189</tspan>
</text>
<text x='30' y='30' font-size='18px' text-anchor='middle' role="row">
<tspan id="q4" x='30' dy='4.5em' font-weight='bold' fill='crimson' text-anchor='start'>Q4</tspan>
<tspan x='100'>$ 402</tspan>
<tspan x='200'>$ 133</tspan>
<tspan x='300'>$ 269</tspan>
</text>
</g>
</svg>
Missing semantics
It looks like a table, but the samantic information exposed by SVG in the accessibility tree is not that of a table. According to the SVG Accessibility API Mappings (AAM), the exposed roles are:
graphics-document
for thesvg
element;graphics-symbol
for therect
element;group
for theg
element;group
ortext
(depending on the accessibility API) for thetext
element.
Note: the graphics-document and graphics-symbol
roles are documented in the ARIA Graphics module, and the Graphics AAM.
There is an added complication: although these roles are supported by browsers and accessibility APIs, they are not well supported by screen readers. The group
role is recognised by screen readers but is not communicated to users in this context, and neither the graphics-document
or graphics-symbol
roles is recognised at all. The text content is available to screen readers though.
Adding semantics
So in the absence of useful semantic information, we can use these ARIA roles as an accessibility polyfill:
Basic SVG table + ARIA
This SVG table looks the same, but thanks to the ARIA it can be navigated by screen reader users in the same way as an HTML table.
>ExpensesSalesExpensesNetQ1$ 223$ 195$ 28Q2$ 183$ 70$ 113Q3$ 277$ 88$ 189Q4$ 402$ 133$ 269
Basic SVG table + ARIA code
The updated code looks like this:
-
Code language
HTML
COPY TO CLIPBOARD
<svg width="100%" height="100%" viewBox="0 0 370 160">
<title>Expenses</title>
<g id='rowGroup' transform='translate(0, 0)' role="table">
<rect x='25' y='40' width='310' height='20' fill='gainsboro'/>
<rect x='25' y='76' width='310' height='20' fill='gainsboro'/>
<text x='30' y='30' font-size='18px' font-weight='bold' fill='crimson' text-anchor='middle' role="row">
<tspan role="columnheader" x='100'>Sales</tspan>
<tspan role="columnheader" x='200'>Expenses</tspan>
<tspan role="columnheader" x='300'>Net</tspan>
</text>
<text x='30' y='30' font-size='18px' text-anchor='middle' role="row">
<tspan role="rowheader" x='30' dy='1.5em' font-weight='bold' fill='crimson' text-anchor='start'>Q1</tspan>
<tspan role="cell" x='100'>$ 223</tspan>
<tspan role="cell" x='200'>$ 195</tspan>
<tspan role="cell" x='300'>$ 28</tspan>
</text>
<text x='30' y='30' font-size='18px' text-anchor='middle' role="row">
<tspan role="rowheader" x='30' dy='2.5em' font-weight='bold' fill='crimson' text-anchor='start'>Q2</tspan>
<tspan role="cell" x='100'>$ 183</tspan>
<tspan role="cell" x='200'>$ 70</tspan>
<tspan role="cell" x='300'>$ 113</tspan>
</text>
<text x='30' y='30' font-size='18px' text-anchor='middle' role="row">
<tspan role="rowheader" x='30' dy='3.5em' font-weight='bold' fill='crimson' text-anchor='start'>Q3</tspan>
<tspan role="cell" x='100'>$ 277</tspan>
<tspan role="cell" x='200'>$ 88</tspan>
<tspan role="cell" x='300'>$ 189</tspan>
</text>
<text x='30' y='30' font-size='18px' text-anchor='middle' role="row">
<tspan id="q4" role="rowheader" x='30' dy='4.5em' font-weight='bold' fill='crimson' text-anchor='start'>Q4</tspan>
<tspan role="cell" x='100'>$ 402</tspan>
<tspan role="cell" x='200'>$ 133</tspan>
<tspan role="cell" x='300'>$ 269</tspan>
</text>
</g>
</svg>
This has the advantage of being supported on multiple platforms, in multiple browsers, and with multiple screen readers. The ARIA tables demo includes information about browser and screen reader support for Windows, MacOS, iOS, and Android.
https://tink.uk/accessible-svg-flowcharts/
The accessible SVG line graphs post explains how to use ARIA table semantics to make that form of data visualisation accessible to screen readers. This article uses the same ARIA based approach to make a screen reader accessible SVG flowchart.
The example comes from the W3C Process document, and a flowchart that describes the process of taking a specification from its First Public Working Draft (FPWD) through to official W3C Recommendation (Rec). The original SVG can be found in the W3C Process 2015 document, and a revised version (with a first attempt at accessibility) is in the W3C Process 2017 document.
SVG lacks the semantics to express structures like line graphs, bar charts, or flowcharts, and so SVG data visualisations are difficult for screen reader users to understand. The structure of a flowcart can be represented as a series of nested lists, so we can use ARIA to impose these semantics for the benefit of screen reader users.
The following ARIA roles and attributes were used for the W3C flowchart:
Adding the ARIA
The first step was to create the skeleton structure of the flowchart, using the g
element as the base. Here's a fragment:
-
Code language
svg
COPY TO CLIPBOARD
<g role="list">
<g role="listitem"></g>
<g role="listitem">
<g role="list">
<g role="listitem"></g>
<g role="listitem"></g>
</g>
</g>
</g>
Once the existing SVG content had been moved into the new structure, the img
role was applied to the first child element inside each link (more on the links later), and the title
element used to give the element its accessible name, to encourage screen readers to recognise the listitems.
The aria-hidden
attribute was then used to hide the text
element from screen readers (because it duplicated the accessible name of the title
element).
-
Code language
svg
COPY TO CLIPBOARD
<g role="list">
<!-* FPWD -->
<g role="listitem">
<a xlink:href="#wd-1">
<rect role="img" fill="white" y="122" width="57" height="45">
<title>First Public Working Draft (FPWD) * Exclusion opportunity</title>
</rect>
<text font-size="8">
<tspan aria-hidden="true" y="132" x="0">First Public WD</tspan>
<tspan x="0" y="152">WG decision</tspan>
<tspan x="0" y="162">Director's approval</tspan>
</text>
<path d="M0,140h53" fill="none" stroke="#000"></path>
<polygon points="47,136 57,140 47,144"></polygon>
</a>
</g>
<!-* WD -->
<g role="listitem">
<a xlink:href="#cr-1">
<ellipse role="img" id="wd-1" ry="18" rx="38" cy="140" cx="97" stroke="black" fill="#fff">
<title>Working Draft (WD)</title>
</ellipse>
<text aria-hidden="true" font-size="14" y="144" x="97" text-anchor="middle">WD</text>
</a>
<g role="list">
<!-* New WD -->
<g role="listitem">
<a xlink:href="#wd-1">
<g role="img">
<title>Publish a new Working Draft</title>
</g>
<text font-size="8"><tspan x="30" y="92">WG Decision: review needed, or</tspan>
<tspan x="40" y="100">No change for 6 months</tspan>
</text>
<path d="M78,124C73,114 79,104 97,104 108,104 115,108 117,114" fill="none" stroke="black" stroke-dasharray="6 1"></path>
<polygon points="120,114 116,124 114,113"></polygon>
</a>
</g>
<!-* Advance to CR -->
<g role="listitem" fill="#060">
<a xlink:href="#cr-1">
<g role="img">
<title>Advance to Candidate Recommendation</title>
</g>
<text x="138" y="134" font-size="8">Director's approval</text>
<path stroke="#060" d="M135,140h81"></path>
<polygon points="211,136 221,140 211,144"></polygon>
</a>
</g>
</g>
...
</g>
...
</g>
Navigation
The SVG flowchart now has a semantically meaningful structure, but to really understand the data in a flowchart, it's helpful to be able to navigate between siblings, as well as to follow a parent/child path through it.
The flowchart in the W3C Process document attempts to provide this form of navigation using links. This makes it possible to navigate directly between the siblings in the first layer of the flowchart (using the tab key), but is also introduces some different problems:
- The links have no perceived affordance; this may be ok for pointer device users who do not need the links, but not for sighted keyboard users who do. The visual affordance of the links could be improved, but that creates a different problem; should a pointer device user click/tap on the link, the shift of focus would be so slight as to be unnoticeable and the link would appear to be broken.
- The destination of the link is not indicated to anyone. This could be resolved by separating the link from the item, but this reduces the usability of navigating from one sibling item directly to the next by introducing additional keystrokes.
- It isn't particularly scalable. Without some way of knowing when a press of the tab key moved focus out of one branch to the next, it would be too easy for keyboard users (sighted and not) to get lostt in a flowchart with multiple branches.
Although the keyboard experience still leaves something to be desired in SVG, adding ARIA goes some way to making the content more usable by screen reader users. The ARIA enabled version is included in the W3C Process 2018, and a demo version is also available. If you find any issues, you can file them on Github.