# What Accessibility Features Are Included in Generated SVG Diagrams?

> Discover accessibility features in generated SVG diagrams. Learn how Diagram Design ensures WCAG 2.1 AA compliance with role img title desc and aria-labelledby.

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

---

**Generated SVG diagrams in the Diagram-Design repository must pass the `lint_accessible_svgs` validator in [`scripts/lint-skin.py`](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/lint-skin.py), which enforces `role="img"`, a first-child `<title>`, a `<desc>` element, and `aria-labelledby` referencing slug-prefixed IDs to satisfy WCAG 2.1 AA requirements.**

The `cathrynlavery/diagram-design` toolchain treats accessibility as a non-negotiable output contract. Whenever a user runs the `export-diagram` command to produce an SVG, the resulting markup is scrutinized by the Python linter defined in [`scripts/lint-skin.py`](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/lint-skin.py). These accessibility features in generated SVG diagrams ensure that every graphic is immediately consumable by screen readers without manual post-processing.

## Semantic Role and ARIA Labeling

Every meaningful SVG must explicitly declare its semantic purpose to assistive technology. The linter enforces two complementary rules on the root `<svg>` element.

### Mandatory `role="img"` Attribute

Inside [`scripts/lint-skin.py`](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/lint-skin.py), lines 49–51, the `lint_accessible_svgs` function checks that `svg.attrs.get("role")` equals `"img"`. If the attribute is missing or holds any other value, the linter raises an error and blocks the diagram from export.

### `aria-labelledby` References

The root `<svg>` must carry an `aria-labelledby` attribute that references the IDs of both the `<title>` and `<desc>` elements. This programmatic link exposes the accessible name and description to screen readers. The linter validates these references at lines 52–55 and confirms ID resolution at lines 78–84.

## Title and Description Structure

Beyond ARIA attributes, the document tree itself must contain specific semantic children in the correct order.

### `<title>` Element Placement and Content

The `<title>` element must be the **first child** of the `<svg>` root and must contain non-empty text. At lines 96–104 in [`scripts/lint-skin.py`](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/lint-skin.py), the linter checks for presence, verifies child order, and rejects empty title nodes.

### `<desc>` Element for Extended Descriptions

Immediately following the title, a `<desc>` element supplies a longer human-readable explanation. The linter enforces its presence and forbids empty description strings at lines 108–112. This pairing satisfies WCAG 2.1 AA expectations for both a short name and a detailed text alternative.

## ID Conventions and Uniqueness Validation

To keep generated diagrams deterministic and collision-free, the linter imposes a strict ID naming scheme across all exports.

### Slug-Prefixed ID Naming

IDs for `<title>` and `<desc>` must be prefixed with the diagram's slug. For a diagram slugged `example-flowchart`, the expected IDs are `example-flowchart-title` and `example-flowchart-desc`. This rule is enforced at lines 122–134 of [`scripts/lint-skin.py`](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/lint-skin.py).

### Duplicate and Bare ID Detection

The linter scans the entire document for duplicate IDs at lines 38–44 and raises an error if any are found. Additionally, bare IDs such as `id="title"` or `id="desc"` are explicitly prohibited at lines 155–161; the slug prefix is required to avoid ambiguity.

## Decorative SVG and Template Handling

Not every SVG is intended to convey meaning. The linter supports exemptions for decorative graphics and template placeholders.

### Decorative Graphics with `aria-hidden="true"`

If an SVG is purely decorative, adding `aria-hidden="true"` to the root element triggers an early exit in the linter at lines 44–48. When this attribute is present, all other accessibility checks are suppressed and the file passes validation.

### Template Placeholder IDs

During template generation, placeholder IDs formatted as `[diagram-slug]-title` are allowed through the `allow_template_placeholders` parameter. The linter handles these gracefully at lines 66–70, enabling reusable skins before a final slug is injected.

## Valid SVG Examples

Below is a minimal valid SVG that satisfies the Diagram-Design accessibility contract for a diagram whose slug is `example-flowchart`:

```svg
<svg role="img"
     aria-labelledby="example-flowchart-title example-flowchart-desc"
     width="800" height="600"
     xmlns="http://www.w3.org/2000/svg">
  <title id="example-flowchart-title">Flowchart of Process</title>
  <desc id="example-flowchart-desc">A step-by-step flowchart showing the stages of the process.</desc>
  <!-- diagram graphics go here -->
</svg>

```

For purely decorative diagrams, suppress the accessibility checks as follows:

```svg
<svg aria-hidden="true" width="800" height="600" xmlns="http://www.w3.org/2000/svg">
  <!-- purely decorative graphics -->
</svg>

```

## Test and Enforcement Pipeline

The rules above are executable constraints, not guidelines. The `lint_accessible_svgs` function inside [`scripts/lint-skin.py`](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/lint-skin.py) is exercised by [`scripts/test-lint-a11y.py`](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/test-lint-a11y.py), which confirms that compliant SVGs are accepted and that every violation—missing roles, malformed IDs, empty titles—is caught before the `export-diagram` command finalizes output.

## Summary

- **Mandatory `role="img"`** is required on every meaningful SVG root element.
- **`<title>` must be the first child** of the `<svg>` and contain non-empty text.
- **`<desc>` must provide** an extended, non-empty description.
- **`aria-labelledby`** must reference both the `<title>` and `<desc>` elements by their unique IDs.
- **Slug-prefixed IDs** prevent collisions; bare IDs like `id="title"` are forbidden.
- **Decorative SVGs** can bypass all checks by setting `aria-hidden="true"`.
- **Template placeholders** are permitted when `allow_template_placeholders` is enabled.
- The entire contract is enforced by [`scripts/lint-skin.py`](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/lint-skin.py) and validated by [`scripts/test-lint-a11y.py`](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/test-lint-a11y.py).

## Frequently Asked Questions

### What happens if an SVG is missing the `role="img"` attribute?

The linter in [`scripts/lint-skin.py`](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/lint-skin.py) flags the omission as an error at lines 49–51. The SVG will fail validation and cannot be exported until the semantic role is added to the root element.

### Can I use generic IDs such as `id="title"` in my SVG?

No. Bare IDs are explicitly rejected at lines 155–161. You must use the diagram slug as a prefix—for example, `example-flowchart-title`—to satisfy the linter and prevent ID collisions.

### How does the linter handle decorative SVGs?

When the root `<svg>` element includes `aria-hidden="true"`, the linter executes an early exit at lines 44–48 and skips all other accessibility checks, treating the graphic as purely decorative.

### Where are these accessibility rules tested?

The test suite in [`scripts/test-lint-a11y.py`](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/test-lint-a11y.py) validates the `lint_accessible_svgs` function, ensuring that compliant SVGs pass and that every rule violation is caught before the diagram reaches production.