Swapping Out SVG Icons

Avatar of Chris Coyier
Chris Coyier on (Updated on )

Let’s say you’re using inline SVG and want to change the SVG icon that is displayed in an element on state change, like changing a class or on :hover/:focus. There are a number of ways you can approach that.

Technique #1: Hide/Show SVG Element

Include both icons:

<a href="#0" class="expand-link">
  <svg class="icon icon-expand" viewBox="0 0 32 32"><use xlink:href="#icon-expand"></use></svg>
  <svg class="icon icon-contract" viewBox="0 0 32 32"><use xlink:href="#icon-contract"></use></svg>
</a>

Hide one by default:

.expand-link .icon-contract {
  display: none;
}

When the state changes, swap the display property:

.expand-link:hover .icon-expand,
.expand-link:active .icon-expand{
  display: none;
}
.expand-link:hover .icon-contract,
.expand-link:active .icon-contract{
  display: block;
}

See the Pen praEH by Chris Coyier (@chriscoyier) on CodePen.

Technique #2: Hide/Show Paths

You won’t be able to use the <use> technique with this (because a state change on an HTML element happens at a level above the shadow root the <use> creates, thus CSS can’t penetrate through) but you can hide/show shape elements directly if the inline SVG is just right in there raw:

<a href="#0"class="icon-expand-link">
  <svg ... >
    <path class="expand" d="M32 0v12l-etc"></path>
    <path class="contract" d="M2 18h12v12l-etc"></path>
  </svg>
</a>
.icon-expand-link .contract {
  display: none;
}
.icon-expand-link:hover .expand {
  display: none;
}
.icon-expand-link:hover .contract {
  display: block;
}

See the Pen cCiBn by Chris Coyier (@chriscoyier) on CodePen.

Technique #3: Alter the xlink:href

Using JavaScript, you could alter the chunk of SVG that is referenced in the <use>. Seems weird to do styling things with JavaScript, but, not having duplicated markup is nice, and if you reference a <symbol>, the <title> and <desc> would change too which is nice.

<a href="#0" class="expand-link">
  <svg class="icon icon-expand" viewBox="0 0 32 32"><use id="target" xlink:href="#icon-expand"></use></svg>
</a>
$(".expand-link")
  .on("mouseenter", function() {
    $(this).find("use").attr("xlink:href", "#icon-contract");
  })
  .on("mouseleave", function() {
    $(this).find("use").attr("xlink:href", "#icon-expand");
  });

See the Pen Dqpib by Chris Coyier (@chriscoyier) on CodePen.

Technique #4: Restyle with CSS

Remember you can do a lot with CSS as well. Perhaps you don’t need an entirely different set of shapes, but you can just change things with CSS to essentially make a new icon.

If you don’t set styles on anything inside the SVG, you can set them directly on the SVG (essentially crossing the shadow DOM boundary). Plus you can do CSS transforms and such to rotate/scale/move:

See the Pen JFjdl by Chris Coyier (@chriscoyier) on CodePen.

Not using inline SVG?

If you aren’t using inline SVG, but instead using SVG-as-<img>, this hide-show stuff or altering the source should work fine.

If you’re using SVG as background-image, changing the source is also an option. Also know that even if you use Data URI’s in your CSS for multiple versions of an icon, that can seem bloat-y, but if they are pretty similar GZIP can do great work on that.