SVG inline vs img
When to inline an SVG, when to use it as an img source — and the accessibility attributes that decide whether a screen reader sees it.
SVG is the format for anything geometric — logos, icons, charts, diagrams. Vector files scale to any size without blurring, are usually smaller than equivalent PNGs at large sizes, and can be styled and animated with CSS.
But SVG behaves differently from raster formats. You can drop an SVG file into an <img> like a PNG, or you can paste the SVG markup directly into your HTML. Each option has consequences. This lesson covers both, the accessibility attributes that decide whether the SVG is visible to screen readers, and how to pick.
Two ways to use an SVG
As an <img> source — the SVG is an external file, loaded the same way as a JPEG or PNG.
<img src="logo.svg" alt="Daily Bread Bakery" width="200" height="60">
The SVG is fetched as a file. It renders at the size you set. CSS in the page cannot reach into it — the SVG is a sealed bundle.
Inline — the SVG markup lives directly in the HTML.
<svg viewBox="0 0 200 60" width="200" height="60" role="img" aria-label="Daily Bread Bakery"> <text x="100" y="40" textAnchor="middle" fontFamily="Georgia" fontSize="28" fill="currentColor"> Daily Bread </text> </svg>
The SVG renders as part of the page. CSS rules can target the elements inside it. JavaScript can manipulate it. No extra HTTP request.
The trade-offs:
- Inline is best when you need to style or animate the SVG with CSS, or when the SVG is small enough that an external file is overkill (icons, small marks).
<img>is best when the SVG is large, used in many places, or when you do not need to manipulate it from CSS or JavaScript. Browser caches the file; your HTML stays small.
For an icon system used across a whole site, inlining via JavaScript (importing the SVG as a component) is the modern compromise — small enough to stay snappy, accessible to CSS, but not duplicated in every page's HTML.
SVG accessibility attributes
An SVG with no accessibility attributes is invisible to screen readers in some cases and announced as "image" with no name in others. The behavior is inconsistent across browsers, so always be explicit.
Two attributes do most of the work:
role="img" — declares the SVG as an image in the accessibility tree. Without it, browsers may treat the SVG as a generic group of shapes.
aria-label="..." — names the image. Equivalent to the alt attribute on <img>.
<svg viewBox="0 0 24 24" role="img" aria-label="Search"> <circle cx="10" cy="10" r="6" fill="none" stroke="currentColor" strokeWidth="2"/> <line x1="14" y1="14" x2="20" y2="20" stroke="currentColor" strokeWidth="2"/> </svg>
The screen reader announces "Search, image". Same as an <img alt="Search" src="search.png">.
For an SVG with a more complex structure (a chart, a diagram), you can use child elements <title> and <desc> instead of (or alongside) aria-label:
<svg viewBox="0 0 320 180" role="img" aria-labelledby="chart-title chart-desc"> <title id="chart-title">Q4 sales by region</title> <desc id="chart-desc">Bar chart with four bars; west region leads at 42%, east 28%, north 18%, south 12%.</desc> <!-- ...chart geometry... --> </svg>
<title> is the short name; <desc> is the longer description. aria-labelledby points to both — the screen reader reads them in order.
For decorative SVGs (a flourish, an icon next to text that already says what the button does), use aria-hidden="true":
<button> <svg aria-hidden="true" viewBox="0 0 16 16"> <path d="M3 8 L13 8" stroke="currentColor" strokeWidth="2"/> </svg> Save </button>
The button announces "Save". The decorative line icon is hidden from the accessibility tree. Same pattern as a decorative <img alt="">.
Don't put text inside an SVG and rely on it as the label. Screen readers may or may not announce <text> elements — it varies. Use role="img" + aria-label, or aria-labelledby + <title>, to be sure.
<svg viewBox="0 0 16 16">...</svg> Save. The button text already says Save. The SVG should be invisible to screen readers. The right attribute is:Styling inline SVG
The killer feature of inline SVG: CSS can style the inside.
.icon {
width: 1em;
height: 1em;
}
.icon path {
fill: var(--accent);
stroke: var(--accent);
}
button:hover .icon path {
fill: var(--accent-strong);
}Two patterns earn their keep:
width: 1emon the SVG sizes it relative to the surrounding text — perfect for icons in buttons and labels.fill: currentColoron SVG paths inherits the text color. Set the button'scolorand the icon recolors automatically.
<button class="primary">
<svg viewBox="0 0 16 16" aria-hidden="true">
<path d="M2 8 L7 13 L14 4" fill="none" stroke="currentColor" strokeWidth="2"/>
</svg>
Confirm
</button>
<style>
button.primary { color: white; background: blue; }
button.danger { color: red; background: white; }
</style>Same SVG markup. The icon's color follows the button's color property. Switch the button's class and the icon updates without touching the SVG.
currentColor is the bridge between SVG and CSS — set it on fill, stroke, or any color attribute and SVG inherits the surrounding color value. It is the single most useful detail when building an icon system.
A useful picture: inline SVG is a window into the document the rest of your CSS can see through; an <img src="...svg"> is the same drawing but behind frosted glass — visible, sized, but unreachable from CSS.
How to choose
A short decision tree.
Inline SVG when:
- The SVG is small (an icon, a logo).
- You want CSS or JavaScript to manipulate it.
- The SVG is unique to the page (not reused elsewhere).
<img src="...svg"> when:
- The SVG is large (a complex illustration).
- The same SVG appears on many pages — the file caches.
- You do not need CSS to reach inside it.
Component-based SVG (Next.js, React, Astro, etc.) when:
- You have an icon system used across the site — the build tool inlines unique SVGs once per page, and you import them like JavaScript modules.
For very small icons (under ~1KB), inline almost always wins. The HTTP request to fetch a 200-byte file costs more than the bytes themselves. For larger SVGs, the cacheable external file pattern is more efficient on repeat visits.
Run inline SVGs through SVGO (or an equivalent optimizer) before pasting into your HTML. Hand-exported SVGs from Figma or Illustrator carry pages of metadata that bloat the file. SVGO strips it without touching the visible output.
<img src="icon.svg">. CSS is not affecting the icon's color. Why?