# Motion Controller Contract in diagram-design: Validation Rules and Canonical Implementation

> Explore the motion controller contract in diagram design. Learn how strict HTML schema validation ensures consistent, accessible animated diagrams with canonical JavaScript controllers.

- Repository: [Cathryn Lavery/diagram-design](https://github.com/cathrynlavery/diagram-design)
- Tags: deep-dive
- Published: 2026-09-11

---

**The motion controller contract in diagram-design is a strict HTML schema enforced by [`verify-motion.py`](https://github.com/cathrynlavery/diagram-design/blob/main/verify-motion.py) that mandates specific data attributes, accessibility annotations, and an exact canonical JavaScript controller to ensure animated diagrams remain consistent, accessible, and performant across all browsers and assistive technologies.**

The cathrynlavery/diagram-design repository defines a rigorous **motion controller contract** governing how optional animations are embedded in diagram HTML files. This contract ensures that motion-enabled diagrams meet strict accessibility standards—including reduced-motion fallbacks and screen-reader compatibility—while maintaining behavioral consistency through a validator that parses and enforces every structural requirement.

## Core Elements of the Motion Controller Contract

The contract specifies precise markup requirements across seven domains, validated in [`scripts/verify-motion.py`](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/verify-motion.py) using a custom `MotionParser` class.

### Root Element and Mode Attributes

Every motion-enabled diagram must contain **exactly one** element carrying `data-motion-root`. The validator explicitly checks `len(parser.roots) != 1` and raises the error *"expected exactly one data-motion-root"* if this constraint is violated according to the source at lines 84-86 of [`verify-motion.py`](https://github.com/cathrynlavery/diagram-design/blob/main/verify-motion.py).

The `data-motion-mode` attribute must belong to a strict set defined in the `MODES` constant. Valid values are `none`, `reveal`, `step`, or `loop`, enforced at lines 90-92 against the set defined at lines 16-18.

### Step Count and Motion Item Constraints

The `data-step-count` attribute must be an ASCII decimal integer bounded between `0` (for `none` mode) and `8`. The validator parses and bounds this value in the `verify` logic at lines 92-100.

A diagram may contain up to **12 motion items** (`data-motion-item`). Each item requires:
- A valid `data-step` attribute containing an ASCII decimal integer (checked in `parser.items` validation at lines 102-122)
- For semantic items (lacking `data-motion-decorative`): a non-empty `aria-label`
- For decorative items: `aria-hidden="true"` and `focusable="false"` attributes

### Control Elements and Actions

For controlled modes (`step` or `reveal` with a script), the contract mandates exactly one control group marked with `data-motion-controls`. This group must contain all five required actions: `play`, `pause`, `replay`, `prev`, and `next`, validated at lines 136-148 of the verification script.

Additionally, a status element marked `data-motion-status` must appear with `role="status"`, `aria-live="polite"`, and `aria-atomic="true"` to provide focus-friendly announcements to assistive technologies.

### Script and CSS Requirements

The contract enforces strict embedding rules for JavaScript and stylesheets:

- **Script uniqueness**: At most one `<script>` tag, carrying only the `data-diagram-controls` attribute
- **Canonical fidelity**: The script body must match the **canonical controller** exactly after whitespace normalization via `normalized_controller()` at lines 34-36, compared at lines 82-86
- **Reduced motion**: CSS must contain a `@media (prefers-reduced-motion: reduce)` block
- **Print support**: CSS must contain a `@media print` block
- **Control hiding**: Selector `[data-motion-controls] { display:none !important; }` must be present
- **Static fallback**: Selector `html[data-motion="static"] [data-motion-item] { opacity:1 … }` must provide static overrides

### SVG Accessibility Standards

Every motion diagram must include at least one `<svg>` element with `role="img"` and a proper `aria-labelledby` attribute referencing a non-empty `<title>` and `<desc>`. The title must be the first child of the SVG, validated at lines 70-88 of [`verify-motion.py`](https://github.com/cathrynlavery/diagram-design/blob/main/verify-motion.py).

## The Canonical Motion Controller

The canonical controller lives in [`skills/diagram-design/assets/template-motion.html`](https://github.com/cathrynlavery/diagram-design/blob/main/skills/diagram-design/assets/template-motion.html). This self-executing JavaScript provides the exact logic that every motion-enabled diagram must embed:

1. **Parameter parsing**: Reads URL parameters (`motion`, `step`) and respects the user's `prefers-reduced-motion` setting
2. **Step management**: Determines step count from `data-step-count` and builds label lists for each step
3. **Playback controls**: Wires DOM elements marked with `data-motion-action` to play, pause, replay, prev, and next functions
4. **Keyboard navigation**: Handles shortcuts for arrow keys (←/→), Home/End, Space, and **R**
5. **ARIA management**: Updates the `data-motion-status` live region to announce state changes to screen readers
6. **Render stability**: Adds the `motion-ready` class only after the initial render succeeds, ensuring static fallbacks display correctly if JavaScript fails

The verification script extracts this controller from [`template-motion.html`](https://github.com/cathrynlavery/diagram-design/blob/main/template-motion.html) and compares it against the target file's script body using whitespace normalization to ensure byte-for-byte equivalence.

## How verify-motion.py Enforces the Contract

Running `python -m scripts.verify-motion.py <file>` executes a dependency-free validation pipeline (standard library only) consisting of six stages:

1. **HTML Parsing**: Instantiates `MotionParser` to collect roots, items, actions, controls, statuses, scripts, styles, and SVGs
2. **Structural Validation**: Verifies root count, mode validity against `MODES`, step count bounds (0-8), and item budget (max 12)
3. **Controlled Mode Checks**: Ensures unique control groups, presence of all five actions, and required status element for interactive modes
4. **Script Verification**: Confirms single `data-diagram-controls` script presence and validates canonical controller equivalence using `normalized_controller()`
5. **CSS Contract Enforcement**: Checks for `prefers-reduced-motion` and `print` media queries, control hiding rules, and static overrides
6. **SVG Accessibility Audit**: Validates `role="img"`, `aria-labelledby` references, and proper `<title>`/`<desc>` ordering

The validator emits human-readable error messages for any violations; the CI workflow fails on any non-empty report.

## Running the Verification

Validate individual files or the entire repository:

```bash

# Verify a single diagram file

python -m scripts.verify-motion.py docs/example-diagram.html

# Verify every shipped motion asset (templates + HTML containing motion)

python -m scripts.verify-motion.py --shipped

```

Successful validation outputs `OK <path>` when all contract rules are satisfied.

## Practical Implementation Example

The following minimal HTML satisfies the motion controller contract for a three-step diagram:

```html
<main data-motion-root data-motion-mode="step" data-step-count="3">
  <svg role="img" aria-labelledby="title desc">
    <title id="title">Demo Diagram</title>
    <desc id="desc">A three-step illustration</desc>
    
    <g data-motion-item data-step="1" aria-label="Step 1">…</g>
    <g data-motion-item data-step="2" aria-label="Step 2">…</g>
    <g data-motion-item data-step="3" aria-label="Step 3">…</g>
  </svg>

  <div data-motion-controls role="group" aria-label="Playback controls">
    <button data-motion-action="prev">Previous</button>
    <button data-motion-action="play" aria-pressed="false">Play</button>
    <button data-motion-action="pause" aria-pressed="true">Pause</button>
    <button data-motion-action="next">Next</button>
    <button data-motion-action="replay">Replay</button>
  </div>

  <p data-motion-status role="status" aria-live="polite" aria-atomic="true"></p>

  <script data-diagram-controls>
    /* Exact copy of the canonical controller from template-motion.html */
  </script>
</main>

```

## Summary

- The **motion controller contract** requires exactly one `data-motion-root` element and a valid `data-motion-mode` selected from the `MODES` set (`none`, `reveal`, `step`, `loop`)
- Interactive modes mandate a `data-motion-controls` group containing five specific actions (`play`, `pause`, `replay`, `prev`, `next`) and a `data-motion-status` live region with proper AIA attributes
- The canonical JavaScript controller must be embedded identically—verified via `normalized_controller()` whitespace comparison—within a single `<script data-diagram-controls>` tag
- CSS must provide `@media (prefers-reduced-motion: reduce)` and `@media (print)` fallbacks alongside selectors that hide controls and enable static overrides
- [`scripts/verify-motion.py`](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/verify-motion.py) validates all constraints using the `MotionParser` class without external dependencies, ensuring diagrams meet accessibility and behavioral standards before publication

## Frequently Asked Questions

### What happens if the embedded script doesn't match the canonical controller exactly?

The validator will report a contract violation. At lines 82-86, [`verify-motion.py`](https://github.com/cathrynlavery/diagram-design/blob/main/verify-motion.py) compares the whitespace-normalized content of the diagram's `<script data-diagram-controls>` against the canonical controller extracted from [`template-motion.html`](https://github.com/cathrynlavery/diagram-design/blob/main/template-motion.html) using `normalized_controller()`. Any deviation—including extra whitespace, comments, or logic changes—causes validation to fail, ensuring behavioral consistency across all diagrams.

### How does the contract accommodate users with motion sensitivity?

The contract enforces multiple accessibility layers. First, the canonical controller automatically detects and respects `prefers-reduced-motion` settings. Second, CSS must include a `@media (prefers-reduced-motion: reduce)` block that disables animations. Third, semantic items require `aria-label` attributes while decorative items must carry `aria-hidden="true"`, ensuring screen readers announce meaningful content without distraction.

### What is the maximum number of animation steps allowed in the contract?

The **step count** is constrained to ASCII decimal integers between `0` and `8`, validated at lines 92-100 of [`verify-motion.py`](https://github.com/cathrynlavery/diagram-design/blob/main/verify-motion.py). Additionally, diagrams are limited to **12 motion items** (`data-motion-item` elements), checked during the `parser.items` validation phase at lines 102-122.

### How do I verify all motion diagrams in the repository at once?

Run the verification script with the `--shipped` flag: `python -m scripts.verify-motion.py --shipped`. This command validates every named template in `skills/diagram-design/assets/` plus any HTML files containing motion attributes, making it suitable for CI/CD pipelines to enforce the motion controller contract across the entire cathrynlavery/diagram-design codebase.