# How to Create Accessible SVG Exports with `role="img"` and `aria-labelledby` in Diagram Design

> Learn to create accessible SVG exports with role="img" and aria-labelledby in Diagram Design. Ensure WCAG 2.1 AA compliance for your diagrams automatically.

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

---

**Diagram Design automatically generates WCAG 2.1 AA compliant SVG exports by enforcing a strict markup contract that assigns `role="img"` to every root `<svg>` element, links accessible names via `aria-labelledby` to prefixed `<title>` and `<desc>` IDs, and validates these rules through automated self-checking scripts.**

The cathrynlavery/diagram-design repository produces diagrams that meet accessibility standards without manual intervention. Every exported SVG follows a programmatically enforced contract ensuring screen-reader compatibility through semantic markup and collision-free identifier management.

## The Accessibility Contract

Diagram Design implements a four-part accessibility contract for every SVG export. This contract guarantees that assistive technologies can interpret diagrams as meaningful images rather than decorative graphics.

### Semantic Role Assignment

Each SVG root element receives **`role="img"`** to signal to screen readers that the element represents a graphical image. This static attribute overrides the default SVG behavior, ensuring the element enters the accessibility tree as a single cohesive unit rather than exposing individual geometric paths to assistive technology.

### Programmatic Label Association

The **`aria-labelledby`** attribute references two child elements by their `id` values: the `<title>` element providing the accessible name and the `<desc>` element providing the extended description. According to the source code in [`template.html`](https://github.com/cathrynlavery/diagram-design/blob/main/template.html), the attribute concatenates these IDs with a space separator:

```html
<svg viewBox="0 0 1000 600"
     xmlns="http://www.w3.org/2000/svg"
     role="img"
     aria-labelledby="my-diagram-title my-diagram-desc">

```

### Slug-Based ID Prefixing

To prevent collisions when multiple SVGs appear on the same page, Diagram Design prefixes every ID with the diagram's unique slug. The rendering engine appends `-title` and `-desc` suffixes to the base slug (e.g., `my-diagram-title` and `my-diagram-desc`), ensuring unique identifiers across inlined graphics.

### Document Order Requirements

The [`self_check.py`](https://github.com/cathrynlavery/diagram-design/blob/main/self_check.py) script enforces that `<title>` and `<desc>` must appear as the **first two children** of the SVG element (verified at lines 235-236). This strict ordering ensures screen readers encounter the accessible name before any graphical content.

## Implementation in the Source Code

The accessibility features are hardcoded into the rendering pipeline and validated through automated tooling.

### Template Injection

The core HTML scaffold in [`skills/diagram-design/assets/template.html`](https://github.com/cathrynlavery/diagram-design/blob/main/skills/diagram-design/assets/template.html) injects the required attributes dynamically. When rendering occurs, the template replaces placeholder values with the actual diagram slug, generating the complete `aria-labelledby` value and corresponding ID attributes.

### Automated Validation with self_check.py

The `check_svgs` function within [`skills/diagram-design/scripts/self_check.py`](https://github.com/cathrynlavery/diagram-design/blob/main/skills/diagram-design/scripts/self_check.py) (lines 214-244) parses generated SVGs and validates five specific rules:

- The root `<svg>` element contains `role="img"`
- The first child element is `<title>`
- Both `<title>` and `<desc>` contain non-empty text content
- The `aria-labelledby` attribute references the title ID followed by the desc ID
- All referenced IDs match the expected slug-prefix pattern

### CI Enforcement via lint-skin.py

The repository's linter ([`lint-skin.py`](https://github.com/cathrynlavery/diagram-design/blob/main/lint-skin.py)) includes an **a11y** category that executes during continuous integration. This linter rejects any SVG failing the accessibility contract, preventing non-compliant diagrams from reaching production.

## Practical Code Examples

### Minimal Accessible SVG Export

The following markup represents the default output generated by Diagram Design when rendering a simple flowchart:

```html
<svg viewBox="0 0 1000 600"
     xmlns="http://www.w3.org/2000/svg"
     role="img"
     aria-labelledby="example-title example-desc">
  <title id="example-title">Example Diagram</title>
  <desc id="example-desc">Shows a simple flow from A to B.</desc>
  <rect x="100" y="100" width="300" height="200" fill="#eee"/>
  <text x="250" y="210" text-anchor="middle">A → B</text>
</svg>

```

This structure matches the template found in [`example-flowchart.html`](https://github.com/cathrynlavery/diagram-design/blob/main/example-flowchart.html) within the assets folder.

### Custom Slug Configuration

When generating multiple diagrams for a single page, control the identifier prefix by setting the `diagram-slug` parameter in your rendering configuration:

```html
<svg viewBox="0 0 1000 600"
     xmlns="http://www.w3.org/2000/svg"
     role="img"
     aria-labelledby="user-journey-title user-journey-desc">
  <title id="user-journey-title">User Journey Map</title>
  <desc id="user-journey-desc">Steps a user takes from sign-up to purchase.</desc>
  <!-- generated geometry -->
</svg>

```

The slug `user-journey` generates unique IDs that will not conflict with other diagrams using different slugs.

## Validating Your Exports

Run the bundled validation script against any generated HTML file to verify accessibility compliance:

```bash
python3 skills/diagram-design/scripts/self_check.py path/to/generated.html

```

Successful validation prints `OK`. If the SVG violates the contract, the script emits specific errors such as:

```

svg 1 needs role=img
svg 1 title must be its first child
svg 1 needs non-empty title and desc
svg 1 aria-labelledby must name title then desc

```

These messages originate from the validation logic in [`self_check.py`](https://github.com/cathrynlavery/diagram-design/blob/main/self_check.py) and indicate exactly which accessibility requirement failed.

## Summary

- **Diagram Design enforces accessible SVG exports** by automatically injecting `role="img"` and `aria-labelledby` attributes into every generated diagram.
- **ID collision prevention** occurs through automatic slug-prefixing of `<title>` and `<desc>` identifiers.
- **Validation happens at multiple layers**: the [`self_check.py`](https://github.com/cathrynlavery/diagram-design/blob/main/self_check.py) script checks individual files, while [`lint-skin.py`](https://github.com/cathrynlavery/diagram-design/blob/main/lint-skin.py) prevents non-compliant merges in CI/CD pipelines.
- **Document order matters**: the `<title>` element must be the first child of the SVG, followed immediately by `<desc>`, as enforced by lines 235-236 of the checking script.

## Frequently Asked Questions

### Why does Diagram Design use `aria-labelledby` instead of `aria-label`?

`aria-labelledby` creates a programmatic association between the SVG and its visible (or hidden) text elements, allowing the same content to serve both sighted users and assistive technology without duplication. This approach also enables more complex descriptions through the separate `<desc>` element, whereas `aria-label` supports only plain text strings and obscures the content from translation workflows.

### How does the repository prevent ID collisions when multiple SVGs appear on one page?

The rendering engine prefixes every ID with the diagram's unique slug (e.g., `my-diagram-title` instead of generic `title`). This naming convention ensures that even when twenty diagrams are inlined within a single HTML document, each `aria-labelledby` reference points to the correct, unique elements without namespace conflicts.

### What happens if the self-check script finds accessibility violations?

The script exits with an error code and prints specific diagnostic messages indicating which contract rules failed (such as missing `role="img"` or incorrect child element ordering). In the continuous integration environment, [`lint-skin.py`](https://github.com/cathrynlavery/diagram-design/blob/main/lint-skin.py) catches these same violations and fails the build, preventing inaccessible diagrams from being merged into the main branch.

### Where is the accessibility validation enforced in the CI/CD pipeline?

The **a11y** category within [`lint-skin.py`](https://github.com/cathrynlavery/diagram-design/blob/main/lint-skin.py) executes during pull request checks, scanning all generated SVGs and template outputs. Additionally, the test suite in [`test-self-check.py`](https://github.com/cathrynlavery/diagram-design/blob/main/test-self-check.py) provides automated regression testing, ensuring that future refactors to the rendering engine cannot unintentionally break the accessibility contract.