Skip to content

SVG images need alternative text

  • Rule: axe/svg-img-alt
  • Checks: <svg> elements with an img or image role must have alternative text
  • WCAG: 1.1.1
  • Fix class: content: A fix needs authored text.
  • People who use a screen reader or a braille display, most of them blind.

An <svg role="img"> conveys information but has no name

Section titled “An <svg role="img"> conveys information but has no name”

Add a <title> directly inside the <svg>, before its shapes, and point the <svg>’s aria-labelledby at it, or name it with aria-label. Decide the words from what the graphic conveys in its context.

<svg role="img" viewBox="0 0 24 24" aria-labelledby="paid-title">
<title id="paid-title">Payment received</title>
<path d="M9 16.2 4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4z"></path>
</svg>

The SVG is decorative, or text beside it says the same

Section titled “The SVG is decorative, or text beside it says the same”

Remove role="img" and add aria-hidden="true", so assistive technology skips it.

<svg aria-hidden="true" focusable="false" viewBox="0 0 24 24"><path d="M12 2 2 22h20z"></path></svg>

Give it a short name that says what it shows, and put the full information, such as the data, in text or a table beside it.

Nesting the <title> inside a <g> or a shape

Section titled “Nesting the <title> inside a <g> or a shape”

A <title> names its parent, so it names that part rather than the <svg>, and the image stays unnamed.

A <desc> gives a description, not a name, so the image stays unnamed and axe still reports it.

Keeping a <title> a design tool exported, such as “Layer 1” or “icon-check-24”

Section titled “Keeping a <title> a design tool exported, such as “Layer 1” or “icon-check-24””

axe accepts any text, but people hear words that mean nothing to them. Replace it with what the graphic conveys.

The part of the fix that differs in React, in its syntax. The rest of the guidance holds.

An SVG component gives its <title> a hard-coded id

Section titled “An SVG component gives its <title> a hard-coded id”

Build the id with useId, so each instance’s aria-labelledby points at its own <title>.

function StatusIcon({ label }) {
const titleId = useId();
return (
<svg role="img" viewBox="0 0 24 24" aria-labelledby={titleId}>
<title id={titleId}>{label}</title>
<path d="M9 16.2 4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4z" />
</svg>
);
}

An icon component always sets role="img", named or not

Section titled “An icon component always sets role="img", named or not”

Have callers say when an icon is decorative, and render aria-hidden="true" only then. Otherwise keep role="img" with the label they pass, so an icon left unnamed still shows up in axe instead of being hidden.

function Icon({ label, decorative = false, children }) {
return decorative ? (
<svg aria-hidden="true" focusable="false" viewBox="0 0 24 24">
{children}
</svg>
) : (
<svg role="img" aria-label={label} viewBox="0 0 24 24">
{children}
</svg>
);
}

Hiding every icon that has no label with aria-hidden="true"

Section titled “Hiding every icon that has no label with aria-hidden="true"”

A missing name does not make an icon decorative. It silences axe, and screen reader users lose meaningful icons, such as a status or a warning.

Generating the <title>’s id with Math.random()

Section titled “Generating the <title>’s id with Math.random()”

It differs between the server and the client render, so hydration reports a mismatch. useId is unique and stable.

axe-core’s documentation of the rule

CORA’s fix guidance adapts content from accessibility-agents, under the MIT License: see its notice.