Writing Accessible Activities
This guide is a set of practical recipes for the everyday accessibility of a Doenet activity: describing images and graphs, labelling inputs, writing good links, structuring with headings, and building accessible tables. Each recipe clears a check from the accessibility report and, more importantly, makes your activity usable by someone who can’t see it.
These recipes supply the information the accessibility checker looks for. But clearing a check is the floor, not the goal — a description has to actually be useful. For why that distinction matters, see Accessibility in Doenet.
Most of these recipes use two children you can add to many components:
<shortDescription>— a brief description read by screen readers only. It is not shown on screen, so it is where you put information a sighted reader already gets from the visuals.<description>— longer information shown to everyone, in a disclosure or popup. Use it for detail that helps all readers.
Describe graphs, images, and videos
A screen reader can’t see an image, a graph, or a video, so each one needs a <shortDescription> that conveys what it shows. Without one, the checker reports a violation.
Put the <shortDescription> as a child of the visual component. Write it for someone who can’t see the image and needs to know what it depicts:
A <caption> (shown to everyone) is not a substitute for a <shortDescription> (read to screen readers): the caption above says that the squirrel is determined, while the short description says what is in the picture. Pair them.
Add longer detail with <description>
When there is more to say than belongs in a short description — background, a source credit, a fuller account of a complex figure — add a <description>. It is shown to every reader in a disclosure, so both sighted and screen-reader users get it:
Mark a purely decorative image
If an image carries no information — a border flourish, a background texture — it should be skipped by screen readers rather than described. Mark it decorative and the checker won’t ask for a description:
<image source="decorative-divider.png" decorative />Only mark something decorative if a reader truly loses nothing by not knowing it is there. The same attribute works on a <graph> used purely for decoration.
A screen reader reads a <shortDescription> as plain text, so don’t put math components like <m> inside one — they are read out as raw notation. Spell the math out in words (“x squared”, not <m>x^2</m>). The checker flags embedded math as an advisory.
Graphs follow the same rule and have an entire guide of their own — see Accessible Interactive Graphs. A <video> also needs a <shortDescription> (a video can’t be marked decorative).
Label every input
Every input — <mathInput>, <textInput>, <choiceInput>, <booleanInput>, <matrixInput> — needs a label a screen reader can announce. Ordinary text next to an input is not a label: it looks like one to a sighted reader, but nothing connects the two, so a screen reader announces the input as unlabeled. There are three ways to make the connection, depending on your layout.
A visible label, with <label>
The usual case: put a <label> inside the input. It renders next to the input and is programmatically tied to it.
A label placed elsewhere, with <label for="...">
When your layout needs the label to sit somewhere other than right on the input, write a <label> with a for attribute pointing at the input’s name. The two are associated even though they’re written separately.
A screen-reader-only label, with <shortDescription>
When a visible label would just duplicate the surrounding sentence, use a <shortDescription> instead. It gives the screen reader a label without adding anything to the screen — useful for inputs embedded in prose:
Labelling answers
An <answer> needs a label too, but where it goes depends on how the answer is written:
-
The answer creates its own input (there is no explicit
<*Input>inside it). Put the<label>inside the answer: -
The answer contains an explicit input. Put the
<label>on the input, not on the answer:
An answer that checks a condition with <award><when> (rather than creating an input) has nothing to label — keep its prompt as ordinary text beside it. That pattern comes up with interactive graphs; see the check-work pattern.
Write descriptive links
A link’s text should make sense on its own. Screen-reader users often pull up a list of a page’s links to navigate by; a list of identical “here”s or “this link”s tells them nothing. Write the <ref> so its text describes the destination:
Avoid see <ref to="...">here</ref> — “here” describes nothing. Make the meaningful phrase the link.
Build structure with headings
Screen-reader users navigate long documents by jumping from heading to heading, so a correct heading structure is essential. In Doenet you don’t choose heading levels — you nest sectioning components, and each <title> becomes a heading at the right level automatically. A <section> inside another section is a sub-heading of it.
Here “Limits” is a top-level heading and “One-sided limits” is nested one level below it. Because the levels are computed from the nesting, they stay correct no matter how you rearrange the document — which you could never guarantee by, say, bolding a line to look like a heading. Don’t fake a heading with styled text; use a sectioning component so it is a real, navigable heading.
Make tables accessible
Use a <tabular> for data that is genuinely tabular, and mark the header row with header on its <row>. A screen reader then announces each data cell together with its column header, so a listener can tell which column a value is in.
Two cautions, both standard table-accessibility advice cast in DoenetML:
- Don’t use a table for visual layout. A table tells a screen reader “this is a grid of related data.” To place things side by side without that meaning, use a
<sideBySide>instead. - Wrap the
<tabular>in a<table>when you want a numbered, titled table you can cross-reference; the<tabular>alone is just the grid.
Choose colors with enough contrast
Color carries meaning only for readers who can see it and tell the colors apart, so two rules apply. First, never let color be the only signal of a distinction — Doenet’s style numbers already pair each color with a different marker shape or dash pattern for exactly this reason. Second, keep enough contrast: when you redefine a color with a <styleDefinition>, Doenet checks its contrast against the canvas and reports a violation if it is too low. You don’t have to compute ratios yourself — just watch the accessibility report. The reasoning behind all of this is in Styling, meaning, and accessibility.
Put extra guidance where everyone can reach it
It is tempting to tuck a formatting hint into a visual aside — “(type sqrt for a square root)”. A sighted reader sees it; a screen-reader user working through the input may never reach it. Put guidance an input needs into a <description> on that input, which is shown to everyone and announced with the input:
For longer supporting material, the same principle points to components every reader can open: a <hint> for an on-demand nudge, a <footnote> for an aside, or an <aside> for a note set off from the main flow. Each is reachable by keyboard and announced to screen readers — unlike text whose only cue is that it looks different.
Where to go next
- Checking Accessibility — confirm these recipes cleared the report.
- Accessible Interactive Graphs — descriptions that update, keyboard controls, and graph annotations.
- Accessibility in Doenet — the reasoning behind the recipes.