Accessibility Recipes
The package renders accessible labels and can make notes or edit handles focusable. Host apps still own the surrounding keyboard model and any external annotation list.
Static SVG
- Pass a layer
titletorenderAnnotationsSvg. - Give important notes
noteTabIndex: 0when they should be keyboard reachable. - Use
editHandleTabIndex: 0only when the host delegates keyboard actions for edit handles. - Provide
note.ariaLabelwhen the title/body alone is not enough.
React
- Use
AnnotationLayerwithnoteTabIndexfor focusable notes. - Use
editableand edit callbacks to emit suggested anchor or placement patches. - Keep persisted state in the host app, not the annotation package.
- Use
onQualityandonTargetAlignmentto surface generated-report issues in tests or authoring tools.
Host Chart And Diagram Semantics
The annotation layer supplements the host graphic's accessibility rather than
replacing it. Any underlying SVG with role="img" needs an accessible name,
such as an aria-label or <title> describing the chart or diagram. Generated
Vega marks that convey meaning should supply an accessible description in
the mark encoding; purely decorative marks can instead be marked aria: false.
The React and Vega examples demonstrate this alongside the annotation layer. The public-site browser checks run axe-core over every compiled example at both narrow and desktop widths, including the host graphics.
Interactive SVG Layer Semantics
Passive SVG output is labeled as a single image (role="img"). Once the
renderAnnotationsSvg helper receives a finite noteTabIndex or
includeEditHandles, the root changes to a named group (role="group"), preserving
independently focusable notes and edit controls in the accessibility tree.
The React AnnotationLayer uses the same distinction: providing noteTabIndex,
rendering editable handles, or supplying a custom note renderer results in a
named group. Its default noninteractive layer retains the image role.
Keep the layer's title, ariaLabel, or React label descriptive. Do not
force an image role back onto interactive SVG content: screen readers may
otherwise hide its keyboard-reachable descendants. The React Flow fixture is
checked under axe-core on both narrow and desktop layouts to guard this.
External Note Lists
For dense reports, consider a host-owned list that mirrors annotation ids:
- list item points to
data-annotation-id - list focus moves to the annotation note
- annotation note focus updates the list selection
- screen-reader summary uses validation and layout-quality reports
The clean-consumer dogfood report enforces this pattern in
npm run test:dogfood: it renders an External Note List, uses roving focus on
host-owned buttons, supports keyboard activation, focuses the matching SVG note
on activation, updates the list selection when a note receives focus, and exposes a live
screenReaderSummary with validation, target-alignment, and layout-quality
counts.
const notes = Array.from(
document.querySelectorAll<SVGGElement>('.pa-annotation__note[data-annotation-id]')
);
const buttons = notes.map((note, index) => {
const id = note.dataset.annotationId!;
const button = document.createElement('button');
note.id = `annotation-note-${id}`;
button.type = 'button';
button.dataset.noteListId = id;
button.setAttribute('aria-controls', note.id);
button.setAttribute('aria-current', index === 0 ? 'true' : 'false');
button.tabIndex = index === 0 ? 0 : -1;
button.addEventListener('click', () => note.focus({ preventScroll: true }));
return button;
});
Keep the roving focus state in the host list. The annotation package exposes stable ids and focusable note groups, but it does not own the report's keyboard model.
Known Limits
The package does not implement roving focus, command palettes, persistence, or workflow state. Those remain host-app responsibilities.