Topic 15.2
Short and Long Descriptions
In one line
A chart needs a short name that says the finding and a longer description that gives the shape, the extremes and the exceptions. Write both from the data, so they cannot disagree with the picture.
Think of it like this
A radio sports presenter describing a race to listeners. "Blue pulls ahead on the last bend" is the name: one sentence, the finding. Then a few more sentences give what a viewer would see: who started, who led, the gap at the end. A good presenter never reads every split time, and never says only "a race".
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- short description
- One or two sentences that say what the chart is and, ideally, its finding. It becomes the accessible name.
- long description
- A paragraph with the shape, the extremes and the exceptions. It is joined to the chart as the accessible description.
- aria-describedby
- An attribute that points at another element by id, whose text becomes this element's accessible description.
- figure and figcaption
- HTML elements for a self-contained picture and its caption. The browser knows the caption belongs to the picture.
- generated description
- A description a function writes from the chart's own data, so it always matches.
- stale text
- Words that were true for an earlier version of the data and now say something else.
Step by step
01Start from a chart with no words
The panel on the right shows one week of Lumen rides as seven hand-drawn bars. It has no role and no name. The Console says so. Nothing is wrong with the picture; it just has no words yet.
02Add the role and a short name
One change: role="img" and an aria-label that says what the chart is. A screen reader can now announce the chart, and the name says "Rides per day, 2 to 8 June 2025". It does not say which day was quiet.
03Add a visible long description
A paragraph under the chart says the finding in words: Monday was the quietest (the rainy day), Tuesday the busiest. aria-describedby joins it to the chart. Everyone can read the paragraph, not only a screen reader user.
04Group them in a figure
Wrapping the chart and its paragraph in <figure> with a <figcaption> tells the browser they belong together. The Console confirms that the chart sits inside a figure. Use this when the text is truly a caption for the chart.
05Add the table twin
The same seven numbers as a real table, inside a <details> that opens on demand. It is the longest description there is: every value. Topic 15.3 builds it properly.
Try it yourself
- 1
Add a row with NaN
In the first example, add a case before
"a gap":"NaN row": [{ date: day("2025-06-02"), rides: 2979 }, { date: day("2025-06-03"), rides: NaN }],. The Console printsNaN row: Line chart of rides per day. One day with a number: 2 Jun 2025, 2,979 rides (1 day has no number and shows as a gap).The word NaN never reaches a person, and the gap is counted. - 2
Bring one day back after empty
In the second example, add after the last
show:june.push({ date: day("2025-06-02"), rides: 2979 }); show("4. one day back");. The Console printsdescribedby: desc-1anddescription paragraphs on the page: 1, with the textOne day with a number: 2 Jun 2025, 2,979 rides.One paragraph carries every state.
Code & diagrams
describeRides is the function the dashboard uses (src/parts/describe.js). The first four cases are made-up rows. The last line reads the real daily.csv. Dates are UTC midnight, like d3.autoType makes them (Topics 4.2 and 4.3).
Console
(nothing was printed)Expected console output
empty: Line chart of rides per day: no data. There are no rides to show.
one day: Line chart of rides per day. One day with a number: 2 Jun 2025, 2,979 rides.
all equal: Line chart of rides per day. 2 days, from 2 Jun 2025 to 3 Jun 2025. Every day had 100 rides.
a gap: Line chart of rides per day. 3 days, from 2 Jun 2025 to 4 Jun 2025 (1 day has no number and shows as a gap). It starts at 2,979 rides, peaks at 3,759 on 4 Jun 2025 and ends at 3,759. The quietest day is 2 Jun 2025, with 2,979 rides.
full year: Line chart of rides per day. 365 days, from 1 Jan 2025 to 31 Dec 2025. It starts at 1,193 rides, peaks at 4,380 on 18 Jul 2025 and ends at 910. The quietest day is 14 Aug 2025, with 439 rides (app down).labelChart reuses the paragraph that follows the chart, so the id stays desc-1 for every update. Three states: first draw, new data, no data.
Preview
Console
Nothing logged yet.Expected console output
1. first draw
aria-label: Line chart of rides per day
describedby: desc-1 | description paragraphs on the page: 1
text: 3 days, from 2 Jun 2025 to 4 Jun 2025. It starts at 2,979 rides, peaks at 3,992 on 3 Jun 2025 and ends at 3,759. The quietest day is 2 Jun 2025, with 2,979 rides.
2. after the data changed
aria-label: Line chart of rides per day
describedby: desc-1 | description paragraphs on the page: 1
text: 3 days, from 2 Jun 2025 to 4 Jun 2025. It starts at 2,979 rides, peaks at 4,100 on 4 Jun 2025 and ends at 4,100. The quietest day is 2 Jun 2025, with 2,979 rides.
3. after the data was emptied
aria-label: Line chart of rides per day: no data
describedby: desc-1 | description paragraphs on the page: 1
text: There are no rides to show.Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Add a new paragraph at every update
In the second example, change if (!note || !note.classList.contains("desc")) { to if (true) {, so a new paragraph is made on every call.
Myth vs fact
Myth
A long description should list every value.
Fact
That is the table's job (Topic 15.3). The description gives the shape and the places to look.
Myth
Describe the colours and the line style.
Fact
Colour rarely matters to the answer. Say what the data does. Mention colour only when the chart's legend uses it to tell groups apart and the words do not.
Myth
Write the description once, by hand, when the chart is made.
Fact
The data changes and the words do not. Generate the words from the same rows the chart draws.
Myth
Hidden descriptions are fine, because only screen readers need them.
Fact
People with low vision, with a cognitive difference or in a hurry read a visible description too, and a visible one gets checked by the team.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Chart libraries often write a description for you, but the words say what the library knows (series names, point counts), not what matters here. Treat library text as a starting point and test it with real data.
- ▸
A description in the page's language needs the right
langon the page (and on a quoted phrase in another language). Dates and numbers come from one formatter (formatDay,formatNumber), so the text uses the same style as the chart's labels. - ▸
A description over about 150 words is rarely read. When the description grows, move detail into the table and keep the paragraph to range, peak, low, end and the exception.
Remember this
- 1
The problem this solves: one short name cannot hold a year of data. A person who cannot see the chart still wants the shape, the highest and lowest points, and anything odd. Without that, the reader has the table (Topic 15.3) and nothing to tell them where to look.
- 2
Two layers: a short name and a long description. The W3C's guidance on complex images gives the same advice: a short description that says what the image is, and a long description that gives the detail. For a chart: the name says what it shows (and ideally the finding), the description gives the range, the peak, the low, the end and what is unusual.
- 3
Write the description from the data. Hand-written text goes stale: the data changes, the picture updates, and the old words stay. A function that takes the same rows the chart is drawn from and returns the sentences cannot disagree with the picture. This is
describeRidesin the build (Topic 15.7). - 4
Cover the cases, not only the usual one. Empty data says "no data". One point says one day, not "a line from X to X". All values equal says so instead of "peaks at 100 and quietest 100". Rows with no number are counted and named as gaps. The first example runs all of these through one function.
- 5
**Join it with
aria-describedby, and show it.**aria-describedbypoints at an element by id: the browser takes that element's text as the accessible description. A visible paragraph under the chart serves everyone (people who are low-vision, people who are new to the chart, people who want a quote for a report). A hidden description helps only one group, and nobody can check that it is right. Wrap the chart and its text in<figure>and<figcaption>when the text is a caption (WCAG 1.3.1 Info and Relationships). - 6
Keep it in step when the data updates. Charts update: a new month, a new range, a refresh. Change the same paragraph in place (the same id), and call the description function again every time the chart redraws. The second example shows the id staying the same and the text following the data. A page that adds a new paragraph on every update leaves stale paragraphs behind (see Break it).
Explain it without notes
Why write a short description and a long description?
Why generate the description from the data?
What does aria-describedby do?
Which edge cases should a description function handle?
Practice
Write describeBars(rows) for rows { name, rides } that returns one sentence naming the top station and its rides, and "No stations" for an empty list.
Change the second example so the description paragraph is a <figcaption> inside a <figure>. What changes in the accessible tree and in the code?
Trade-offs
- ↔
A generated description is always true and reads a little stiff. A hand-written one reads better and goes stale. Generate the facts and let a person add one sentence of context where it matters.
- ↔
Putting the description on screen costs space. It is worth it: it is the sentence most readers read first.
Done when you can
I can write a short name that says the finding and a long description with the shape and extremes.
I can join a visible paragraph to a chart with aria-describedby.
I generate descriptions from the same rows the chart draws.
My description function handles empty, one-point, equal and gap cases.
I update the same paragraph in place and never add a second one.