Skip to main content

Integrating ARIA roles and descriptive content for enhanced accessibility

Accessibility in web development extends beyond just accommodating keyboard-only users. Many users with varying abilities make use of assistive technologies like screen readers to effectively interact with web applications. ARIA (Accessible Rich Internet Applications) roles and states offer a powerful toolkit to improve this interaction by conveying information about the behavior and purpose of interface components.

In the context of our line chart, we'll be looking at how to implement ARIA attributes to further enhance its accessibility in addition to generating descriptive content for our graph.

info

In this tutorial we will only make use of the aria-live, aria-label, and aria-hidden attributes. You can explore a [more comprehensive list of attributes on this MDN page] (https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes).

Use of ARIA attributes in the chart

aria-live

To provide real-time updates about the chart to assistive technologies, we use the aria-live attribute. It accepts a few potential values, although "polite" and "assertive" are the most common.

With aria-live="polite", updates are presented at the user's next convenient opportunity, such as when they stop typing or when a task is completed. With aria-live="assertive", updates are presented immediately.

Our chart uses the "assertive" value because the description should be read out based on a user action (keyboard action):

<div id="chart" aria-live="assertive" tabindex="0"></div>

aria-label

The aria-label attribute is used to specify a string that labels the current element. It's useful when there isn't any text content that describes this element.

On our chart, it could read something like this:

<div
id="chart"
aria-live="polite"
aria-label="interactive line chart"
tabindex="0"
></div>

aria-hidden

The aria-hidden attribute is used to hide irrelevant or redundant information from assistive technologies. Its value is either "true" or "false".

For example, if our chart contained a decorative element with no semantic meaning, we could use aria-hidden="true" to hide it from screen readers:

<div id="decorative-element" aria-hidden="true"></div>

Adding the ARIA attributes to an existing chart via JavaScript

<!-- Template for HTML elements added to the chart container for the A11y improvements -->
<template id="a11y-helpers">
<div tabindex="-1" role="alert" aria-live="assertive"></div>
</template>

<script>
function addAriaAttributesAndAlerter(chart) {
const containerEl = chart.chartElement().parentElement;
if (!containerEl) return;
// make focusable
containerEl.tabIndex = 0;
containerEl.style.position = 'relative';
containerEl.ariaLabel =
'Line plot of Accessibility stock price. Press the H key to display the available interaction keys.';
chart.chartElement().ariaHidden = 'true';

const templateElement = document.getElementById('a11y-helpers');
const clone = templateElement.content.cloneNode(true);
containerEl.appendChild(clone);
}
</script>

Generating a description of the chart

In addition to ARIA roles, providing a textual description for our charts helps all users, especially those relying on screen readers or other assistive technologies.

You may generate a description of a chart by applying domain-specific knowledge. It's beneficial to outline the general trend or patterns of the data, highlight any notable points or anomalies, and summarize the implications of the data.

You can add a descriptive section to the chart:

<div id="chart-description" aria-live="assertive">
<!-- The content here should be dynamically generated based on the chart data -->
</div>

You can use JavaScript to change the content inside this div whenever the chart data is updated:

const descriptionElement = document.getElementById('chart-description');
descriptionElement.textContent = generateDescription(mainSeries.data());

Here, generateDescription(data) would be a function that you write to translate the chart's data into human-readable insights. The function would vary greatly based on what the charts represent and how much detail you wish to provide.

The example describes the chart based on its first and last visible data points in addition to the highest and lowest points displayed. This is used to generate a description like this:

The first price is $679.10 at Wed Sep 19 2018. The last price is $555.37 at Wed May 15 2019. The actual change in price was -$123.73, corresponding to a percentage change of -18.22%. The lowest price was $32.76 at Fri Dec 21 2018. The highest price was $951.33 at Sun Mar 24 2019.

The following code could be used as a starting point for generating chart descriptions:

function formatDate(time) {
return new Date(time * 1000).toDateString();
}

function formatValue(value) {
return `${value < 0 ? '-' : ''}$${Math.abs(value).toFixed(2)}`;
}

function getStats(data) {
const stats = {
start: data[0],
close: data[data.length - 1],
low: data[0],
high: data[0],
};

for (const point of data) {
if (point.value > stats.high.value) {
stats.high = point;
}
if (point.value < stats.low.value) {
stats.low = point;
}
}

return stats;
}

function getVisibleSeriesData(chart, series) {
const timeScale = chart.timeScale();
const visibleRange = timeScale.getVisibleLogicalRange();
const data = [];
for (let i = Math.round(visibleRange.from); i <= visibleRange.to; i++) {
const d = series.dataByIndex(i, 0);
if (d !== null) {
data.push(d);
}
}
return data;
}

function describeFinanceChart(data) {
if (!data || data.length === 0) {
return 'The data set is empty.';
}

const stats = getStats(data);

const firstPrice = `The first price is ${formatValue(
stats.start.value
)} at ${formatDate(stats.start.time)}.`;
const lastPrice = `The last price is ${formatValue(
stats.close.value
)} at ${formatDate(stats.close.time)}.`;

const actualChange = stats.close.value - stats.start.value;
const percentChange = (actualChange / stats.start.value) * 100;

const changeDescription = `The actual change in price was ${formatValue(
actualChange
)}, corresponding to a percentage change of ${percentChange.toFixed(2)}%.`;

let lowHigh = '';
if (
stats.low.time !== stats.start.time &&
stats.low.time !== stats.close.time
) {
lowHigh += `The lowest price was ${formatValue(
stats.low.value
)} at ${formatDate(stats.low.time)}.`;
}
if (
stats.high.time !== stats.start.time &&
stats.high.time !== stats.close.time
) {
lowHigh += ` The highest price was ${formatValue(
stats.high.value
)} at ${formatDate(stats.high.time)}.`;
}

return `${firstPrice} ${lastPrice} ${changeDescription} ${lowHigh}`.trim();
}

Semantic HTML

Using Semantic HTML elements offers several benefits. Firstly, they enhance the accessibility of web content as they provide specific meaning to the browser and assistive technology like screen readers, helping them understand the content's structure and purpose. This is crucial for users with disabilities.

It is suggested that the container provided to the createChart method should use a semantic element such as <figure> instead of a generic element like div.

<figure id="chart"></figure>

In the next part, we'll discuss contrast control and font scaling.