# How to Add Optional Accessible Motion to Diagrams in Diagram-Design

> Enhance diagrams with optional accessible motion using Diagram-Design. Add CSS animations and JavaScript controls for dynamic visuals while ensuring static fallbacks for all users.

- Repository: [Cathryn Lavery/diagram-design](https://github.com/cathrynlavery/diagram-design)
- Tags: how-to-guide
- Published: 2026-09-12

---

**Diagram-Design supports optional accessible motion through a lightweight HTML contract that progressively enhances SVG-based diagrams with CSS animations and JavaScript controls while maintaining a fully static fallback for users who prefer reduced motion.**

The [Diagram-Design](https://github.com/cathrynlavery/diagram-design) repository provides a declarative system for adding motion to diagrams without sacrificing accessibility standards. By implementing a specific data-attribute contract defined in [`skills/diagram-design/assets/template-motion.html`](https://github.com/cathrynlavery/diagram-design/blob/main/skills/diagram-design/assets/template-motion.html), you can create animated visualizations that automatically degrade to accessible, static alternatives when users have motion sensitivities or disabled JavaScript.

## Understanding the Motion Contract Architecture

The motion system centers on a single root controller coordinating multiple motion items. This architecture is enforced by continuous integration validators to ensure consistent accessibility across all diagrams.

### The Root Controller (data-motion-root)

Every motion-enabled diagram requires exactly one container element marked with `data-motion-root`. As defined in the template source, this element accepts configuration attributes that determine animation behavior:

- **`data-motion-mode`**: Set to `"step"` for step-through animations or `"loop"` for infinite cycling
- **`data-step-count`**: Integer defining total steps (maximum 12, strictly enforced by CI)
- **`data-frame="static"`**: Indicates the initial static fallback state before JavaScript activation

The root element acts as the central controller that manages visibility states and synchronization across all child motion items.

### Motion Items and Semantic Structure

Individual animation steps are marked with `data-motion-item` attributes and indexed using `data-step="N"` (1-based indexing). The contract enforces strict separation between semantic and decorative content:

**Semantic items** convey meaning and must include descriptive `aria-label` attributes. **Decorative items**—such as connector arrows or background fills—require `data-motion-decorative` combined with `aria-hidden="true"` and `focusable="false"` to ensure assistive technology ignores them.

The validator [`scripts/verify-semantic-motion.py`](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/verify-semantic-motion.py) specifically checks that non-decorative items carry meaningful labels and that decorative elements are properly hidden from the accessibility tree.

### Playback Controls and Live Regions

User interface controls reside within a container marked `data-motion-controls`. Individual buttons use `data-motion-action` attributes accepting values including `"play"`, `"pause"`, `"prev"`, `"next"`, and `"replay"`.

For screen reader users, a live region with `role="status"` and `aria-live="polite"` provides immediate spoken updates about the current step. This element uses `data-motion-status` and remains visually hidden while announcing changes to assistive technology.

## Implementing the Motion Contract

To add optional accessible motion, import the motion template and implement the data-attribute contract within your SVG markup. The following example demonstrates a three-step process flow with decorative connectors and full accessibility support:

```html
<link rel="stylesheet" href="assets/template-motion.html">

<main
  data-motion-root
  data-motion-mode="step"
  data-step-count="3"
  data-step-current="3"
  data-frame="static"
  data-static-frame="complete"
>
  <h1>Sample Process Flow</h1>

  <svg viewBox="0 0 600 200" role="img" aria-labelledby="sample-title sample-desc">
    <title id="sample-title">Sample Process Flow</title>
    <desc id="sample-desc">
      A three-step process that shows request → validation → completion.
    </desc>

    <g data-motion-item data-step="1" aria-label="Step 1: Request received">
      <rect x="20" y="50" width="150" height="80" fill="#f5f5f5" stroke="#2d3142"/>
      <text x="95" y="100" text-anchor="middle" fill="#2d3142">Request</text>
    </g>

    <path
      data-motion-item
      data-motion-decorative
      aria-hidden="true"
      focusable="false"
      d="M170 90 H380"
      stroke="#4f5d75"
    />

    <g data-motion-item data-step="2" aria-label="Step 2: Validation passed">
      <rect x="380" y="50" width="150" height="80" fill="rgba(235,108,36,0.08)" stroke="#eb6c36"/>
      <text x="455" y="100" text-anchor="middle" fill="#eb6c36">Validate</text>
    </g>

    <path
      data-motion-item
      data-motion-decorative
      aria-hidden="true"
      focusable="false"
      d="M530 90 H590"
      stroke="#4f5d75"
    />

    <g data-motion-item data-step="3" aria-label="Step 3: Process complete">
      <rect x="590" y="50" width="150" height="80" fill="#f5f5f5" stroke="#2d3142"/>
      <text x="665" y="100" text-anchor="middle" fill="#2d3142">Done</text>
    </g>
  </svg>

  <div data-motion-controls role="group" aria-label="Diagram playback controls">
    <button type="button" data-motion-action="prev">Previous</button>
    <button type="button" data-motion-action="play" aria-pressed="false">Play</button>
    <button type="button" data-motion-action="pause" aria-pressed="true">Pause</button>
    <button type="button" data-motion-action="next">Next</button>
    <button type="button" data-motion-action="replay">Replay</button>
    <span data-motion-status-visible aria-hidden="true">
      Step <span data-motion-step-label>3</span> of 3
    </span>
  </div>

  <p class="sr-only" data-motion-status role="status" aria-live="polite" aria-atomic="true">
    Step 3 of 3 – Request received → Validation passed → Process complete.
  </p>
</main>

```

The [`template-motion.html`](https://github.com/cathrynlavery/diagram-design/blob/main/template-motion.html) asset provides CSS animations and a `@media (prefers-reduced-motion: reduce)` media query that automatically disables animations, forces all items to full visibility, and hides playback controls when users prefer reduced motion.

## Automated CI Validation

The repository enforces accessibility compliance through two Python validators that run in continuous integration:

**[`scripts/verify-motion.py`](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/verify-motion.py)** performs structural validation, ensuring:
- Exactly one `data-motion-root` exists per diagram
- No more than 12 motion items are present
- The reduced-motion CSS media query is implemented
- A `<noscript>` fallback element is provided for non-JavaScript environments

**[`scripts/verify-semantic-motion.py`](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/verify-semantic-motion.py)** checks accessibility semantics, verifying:
- Non-decorative items have meaningful `aria-label` attributes
- Decorative items use `aria-hidden="true"` and `focusable="false"`
- Status regions implement `role="status"` and `aria-live="polite"` correctly

Both scripts prevent merging code that violates the motion contract, ensuring all diagrams remain accessible by default.

## Summary

- **Optional accessible motion** relies on a declarative HTML contract using `data-motion-root` and `data-motion-item` attributes
- The system supports **step-through** (`data-motion-mode="step"`) or **loop** modes with a maximum of 12 steps enforced by [`verify-motion.py`](https://github.com/cathrynlavery/diagram-design/blob/main/verify-motion.py)
- **Decorative elements** must use `data-motion-decorative` with `aria-hidden="true"` to remain invisible to assistive technology
- **Reduced-motion fallback** activates automatically via CSS media queries in [`template-motion.html`](https://github.com/cathrynlavery/diagram-design/blob/main/template-motion.html), displaying a static version of the diagram
- **CI validation** through [`verify-motion.py`](https://github.com/cathrynlavery/diagram-design/blob/main/verify-motion.py) and [`verify-semantic-motion.py`](https://github.com/cathrynlavery/diagram-design/blob/main/verify-semantic-motion.py) guarantees compliance with WCAG guidelines

## Frequently Asked Questions

### How does the motion system handle users who prefer reduced motion?

The system uses a `@media (prefers-reduced-motion: reduce)` media query defined in [`template-motion.html`](https://github.com/cathrynlavery/diagram-design/blob/main/template-motion.html) that forces all `data-motion-item` elements to full visibility, disables CSS transitions and animations, and hides the `data-motion-controls` container. This happens automatically at the browser level without JavaScript detection, ensuring users with vestibular disorders receive a static, readable diagram immediately upon page load.

### What is the maximum number of animation steps supported?

The contract enforces a maximum of 12 steps per diagram, as validated by [`scripts/verify-motion.py`](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/verify-motion.py). This limit prevents cognitive overload and ensures step-through interfaces remain manageable. The validator checks the `data-step-count` attribute against the actual number of `data-motion-item` elements with unique `data-step` values. If your diagram requires more than 12 steps, refactor it into multiple linked diagrams or consolidate conceptual steps.

### How do I mark decorative elements versus semantic content?

Semantic content—such as process steps or data points—requires a descriptive `aria-label` attribute on the `data-motion-item` element. Purely visual decorations like arrows or backgrounds must include `data-motion-decorative`, `aria-hidden="true"`, and `focusable="false"` attributes. The [`verify-semantic-motion.py`](https://github.com/cathrynlavery/diagram-design/blob/main/verify-semantic-motion.py) script validates this distinction, failing builds where decorative elements lack proper hiding attributes or where semantic items lack labels.

### Is JavaScript required to view motion-enabled diagrams?

No. Optional accessible motion is implemented as **progressive enhancement**. When JavaScript is unavailable, the `data-frame="static"` attribute ensures the diagram renders in its complete state, and the required `<noscript>` element provides a text description of the full process. Motion controls and step-by-step animations only activate when JavaScript successfully loads, ensuring the core informational content remains accessible in all browsing contexts.