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
| Method | Looks like | Colour from CSS | Requests |
|---|---|---|---|
| Inline SVG | <svg viewBox="..."><path d="..."/></svg> | Yes | None |
Sprite with <use> | <svg><use href="#icon-save"/></svg> | Yes, by inheritance | None for a sprite in the page, one for a sprite file |
| Image | <img src="save.svg" alt=""> | No | One for each icon |
| CSS mask | mask: url(save.svg) over a background-color | Yes, one flat colour | One 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 get | Write |
|---|---|
| A fixed size | width="24" height="24" on the <svg> |
| A size that follows the text | width="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 style | On the <svg> |
|---|---|
| Solid shapes | fill="currentColor" |
| Outlines | fill="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 icon | Icon font | |
|---|---|---|
| Colours | Any number, each part styled separately | One colour for each character |
| Colour and size from the text | With currentColor and em sizes | Built in |
| If the file fails to load | Inline icons have no file to fail | The character has no picture, so an empty box or a stray letter can show |
| Text alternative | aria-label, <title> or alt | The character is text, so it is usually hidden from screen readers and the control named separately |
| Adding your own icon | Any SVG file will do | The font has to be rebuilt |
| Animation | Parts can move separately | The 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 see | Cause | Fix |
|---|---|---|
| The icon is huge | No width or height | Set both, or a CSS rule for all icons |
| The icon is black, not the text colour | No fill="currentColor" | Add it to the <svg> |
currentColor set but the colour does not change | Paths carry their own fill | Remove those, or set them to currentColor |
| An outline icon shows as a solid blob | It needs fill="none" and a stroke | Copy the attributes from the icon’s original <svg> tag |
A <use> icon is empty | The id does not match, or the sprite file is on another origin | Check the id; serve the sprite from the page’s origin |
A <use> icon is the wrong size or cut off | The <symbol> has no viewBox | Move the icon’s viewBox on to its symbol |
| CSS cannot recolour the icon | It is in an <img> | Inline it, use a sprite, or use a mask |
| The icon disappears in a narrow row | Flex shrinking | flex-shrink: 0 |
| A screen reader says nothing on an icon button | No accessible name | aria-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.