How to Add Accessible Motion to Diagrams Using cathrynlavery/diagram-design
Diagram-Design provides a WAI-ARIA-compatible motion system with four configurable modes that enhances static SVG diagrams while guaranteeing full accessibility through a static-first enhancement contract.
The cathrynlavery/diagram-design repository generates editorial-style static diagrams by default, but includes an optional motion framework defined in skills/diagram-design/references/animation.md. This system allows you to add deterministic animations to HTML/SVG diagrams without sacrificing semantic meaning or violating accessibility standards for users with vestibular disorders or screen readers.
Understanding the Static-First Enhancement Contract
The motion system enforces a strict static-first enhancement contract to ensure diagrams remain fully interpretable regardless of animation state. According to this contract, the complete diagram—including all semantic nodes, labels, and relationships—must be present in the DOM before any JavaScript animation executes.
Only selectors within the .motion-ready scope may hide or transform elements, guaranteeing that static captures, print views, or reduced-motion environments always display the full meaning. The contract is enforced by the verification suite in scripts/verify-motion.py and scripts/verify-semantic-motion.py, which check that diagrams maintain proper static fallbacks and semantic integrity.
The Four Motion Modes
The system supports four motion behaviors selected via the data-motion-mode attribute on the root HTML element. Each mode serves distinct content strategies while maintaining accessibility compliance.
none (Static Default)
Setting data-motion-mode="none" produces fully static output with no JavaScript execution. This is the default for print media, screenshot generation, and environments where motion provides no pedagogical value. The diagram renders immediately as a complete SVG without loading the animation controller.
reveal (Deterministic Autoplay)
The reveal mode executes one deterministic autoplay run that concludes in a complete static frame. Use this for short, ordered explanations where a single play-through sufficiently communicates the sequence. The animation ends automatically, leaving the diagram in its final, fully accessible state without requiring user interaction.
step (Interactive Playback)
The step mode provides paused, interactive playback with native Play, Pause, Replay, Previous, and Next controls. This mode is ideal for teaching scenarios, policy-trace visualization, or any context requiring user-driven pacing. The controller in assets/template-motion.html automatically generates focusable buttons (<button data-motion-action="play">, etc.) with standard keyboard bindings including ArrowRight, ArrowLeft, Home, End, Space, and unmodified R for replay.
loop (Decorative)
Setting data-motion-mode="loop" creates non-semantic decorative motion that repeats without altering the diagram's meaning—such as a rotating token indicating flow direction. These elements carry aria-hidden="true" and focusable="false" to ensure screen readers ignore the decorative animation while preserving the underlying static content.
Implementing Motion in Your Diagrams
Starting from the Template
All motion implementations must begin with the canonical controller template to ensure linter compliance. Copy the template without modifying the inline controller script, as scripts/lint-skin.py will reject any unauthorized changes to the motion logic.
cp skills/diagram-design/assets/template-motion.html my-policy-trace.html
The template automatically prefixes IDs to avoid collisions when embedding multiple diagrams, and includes the live status region required for screen reader announcements.
Marking Elements for Animation
Within your SVG markup, annotate sequential elements using data-motion-item and data-step attributes. The controller uses these selectors to manage visibility states while maintaining DOM presence.
<g data-motion-item data-step="1" class="is-visible">
<rect width="200" height="40" />
<text x="10" y="25">Request received</text>
</g>
<g data-motion-item data-step="2">
<rect width="200" height="40" y="50" />
<text x="10" y="75">Validate token</text>
</g>
Ensure your root element declares the mode and step count:
<html data-motion-mode="step" data-step-count="4">
Configuring Accessibility Attributes
Every motion-enabled diagram must provide semantic descriptors and live regions. The template includes a status region using role="status" with aria-live="polite" and aria-atomic="true" to announce step changes without overwhelming assistive technology streams.
<div role="status" aria-live="polite" aria-atomic="true"></div>
The SVG itself should contain <title> and <desc> elements describing the complete static meaning of the diagram, ensuring users with reduced-motion preferences receive identical cognitive content to animated viewers.
Validating Motion Accessibility
Before deploying diagrams, run the verification suite to confirm compliance with the static-first contract and accessibility policies.
python3 scripts/verify-motion.py path/to/diagram.html
python3 scripts/lint-skin.py path/to/diagram.html
The verify-motion.py script checks for proper mode declarations, correct step attributes, motion budget limits, and static fallback completeness. For runtime integration, the packaged checker at skills/diagram-design/scripts/self_check.py allows automated agents to validate generated diagrams programmatically.
Respecting User Preferences and ARIA Standards
The motion system implements a prefers-reduced-motion media query that automatically disables all playback controls, hides decorative overlays, and forces the diagram into its final static frame when users indicate motion sensitivity. This behavior requires no additional configuration beyond the standard template inclusion.
Keyboard navigation is built-in for step mode, with all control buttons being natively focusable and operable via standard key bindings. Decorative motion elements in loop mode explicitly declare aria-hidden="true" to prevent screen reader verbosity while maintaining visual interest for sighted users.
Summary
- Static-first contract: The complete diagram must exist in the DOM before animation; only
.motion-readyscoped selectors may transform elements. - Four motion modes:
none(static),reveal(autoplay once),step(interactive controls), andloop(decorative only). - Template requirement: Always start from
assets/template-motion.htmland do not modify the inline controller script to passlint-skin.pyvalidation. - Accessibility markers: Use
data-motion-itemanddata-stepfor sequencing,role="status"witharia-live="polite"for announcements, andaria-hidden="true"for decorative elements. - Verification workflow: Run
verify-motion.pyandlint-skin.pyto ensure compliance with WAI-ARIA standards and the static-first enhancement contract. - User preference support: The system automatically respects
prefers-reduced-motionand provides full keyboard navigation for interactive modes.
Frequently Asked Questions
What is the static-first enhancement contract in Diagram-Design?
The static-first enhancement contract is a core accessibility principle requiring that the complete diagram—with all semantic nodes, labels, and relationships—must be present in the DOM prior to any animation initialization. This ensures that if JavaScript fails, motion is disabled, or a user prefers reduced motion, the diagram remains fully interpretable as a static image. The contract is enforced by scripts/verify-motion.py, which rejects any implementation that hides essential content outside the .motion-ready scope.
How do I disable motion for users who prefer reduced motion?
You do not need to implement custom logic. The controller in template-motion.html includes a prefers-reduced-motion media query that automatically disables playback controls, suppresses decorative animations, and forces the diagram into its final static frame when users indicate motion sensitivity at the operating system level. This ensures compliance with WCAG guidelines without requiring manual intervention from diagram authors.
Can I modify the motion controller script in template-motion.html?
No. The inline controller script in assets/template-motion.html is canonical and must not be altered. The scripts/lint-skin.py tool will reject any diagrams containing modified controller logic. You should copy the template file to create new diagrams, swap out the SVG content and IDs (which are automatically prefixed), and use the provided data-* attributes to customize behavior, while leaving the controller implementation untouched.
How do I verify that my animated diagram meets accessibility standards?
Run the verification suite using python3 scripts/verify-motion.py path/to/diagram.html to check mode declarations, step attributes, static fallbacks, and budget limits. Follow this with python3 scripts/lint-skin.py path/to/diagram.html to validate HTML accessibility policies and motion compliance. For automated workflows, integrate skills/diagram-design/scripts/self_check.py to programmatically validate generated diagrams against the repository's accessibility contracts.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →