Inline SVG and icon markup

viewBox coordinates, currentColor, symbol sprites, accessible names and hiding decorative graphics.

Coordinates and viewBox

Inline SVG is markup in your document: it inherits CSS, scales without blurring, and needs no extra request. The viewBox defines the internal coordinate system, while width and height define the rendered size.

<svg viewBox="0 0 24 24" width="24" height="24"
     fill="none" stroke="currentColor" stroke-width="2"
     stroke-linecap="round">
  <path d="M4 12h16M12 4v16"/>
</svg>

viewBox="0 0 24 24" means the drawing lives on a 24 by 24 grid starting at the origin. Changing only width and height scales everything inside proportionally, so the same icon can render at 16px in a table and 48px in a hero.

  • Omit viewBox and the drawing clips instead of scaling — the single most common inline SVG bug.
  • preserveAspectRatio defaults to centring the drawing, which is usually what you want.
  • Stroke art needs fill="none"; filled art needs no stroke. Mixing the two is what makes icons look muddy.
  • Set vector-effect="non-scaling-stroke" when a stroked icon is scaled and the line weight should stay constant.

currentColor and sprite symbols

currentColor makes a shape take the computed color of its context, which means one icon file can serve a dark toolbar, a red danger button and a link hover with no extra assets.

<svg style="display:none" aria-hidden="true">
  <symbol id="icon-search" viewBox="0 0 24 24">
    <circle cx="11" cy="11" r="7"/>
    <path d="M16 16l4 4"/>
  </symbol>
</svg>

<button type="button" class="toolbar-btn">
  <svg width="20" height="20"><use href="#icon-search"></use></svg>
  Search
</button>
  • Define each icon once as a <symbol> and reference it with <use href="#id"> to keep markup short.
  • Ship the sprite container with display:none so the definitions never take up space.
  • Styling through <use> is limited: inherited properties such as color, fill and stroke reach the shadow content, but a class selector targeting an inner path does not.
  • An external sprite file must be same-origin and cannot be used to inherit currentColor across origins.

Naming and hiding icons

An inline SVG is a graphic in the accessibility tree. Whether it should be announced depends entirely on whether the icon carries meaning that is not already written next to it.

SituationCorrect markup
Icon conveys meaning alonerole="img" plus aria-label="Settings" on the SVG
Icon sits beside its own text labelaria-hidden="true" on the SVG
Icon is the only content of a buttonaria-label on the button, SVG hidden
Logo wrapped in a linkText or aria-label on the link naming the destination
Purely decorative flourisharia-hidden="true", no label
<button type="button" aria-label="Close">
  <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
    <path d="M6 6l12 12M18 6L6 18"/>
  </svg>
</button>

<svg viewBox="0 0 24 24" role="img" aria-label="Warning">
  <path d="M12 3l9 16H3z"/>
</svg>
⚠️
If an icon is the only content of a link or button and you forget to name it, the control is announced with no label at all. Reach for an <img> with appropriate alt instead when the graphic is complex, photographic, or not part of the surrounding text.

FAQ

Why does fill on my inline SVG have no effect?
The inner paths probably carry their own fill presentation attribute, which beats an inherited value. Set fill on the shape, or remove the attribute so the inherited colour applies.
Inline SVG or an img tag pointing at an SVG file?
Inline when the icon must inherit currentColor, be styled by CSS or be animated. An <img> when you want the file cached separately and referenced from many pages with a stable alt.

Responsive and performant images in practice Interactive elements without JavaScript

Last refreshed 2026-09-18.