How to Use Step Animation Mode for Teaching and Policy Traces in Diagram Design

The step animation mode pauses your diagram at each semantic step, letting viewers inspect state before advancing—ideal for tutorials and policy trace walkthroughs.

In the cathrynlavery/diagram-design repository, step is the only interactive animation mode supported. It powers everything from three-step tutorials to complex five-stage policy evaluations. This guide shows you exactly how to implement it, with markup patterns drawn directly from the source code.


Declare the Step Mode on Your Root Element

Every step-mode diagram needs a motion root—the container that triggers the controller. Add data-motion-mode="step" to the element carrying data-motion-root.

<main data-motion-root data-motion-mode="step" data-step-count="5">

The data-step-count attribute is mandatory. It tells the controller how many semantic steps exist (range: 1–8). This pattern appears in example-policy-trace-animated.html, the canonical reference implementation.


Mark Semantic Items with Step Numbers

Each SVG group that should appear at a specific step gets two attributes:

  • data-motion-item — enrolls the element in the animation system
  • data-step="k" — reveals the element at step k and keeps it visible for all subsequent steps

Example from the policy trace file:

<g data-motion-item data-step="3" aria-label="Rule 3: authentication required">
  <!-- SVG content for this step -->
</g>

Steps must be contiguous (1, 2, 3, 4, 5…). The verify-motion.py script rejects gaps or out-of-order declarations.


Add Required Playback Controls

The controller wires buttons automatically, but the markup structure is strict. Use a container with data-motion-controls and buttons with specific data-motion-action values:

<div data-motion-controls>
  <button data-motion-action="prev">Previous</button>
  <button data-motion-action="play">Play</button>
  <button data-motion-action="pause">Pause</button>
  <button data-motion-action="next">Next</button>
  <button data-motion-action="replay">Replay</button>
</div>

Missing actions won't break the diagram, but they frustrate keyboard users. Include all five for full accessibility compliance.


Enable Keyboard Navigation

When focus is inside the motion root (set tabindex="-1" to make it focusable), these keys work as defined in animation.md:

  • Left/Right arrows — previous/next step
  • Space — toggle play/pause (when not on a native button)
  • R — replay from step 1
  • Home/End — jump to first or last step

The controller in template-motion.html handles all key events. Do not modify this file—the lint-skin.py CI check will reject any changes.


Handle Reduced Motion Preferences

Step mode automatically respects prefers-reduced-motion. When detected, the diagram:

  • Renders the static frame (all steps visible simultaneously)
  • Hides the control buttons
  • Disables all animation

This behavior is contractually defined in § Static-first enhancement of animation.md. No extra markup required.


For regression testing or documentation embeds, use the query string ?motion=step&step=K:

example-policy-trace-animated.html?motion=step&step=3

The controller renders steps 1 through 3 and stops. The data-motion-status element (if present) shows "Step 3 of 5."


Complete Minimal Example

Here's a three-step teaching diagram following all repository conventions:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Three-step tutorial</title>
  <link href="https://fonts.googleapis.com/css2?family=Instrument+Serif&family=Geist&display=swap" rel="stylesheet">
</head>
<body>
<main data-motion-root data-motion-mode="step"
      data-step-count="3" tabindex="-1">

  <svg viewBox="0 0 800 200" role="img" aria-labelledby="title">
    <title id="title">Three-step tutorial</title>

    <g data-motion-item data-step="1">
      <rect x="50" y="50" width="200" height="80"/>
      <text x="150" y="95" text-anchor="middle">Introduction</text>
    </g>

    <g data-motion-item data-step="2">
      <rect x="300" y="50" width="200" height="80"/>
      <text x="400" y="95" text-anchor="middle">Core Concept</text>
    </g>

    <g data-motion-item data-step="3">
      <rect x="550" y="50" width="200" height="80"/>
      <text x="650" y="95" text-anchor="middle">Application</text>
    </g>
  </svg>

  <div data-motion-controls>
    <button data-motion-action="prev">Previous</button>
    <button data-motion-action="play">Play</button>
    <button data-motion-action="pause">Pause</button>
    <button data-motion-action="next">Next</button>
    <button data-motion-action="replay">Replay</button>
  </div>

  <span data-motion-status role="status" aria-live="polite"></span>
</main>

<script data-diagram-controls src="assets/template-motion.html"></script>
</body>
</html>

Validation and Enforcement

Two CI scripts guarantee compliance:

Script Purpose
scripts/verify-motion.py Checks contiguous step ranges, timing budgets, and accessibility attributes
scripts/lint-skin.py Verifies template-motion.html is unmodified and correctly referenced

Run both before committing. Failures block merge.


Summary

  • Step mode is declared with data-motion-mode="step" on the motion root
  • Contiguous numbering (1–N) is enforced by data-step-count and CI validators
  • Semantic items use data-motion-item + data-step="k" for visibility control
  • Five control buttons (prev, play, pause, next, replay) provide full interaction
  • Keyboard shortcuts (arrows, space, R, home/end) work without extra JavaScript
  • Reduced motion automatically falls back to static rendering
  • Query strings (?step=K) enable direct frame access for testing

Frequently Asked Questions

What happens if I skip a step number?

The verify-motion.py validator fails with a non-contiguous step error. Steps must form an unbroken sequence from 1 to data-step-count. Fix by renumbering or adjusting the count.

Can I use more than 8 steps?

No. The step-mode contract in animation.md caps step counts at 8 to preserve performance and cognitive load. For longer sequences, split into multiple diagrams or use static frames.

Why does my controller not respond to keyboard input?

The motion root needs tabindex="-1" to accept focus. Without it, key events never reach the controller. Also verify template-motion.html is loaded via <script data-diagram-controls>.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →