svg — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited svg (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Canonical source:examples/SVG_ACCESSIBILITY_BEST_PRACTICES.mdinmgifford/ACCESSIBILITY.mdThis 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.
Outcome-based, not checklist-driven. Measure against real usage context: is the SVG perceivable, operable, and compatible with assistive technologies?
Pattern recommendations in this skill are grounded in cross-browser/screen reader testing by Carie Fisher (published on Smashing Magazine, updated at <https://cariefisher.com/a11y-svg-updated/>). When a pattern is labelled "Best in Show", "Use Caution", or "Not Recommended" below, those labels reflect her testing results across OS/browser/screen reader combinations.
| Level | Meaning |
|---|---|
| Critical | SVG conveys essential information with no accessible alternative |
| Serious | SVG is interactive but unreachable or unlabelled for AT users |
| Moderate | AT experience degraded but content still partially accessible |
| Minor | Best-practice gap; marginal impact |
A decorative SVG that is not hidden from AT is a Moderate issue — it adds noise to the accessibility tree and forces screen reader users to listen to meaningless announcements.
Both attributes are required:
<svg aria-hidden="true" focusable="false">
<!-- decorative icon — no title, no desc, no role needed -->
</svg>aria-hidden="true" removes the SVG from the accessibility tree entirelyfocusable="false" is required for IE/Edge legacy compatibility where SVGsbecome focusable by default and appear in the tab order
Never use `role="presentation"` as a substitute for `aria-hidden="true"` on decorative SVGs. role="presentation" does not reliably suppress AT announcement across all browser/screen reader combinations.
When is an SVG decorative? If the same meaning is conveyed by adjacent visible text, or if the image is purely ornamental (borders, backgrounds, flourishes), it is decorative. If removing it would leave a user without information or context they need, it is meaningful.
Based on Carie Fisher's testing across NVDA, JAWS, VoiceOver (macOS and iOS), TalkBack, and Narrator:
<img> (external file reference)Use <img> for simple, uncomplicated images. The SVG file loads separately — this is lighter and easier to maintain for icons used in multiple places, but offers less control over internals.
Best in Show — `<img>` patterns:
<!-- Pattern 2: <img> + role="img" + alt — RECOMMENDED -->
<img role="img" class="icon" alt="Download" src="download.svg">
<!-- Pattern 3: <img> + role="img" + aria-label — RECOMMENDED -->
<img role="img" class="icon" aria-label="Download" src="download.svg">Use Caution:
<!-- Pattern 4: <img> + role="img" + aria-labelledby
Works in most combinations but has gaps in some screen readers -->
<p id="cap1" class="visually-hidden">Download report</p>
<img role="img" aria-labelledby="cap1" src="download.svg">Not Recommended:
<!-- Pattern 1: <img> + alt alone — inconsistent results, omit role="img" -->
<img alt="Download" src="download.svg">Use inline <svg> when you need control over colours, animation, or internals.
Best in Show — inline SVG patterns (simple label):
<!-- Pattern 5: <svg> + role="img" + <title> — RECOMMENDED -->
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<title>Download</title>
<!-- paths -->
</svg>
<!-- Pattern 8: <svg> + role="img" + <title> + aria-labelledby — RECOMMENDED -->
<svg role="img" aria-labelledby="dl-title" viewBox="0 0 24 24">
<title id="dl-title">Download</title>
<!-- paths -->
</svg>Use Caution:
<!-- Pattern 7: aria-describedby pointing to <title> — less reliable than
aria-labelledby; some screen readers do not expose the description -->
<svg role="img" aria-describedby="dl-title7" viewBox="0 0 24 24">
<title id="dl-title7">Download</title>
</svg>Not Recommended:
<!-- Pattern 6: <svg> + role="img" + <text> — poor results across screen readers -->
<svg role="img" viewBox="0 0 24 24">
<text class="visually-hidden" font-size="0">Download</text>
</svg>Use when the SVG conveys more than a name can carry: diagrams, illustrations, charts, infographics.
Best in Show — inline SVG with extended description:
<!-- Pattern 11: <svg> + role="img" + <title> + <desc> + aria-labelledby
pointing to BOTH IDs — RECOMMENDED for complex SVGs -->
<svg role="img"
aria-labelledby="chart-title chart-desc"
viewBox="0 0 400 300">
<title id="chart-title">Q1 2024 Website Visitors</title>
<desc id="chart-desc">
Bar chart. January 12,400. February 15,800.
March 19,200 — highest, up 54% from January.
</desc>
<!-- chart paths -->
</svg>Note: Pattern 11 sometimes reads both <title> and <desc> consecutively, which can sound slightly repetitive. This is acceptable — it did not suppress or ignore any content in testing, unlike the "Use Caution" patterns below.
Use Caution (all have gaps in at least one major screen reader combination):
<!-- Pattern 9: <title> + <text> -->
<!-- Pattern 10: <title> + <desc> without aria-labelledby -->
<!-- Pattern 12: <title> + <desc> + aria-describedby -->For Pattern 10 and 12 specifically: <desc> alone, and aria-describedby pointing to <desc>, are not reliably exposed across all combinations. Always use aria-labelledby referencing both IDs (Pattern 11) for complex SVGs.
prefers-reduced-motionRapid SVG animation is a Critical safety issue if it flashes at 3+ Hz (WCAG 2.3.1 — Three Flashes or Below Threshold). Vestibular disorders can be triggered by any sustained motion; seizure thresholds are lower.
Always wrap SVG animations in a prefers-reduced-motion check:
/* Default: respect the preference — run animation only if user has not
requested reduced motion */
@media (prefers-reduced-motion: no-preference) {
.animated-icon {
animation: spin 1s linear infinite;
}
}
/* Explicit reduction: stop all SVG animation */
@media (prefers-reduced-motion: reduce) {
svg * {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}For SVG animations controlled via JavaScript (e.g., GSAP, Anime.js):
const prefersReduced = window.matchMedia(
'(prefers-reduced-motion: reduce)'
).matches;
if (!prefersReduced) {
// Start animation
gsap.to('.icon-path', { rotation: 360, repeat: -1, duration: 1 });
} else {
// Show static final state instead
element.classList.add('animation-complete');
}For inline <svg> with <animate> or <animateTransform> elements, use begin="indefinite" as the default and only trigger the animation when prefers-reduced-motion: no-preference is confirmed:
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40">
<animate id="spin-anim"
attributeName="r" from="40" to="45"
dur="0.5s" repeatCount="indefinite"
begin="indefinite"/>
</circle>
</svg>if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
document.getElementById('spin-anim').beginElement();
}currentColor for Theme Compatibility<svg viewBox="0 0 24 24" class="icon" aria-hidden="true" focusable="false">
<path fill="currentColor" d="…"/>
</svg>Hardcoded colour values that do not respond to theme changes are Moderate — the SVG may become invisible or low-contrast in dark mode or forced-colours mode.
Only needed when the SVG would lose meaning without it:
@media (forced-colors: active) {
.icon { stroke: CanvasText; fill: CanvasText; }
.accent { stroke: Highlight; fill: Highlight; }
}Use semantic system colour keywords: Canvas, CanvasText, LinkText, ButtonFace, ButtonText, Highlight, HighlightText.
Meaningful graphical elements (not decorative) require minimum 3:1 contrast against adjacent colours (WCAG 1.4.11). This is Moderate if failing — the SVG is still present, but low-contrast elements may be unperceivable for low-vision users who do not use forced-colours mode.
viewBoxDo not remove viewBox during optimisation — it is required for responsive scaling. Its removal is Minor in most fixed-dimension contexts but will break fluid layouts.
Optimisation tools (SVGO and similar) may remove elements that are required for accessibility. Configure your optimiser to preserve:
viewBox<title> when contributing to accessible name<desc> when contributing to accessible descriptionaria-labelledby, aria-describedby, <use href="#…">,clip-path="url(#…)", mask="url(#…)", filter="url(#…)"
<style> elements implementing interaction states, reduced-motion,or forced-colours rules
If the SVG itself is interactive (clickable regions, toggle buttons):
tabindex — nativeelements provide keyboard, ARIA semantics, and AT support for free
<desc> for every SVG — Pattern 8 (<title> + aria-labelledby) issufficient when a name alone fully conveys the meaning
forced-colors block when the SVG remains perceivable without itrole="img" on decorative SVGs — use aria-hidden="true" insteadaria-hidden="true" focusable="false", no <title>, no <desc><img> SVGs: Pattern 2 or 3 (role="img" + alt or aria-label)role="img" + <title> + aria-labelledby)role="img" + <title> + <desc> + aria-labelledby both IDs)currentColor used for fills/strokesviewBox presentprefers-reduced-motion: no-preferenceStandards horizon: The 3:1 non-text contrast ratio is likely to change under WCAG 3.0's APCA model. The current 3:1 requirement remains applicable. Monitor: <https://www.w3.org/TR/wcag-3.0/>
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.