How to use SVG icons in HTML: size, colour and labels

Use an SVG as an icon: inline or from a sprite with use, sized in em, coloured with currentColor, and named for screen readers. Code for buttons and links.

Published

To use an SVG as an icon in HTML, put the <svg> markup inline where the icon goes, give it a size, and set its colour to currentColor so it matches the text around it:

<button type="button">
  <svg viewBox="0 0 24 24" width="1em" height="1em" fill="currentColor" aria-hidden="true">
    <path d="M5 4h11l3 3v13H5z"/>
  </svg>
  Save
</button>

The icon is as tall as the button’s text and the same colour. aria-hidden="true" keeps it away from screen readers, because the word Save already says what the button does.

Four ways to place an icon

MethodLooks likeColour from CSSRequests
Inline SVG<svg viewBox="..."><path d="..."/></svg>YesNone
Sprite with <use><svg><use href="#icon-save"/></svg>Yes, by inheritanceNone for a sprite in the page, one for a sprite file
Image<img src="save.svg" alt="">NoOne for each icon
CSS maskmask: url(save.svg) over a background-colorYes, one flat colourOne for each icon

Inline SVG and <use> are the ways to choose when the icon must follow the text colour, change on hover or have parts styled separately. An <img> is fine for an icon that never changes colour. SVG in HTML compares the placements in general.

Size

An icon needs a viewBox and a size. The viewBox comes with the icon and says what area the drawing covers, usually 0 0 24 24 or 0 0 16 16. Leave it alone. The size is yours to set.

To getWrite
A fixed sizewidth="24" height="24" on the <svg>
A size that follows the textwidth="1em" height="1em", or the same two properties in CSS
One rule for every icon.icon { width: 1em; height: 1em; }

With 1em, an icon inside 32 pixel text measured 32 by 32 in Chrome, with no other change. Remove the size altogether and an inline SVG grows to the width of its container: in a 500 pixel div, the icon became 500 pixels square. That is the usual cause of a giant icon.

A stroke scales with the icon. An icon drawn with stroke-width="2" in a 24 unit viewBox has a 2 pixel line at 24 pixels and a 4 pixel line at 48.

Colour: currentColor

currentColor means “the value of the CSS color property here”. Set it as the icon’s fill or stroke and the icon takes the colour of its link, button or paragraph, including hover and focus colours, with no icon-specific CSS.

Which attribute depends on how the icon is drawn:

Icon styleOn the <svg>
Solid shapesfill="currentColor"
Outlinesfill="none" stroke="currentColor" stroke-width="2"

Without it, a solid icon is black whatever the text colour: fill defaults to black, not to the text colour.

Icons downloaded from a set often have a colour written on each path, such as fill="#000". Those attributes win over anything inherited from the <svg>, so replace them with currentColor or delete them. Changing SVG colour with CSS covers that in detail.

A sprite: define once, use many times

When the same icons appear many times on a page, define each one once as a <symbol> and draw it with <use>:

<svg width="0" height="0" style="position: absolute" aria-hidden="true">
  <symbol id="icon-save" viewBox="0 0 24 24">
    <path d="M5 4h11l3 3v13H5z"/>
  </symbol>
  <symbol id="icon-ring" viewBox="0 0 24 24">
    <circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" stroke-width="2"/>
  </symbol>
</svg>

<button type="button">
  <svg class="icon" width="24" height="24" fill="currentColor" aria-hidden="true"><use href="#icon-save"/></svg>
  Save
</button>

The first SVG draws nothing and takes no space. Each <use> shows one symbol, at the size of the <svg> around it. The viewBox lives on the symbol, so the outer <svg> needs only a width and height.

Colour still works, because fill and color are inherited into the copy. A selector cannot reach inside it, though: .icon path { fill: red } matches nothing, since the path is not a descendant of .icon in the page.

The sprite can also be a separate file, which the browser caches:

<svg width="24" height="24" fill="currentColor" aria-hidden="true">
  <use href="/icons.svg#icon-save"/>
</svg>

The file must come from the same origin as the page. Tested in Chrome, a sprite on another origin was refused, even with a permissive CORS header, with the message “Domains, protocols and ports must match”. MDN notes there is currently no defined way to set a cross-origin policy for <use>.

The SVG sprite generator builds the sprite from a set of icon files, and SVG g and use explains <symbol> and <use>.

Icons as images, and the mask trick

<img src="/icons/save.svg" width="24" height="24" alt="">

Simple and cached, but sealed: page CSS does not reach inside an image. Even an icon file that uses currentColor comes out black in an <img>, because inside the image there is no surrounding text colour to take.

To colour an icon file from CSS anyway, use it as a mask. The element is filled with a background colour and the icon decides where that colour shows:

<style>
  .icon-save {
    display: inline-block;
    width: 1em;
    height: 1em;
    background-color: currentColor;
    -webkit-mask: url(/icons/save.svg) no-repeat center / contain;
    mask: url(/icons/save.svg) no-repeat center / contain;
  }
</style>
<span class="icon-save" aria-hidden="true"></span>

The icon appears in the text colour. Only its shape is used, so this gives one flat colour. MDN’s compatibility data lists the unprefixed mask property from Chrome 120, Firefox 53 and Safari 15.4, and the -webkit- line covers older Chrome and Safari.

Names for screen readers

Whether an icon needs a text alternative depends on what else is there. These cases were checked in Chrome’s accessibility tree.

Icon beside a visible label. Hide the icon. The button is named by its text.

<button type="button">
  <svg aria-hidden="true" width="16" height="16" viewBox="0 0 16 16"><path d="M3 3l10 10" stroke="currentColor"/></svg>
  Save
</button>

Icon alone in a button or link. Name the control and hide the icon. Without the label, Chrome reported the button with an empty name.

<button type="button" aria-label="Close">
  <svg aria-hidden="true" width="16" height="16" viewBox="0 0 16 16">
    <path d="M3 3l10 10M13 3L3 13" stroke="currentColor" stroke-width="2"/>
  </svg>
</button>

The label should say what the control does, not what the picture shows: “Close”, not “Cross”. The W3C’s guidance on functional images gives the same rule, with “Print this page” for a printer icon.

Icon that carries meaning with no control round it, such as a warning sign next to a field. Give the SVG a role and a name:

<svg role="img" aria-label="Warning" width="16" height="16" viewBox="0 0 16 16">
  <path d="M8 1l7 14H1z"/>
</svg>

Chrome exposed this as an image named Warning. A <title> as the first child of the SVG, with role="img", gave the same name. For an <img>, the name is the alt text, and a decorative one takes alt="". Accessible SVG goes through each case.

Lining up and laying out

Two layout faults come up with icons.

The icon sits too high or too low beside text. An inline SVG rests on the text baseline. Make the parent a flex container with align-items: center, or set vertical-align: middle on the icon. How to centre an SVG has the measurements.

The icon is squashed or vanishes in a flex row. When a flex row runs out of room, the icon is allowed to shrink. In Chrome, a 24 pixel icon beside a long label that could not wrap shrank to a width of 0, and beside a wrapping label to 9 pixels. flex-shrink: 0 on the icon kept it at 24:

.icon {
  width: 1em;
  height: 1em;
  flex-shrink: 0;
}

SVG icons or an icon font

An icon font is a font file whose characters are pictures. An icon is placed by writing a character, often through a class name that inserts it with CSS.

SVG iconIcon font
ColoursAny number, each part styled separatelyOne colour for each character
Colour and size from the textWith currentColor and em sizesBuilt in
If the file fails to loadInline icons have no file to failThe character has no picture, so an empty box or a stray letter can show
Text alternativearia-label, <title> or altThe character is text, so it is usually hidden from screen readers and the control named separately
Adding your own iconAny SVG file will doThe font has to be rebuilt
AnimationParts can move separatelyThe character moves as a whole

For new work SVG is the simpler choice: nothing has to be generated, and an icon is just markup.

Common problems

What you seeCauseFix
The icon is hugeNo width or heightSet both, or a CSS rule for all icons
The icon is black, not the text colourNo fill="currentColor"Add it to the <svg>
currentColor set but the colour does not changePaths carry their own fillRemove those, or set them to currentColor
An outline icon shows as a solid blobIt needs fill="none" and a strokeCopy the attributes from the icon’s original <svg> tag
A <use> icon is emptyThe id does not match, or the sprite file is on another originCheck the id; serve the sprite from the page’s origin
A <use> icon is the wrong size or cut offThe <symbol> has no viewBoxMove the icon’s viewBox on to its symbol
CSS cannot recolour the iconIt is in an <img>Inline it, use a sprite, or use a mask
The icon disappears in a narrow rowFlex shrinkingflex-shrink: 0
A screen reader says nothing on an icon buttonNo accessible namearia-label on the button

Questions

Should every icon be inline? For a handful, yes. For dozens repeated across a page, a sprite keeps the HTML smaller, and a sprite file is fetched once and cached.

Can an icon have two colours? Yes. Leave one part on currentColor and give the other fill="var(--icon-accent, currentColor)", then set --icon-accent in CSS where a second colour is wanted. Custom properties are inherited into <use> copies too.

Do I still need xmlns? Not for SVG written in an HTML page. A separate file, including a sprite file, needs xmlns="http://www.w3.org/2000/svg" on its root. See SVG xmlns.

How do I use icons in React? As components that return the same markup: see SVG in React.