mgifford/accessibility-skills/svg

Claude Code and agent skill by mgifford/accessibility-skills

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Load this skill whenever the project contains SVG graphics — inline SVGs, external SVG files, SVG icons, SVG illustrations, or SVG-based data visualizations. Under no circumstances use SVG without proper accessible titles, descriptions, and ARIA roles where required. Absolutely always add <title> and <desc> to meaningful SVGs and set role="img" with aria-labelledby pointing to those elements. Also apply this skill whenever the project accepts untrusted or user-uploaded SVG content, which requires sanitization before rendering.

Install

npx vskill@latest install mgifford/accessibility-skills/svg

SKILL.md

SVG Accessibility Skill

Canonical source: examples/SVG_ACCESSIBILITY_BEST_PRACTICES.md in mgifford/ACCESSIBILITY.md
This skill is derived from that file. When in doubt, the example is authoritative.

Apply these rules when creating, optimising, or reviewing SVG graphics. Only load this skill if the project contains SVGs.


Core Mandate

SVG accessibility depends on the graphic's purpose and how it is embedded. A decorative icon, an informative image, an icon inside a button, and an interactive diagram require different patterns. Start by deciding what users need from the graphic, then provide the simplest semantic implementation that delivers the same information or function. The presence of <title>, <desc>, role="img", or aria-label does not by itself make an SVG accessible — test the result in its final context.

SVG is also an active XML format that can contain scripts, event handlers, and external resource references. Untrusted SVG (uploads, pasted markup, AI-generated output) must be sanitized before rendering — see the Security section below.


Severity Scale (this skill)

LevelMeaning
CriticalSVG conveys essential information with no accessible alternative; untrusted SVG rendered without sanitization
SeriousSVG is interactive but unreachable or unlabelled for AT users
ModerateAT experience degraded but content still partially accessible
MinorBest-practice gap; marginal impact

Choose the Pattern by Purpose and Context

ContextPreferred pattern
Decorative SVG loaded with <img><img src="…" alt="">
Meaningful SVG loaded with <img><img src="…" alt="Useful description">
Decorative inline SVG<svg aria-hidden="true">…</svg>
Simple meaningful inline SVG<svg role="img" aria-labelledby="…"><title>…</title></svg>
Icon inside a named button or linkName the HTML control and hide the SVG
Complex graphicShort name plus visible summary, data, or detailed description
Reused sprite iconLabel or hide each outer <svg> instance, not the shared symbol
Interactive graphicPrefer native HTML controls; provide an equivalent structured alternative

Use native HTML semantics before ARIA. Do not add role="img" to an <img> merely because its source is an SVG file — that's redundant.


Critical: Decorative SVGs Must Be Hidden Completely from AT

html
<svg aria-hidden="true" focusable="false">
<!-- decorative icon — no title, no desc, no role needed -->
</svg>

aria-hidden="true" removes the SVG and its descendants from the accessibility tree — do not place focusable or interactive content inside an element hidden this way. focusable="false" addresses legacy IE/older Edge behavior; it can remain for projects still supporting those browsers but isn't a universal modern requirement. Do not add <title>/<desc> to an intentionally-hidden SVG. Never use `role="presentation"` as a substitute for aria-hidden="true" — it does not reliably suppress announcement across browser/screen-reader combinations.

When is an SVG decorative? If the same meaning is conveyed by adjacent visible text, or the image is purely ornamental, it's decorative. If removing it would leave a user without needed information, it's meaningful.


External SVG Used with <img>

Follows the same text-alternative rules as other image formats.

html
<!-- Informative -->
<img src="/assets/images/service-area.svg" alt="Map showing the service area extending from Kingston to Ottawa" width="640" height="360">
<!-- Decorative -->
<img src="/assets/images/flourish.svg" alt="" width="120" height="24">
<!-- Functional (destination-describing) -->
<a href="/reports/annual-report.pdf">
<img src="/assets/icons/download.svg" alt="Download annual report">
</a>

role="img" is redundant on <img>; aria-label unnecessary when alt can provide the name. Internal SVG `<title>`/`<desc>` does not replace HTML `alt` when loaded through <img> — the HTML alt is what's read.


Serious: Simple Meaningful Inline SVG

html
<svg role="img" aria-labelledby="download-icon-title" viewBox="0 0 24 24">
<title id="download-icon-title">Download</title>
<path d="M12 3v12m0 0 5-5m-5 5-5-5M5 21h14"></path>
</svg>

The <title> must be a direct child of <svg> with a unique ID, referenced via aria-labelledby. A plain aria-label is a reasonable alternative when managing a unique title ID adds needless complexity:

html
<svg role="img" aria-label="Download" viewBox="0 0 24 24">…</svg>

Do not use both patterns without a specific tested reason — ARIA labelling attributes take priority over <title> in the accessible-name calculation (aria-labelledby > aria-label > direct child <title>). Note: some browsers show <title> as a pointer tooltip — that is not a substitute for visible text or a real accessible tooltip; it may not be discoverable by keyboard, touch, or speech-input users.


Serious: Icons Inside Buttons and Links

The HTML control should own the accessible name — hide the SVG icon.

html
<!-- Visible text -->
<button type="button">
<svg aria-hidden="true" viewBox="0 0 24 24"><path d="M6 6 18 18M18 6 6 18"></path></svg>
Close
</button>
<!-- Icon-only -->
<button type="button" aria-label="Close dialog">
<svg aria-hidden="true" viewBox="0 0 24 24"><path d="M6 6 18 18M18 6 6 18"></path></svg>
</button>

Do not give both the button and its hidden icon separate versions of the same name — that produces duplicate or inconsistent announcements. Prefer a visible label when the action may not be obvious — a familiar-looking icon can mean different things across products.

For an external SVG asset loaded via <img> inside an already-labelled button (e.g., a theme selector), use alt="" since the button supplies the name — note that an external <img>-loaded SVG does not inherit currentColor from the surrounding button; validate the asset's own colors on every background it appears on, or inline the paths if it must follow a manual theme.


Serious: Accessible Names and Descriptions — Correct ARIA Relationship

SVG accessible-name priority: aria-labelledby > aria-label > direct child <title> > other fallbacks. Descriptions: aria-describedby > direct child <desc>.

html
<svg role="img" aria-labelledby="weather-title" aria-describedby="weather-desc" viewBox="0 0 320 180">
<title id="weather-title">Weekly temperature trend</title>
<desc id="weather-desc">Temperatures rise from 12 degrees Monday to 21 degrees Friday.</desc>
</svg>

Keep IDs unique in the whole HTML document — a repeated component with fixed SVG IDs can cause one instance to be labelled by another. Do not put both title and description IDs in `aria-labelledby` — that concatenates both strings into the accessible name; use aria-labelledby for the name and aria-describedby for the description separately. Do not bury essential information in `<desc>` — support for navigating long SVG descriptions varies across AT; put essential details in visible HTML instead.


Moderate: Complex Graphics, Diagrams, and Charts

A complex SVG needs more than one long accessible name: a concise name, a visible summary of the purpose/conclusion, complete data in structured HTML when needed, and keyboard-operable controls if interactive.

html
<figure>
<svg role="img" aria-labelledby="visitors-title" aria-describedby="visitors-summary" viewBox="0 0 640 360">
<title id="visitors-title">Website visitors from January to March</title>
</svg>
<figcaption id="visitors-summary">
Visitors increased each month, from 12,400 in January to 19,200 in March.
</figcaption>
<details>
<summary>View the chart data</summary>
<table>
<caption>Monthly website visitors</caption>
<thead><tr><th scope="col">Month</th><th scope="col">Visitors</th></tr></thead>
<tbody><tr><th scope="row">January</th><td>12,400</td></tr></tbody>
</table>
</details>
</figure>

Do not duplicate a detailed table into aria-label/<title>/<desc> — long image-name strings are hard to navigate. The ARIA Graphics Module defines structured roles (graphics-document, graphics-object, graphics-symbol) but support must be tested with actual AT combinations; these don't remove the need for an equivalent text presentation. See skills/charts-graphs/SKILL.md for chart-specific requirements.


Reused Icons and SVG Sprites

html
<svg aria-hidden="true" hidden>
<symbol id="icon-download" viewBox="0 0 24 24">
<path d="M12 3v12m0 0 5-5m-5 5-5-5M5 21h14"></path>
</symbol>
</svg>
<button type="button">
<svg aria-hidden="true" viewBox="0 0 24 24"><use href="#icon-download"></use></svg>
Download report
</button>

Names inside a shared symbol are rarely appropriate for every instance — "Arrow" could mean Next, Previous, Expand, or Collapse depending on context. Name the control or the outer rendered instance, not the shared symbol. An external <use> target must reference a real distinct element (usually a <symbol> with an ID) — do not create recursive self-references.


Moderate: Colour, Contrast, and Themes

css
.icon {
fill: none;
stroke: currentColor;
stroke-linecap: round;
stroke-width: 2;
}

currentColor is useful, not mandatory — multi-colour graphics may need several semantic tokens; validate every meaningful pairing in every theme. Do not rely on colour alone — add text labels, patterns, line styles, or shapes for statuses/series/selected states. Text inside SVG is subject to text contrast requirements; required graphical information generally needs 3:1; decorative details don't need 3:1 merely for appearing in a meaningful graphic.

css
@media (forced-colors: active) {
.icon { color: ButtonText; }
.chart-line { fill: none; stroke: CanvasText; }
.chart-line[data-selected="true"] { stroke: Highlight; stroke-width: 4; }
}

Do not set both fill and stroke on every path indiscriminately — it can turn line artwork into solid shapes. Avoid forced-color-adjust: none unless essential and tested against multiple system palettes.


Text Inside SVG

Keep essential labels visible and legible; meet text contrast requirements. Do not convert essential labels to paths merely to preserve a typeface, and do not use font-size="0", off-canvas positioning, or transparent SVG text as a substitute for a real text alternative. Provide important explanations in HTML. Test resizing/zoom — SVG labels can overlap or clip even when the outer SVG scales. <foreignObject> can embed HTML in SVG but adds rendering/fallback complexity — use ordinary HTML outside the SVG when it can provide the same result.


Critical: Interactive SVG

Prefer native HTML buttons, links, and disclosures placed near the graphic over interaction inside the SVG itself:

html
<div class="map">
<svg aria-hidden="true" viewBox="0 0 640 360"><!-- Visual map --></svg>
<ul class="map-locations">
<li><a href="/locations/ottawa">Ottawa service centre</a></li>
</ul>
</div>

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 95 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

7-day trend score
0

Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/mgifford/accessibility-skills/svg/badge)](https://verified-skill.com/skills/mgifford/accessibility-skills/svg)

Something wrong with this skill or its page? Tell us.