svg icons – CSS-Tricks https://css-tricks.com Tips, Tricks, and Techniques on using Cascading Style Sheets. Wed, 28 Sep 2022 14:30:24 +0000 en-US hourly 1 https://wordpress.org/?v=6.5.5 https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/07/star.png?fit=32%2C32&ssl=1 svg icons – CSS-Tricks https://css-tricks.com 32 32 45537868 How I Made an Icon System Out of CSS Custom Properties https://css-tricks.com/how-i-made-an-icon-system-out-of-css-custom-properties/ https://css-tricks.com/how-i-made-an-icon-system-out-of-css-custom-properties/#comments Thu, 22 Sep 2022 15:17:21 +0000 https://css-tricks.com/?p=373111 SVG is the best format for icons on a website, there is no doubt about that. It allows you to have sharp icons no matter the screen pixel density, you can change the styles of the SVG on hover …


How I Made an Icon System Out of CSS Custom Properties originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
SVG is the best format for icons on a website, there is no doubt about that. It allows you to have sharp icons no matter the screen pixel density, you can change the styles of the SVG on hover and you can even animate the icons with CSS or JavaScript.

There are many ways to include an SVG on a page and each technique has its own advantages and disadvantages. For the last couple of years, I have been using a Sass function to import directly my icons in my CSS and avoid having to mess up my HTML markup.

I have a Sass list with all the source codes of my icons. Each icon is then encoded into a data URI with a Sass function and stored in a custom property on the root of the page.

TL;DR

What I have for you here is a Sass function that creates a SVG icon library directly in your CSS.

The SVG source code is compiled with the Sass function that encodes them in data URI and then stores the icons in CSS custom properties. You can then use any icon anywhere in your CSS like as if it was an external image.

This is an example pulled straight from the code of my personal site:

.c-filters__summary h2:after {
  content: var(--svg-down-arrow);
  position: relative;
  top: 2px;
  margin-left: auto;
  animation: closeSummary .25s ease-out;
}

Demo

Sass structure

/* All the icons source codes */
$svg-icons: (
  burger: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0...'
);

/* Sass function to encode the icons */
@function svg($name) {
  @return url('data:image/svg+xml, #{$encodedSVG} ');
}

/* Store each icon into a custom property */
:root {
  @each $name, $code in $svg-icons {
    --svg-#{$name}: #{svg($name)};
  }
}

/* Append a burger icon in my button */
.menu::after {
  content: var(--svg-burger);
}		

This technique has both pros and cons, so please take them into account before implementing this solution on your project:

Pros

  • There are no HTTP requests for the SVG files.
  • All of the icons are stored in one place.
  • If you need to update an icon, you don’t have to go over each HTML templates file.
  • The icons are cached along with your CSS.
  • You can manually edit the source code of the icons.
  • It does not pollute your HTML by adding extra markup.
  • You can still change the color or some aspect of the icon with CSS.

Cons

  • You cannot animate or update a specific part of the SVG with CSS.
  • The more icons you have, the heavier your CSS compiled file will be.

I mostly use this technique for icons rather than logos or illustrations. An encoded SVG is always going to be heavier than its original file, so I still load my complex SVG with an external file either with an <img> tag or in my CSS with url(path/to/file.svg).

Encoding SVG into data URI

Encoding your SVG as data URIs is not new. In fact Chris Coyier wrote a post about it over 10 years ago to explain how to use this technique and why you should (or should not) use it.

There are two ways to use an SVG in your CSS with data URI:

  • As an external image (using background-image,border-image,list-style-image,…)
  • As the content of a pseudo element (e.g. ::before or ::after)

Here is a basic example showing how you how to use those two methods:

The main issue with this particular implementation is that you have to convert the SVG manually every time you need a new icon and it is not really pleasant to have this long string of unreadable code in your CSS.

This is where Sass comes to the rescue!

Using a Sass function

By using Sass, we can make our life simpler by copying the source code of our SVG directly in our codebase, letting Sass encode them properly to avoid any browser error.

This solution is mostly inspired by an existing function developed by Threespot Media and available in their repository.

Here are the four steps of this technique:

  • Create a variable with all your SVG icons listed.
  • List all the characters that needs to be skipped for a data URI.
  • Implement a function to encode the SVGs to a data URI format.
  • Use your function in your code.

1. Icons list

/**
* Add all the icons of your project in this Sass list
*/
$svg-icons: (
  burger: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24.8 18.92" width="24.8" height="18.92"><path d="M23.8,9.46H1m22.8,8.46H1M23.8,1H1" fill="none" stroke="#000" stroke-linecap="round" stroke-width="2"/></svg>'
);

2. List of escaped characters

/**
* Characters to escape from SVGs
* This list allows you to have inline CSS in your SVG code as well
*/
$fs-escape-chars: (
  ' ': '%20',
  '\'': '%22',
  '"': '%27',
  '#': '%23',
  '/': '%2F',
  ':': '%3A',
  '(': '%28',
  ')': '%29',
  '%': '%25',
  '<': '%3C',
  '>': '%3E',
  '\\': '%5C',
  '^': '%5E',
  '{': '%7B',
  '|': '%7C',
  '}': '%7D',
);

3. Encode function

/**
* You can call this function by using `svg(nameOfTheSVG)`
*/
@function svg($name) {
  // Check if icon exists
  @if not map-has-key($svg-icons, $name) {
    @error 'icon “#{$name}” does not exists in $svg-icons map';
    @return false;
  }

  // Get icon data
  $icon-map: map-get($svg-icons, $name);

  $escaped-string: '';
  $unquote-icon: unquote($icon-map);
  // Loop through each character in string
  @for $i from 1 through str-length($unquote-icon) {
    $char: str-slice($unquote-icon, $i, $i);

    // Check if character is in symbol map
    $char-lookup: map-get($fs-escape-chars, $char);

    // If it is, use escaped version
    @if $char-lookup != null {
        $char: $char-lookup;
    }

    // Append character to escaped string
    $escaped-string: $escaped-string + $char;
  }

  // Return inline SVG data
  @return url('data:image/svg+xml, #{$escaped-string} ');
}		

4. Add an SVG in your page

button {
  &::after {
    /* Import inline SVG */
    content: svg(burger);
  }
}

If you have followed those steps, Sass should compile your code properly and output the following:

button::after {
  content: url("data:image/svg+xml, %3Csvg%20xmlns=%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox=%270%200%2024.8%2018.92%27%20width=%2724.8%27%20height=%2718.92%27%3E%3Cpath%20d=%27M23.8,9.46H1m22.8,8.46H1M23.8,1H1%27%20fill=%27none%27%20stroke=%27%23000%27%20stroke-linecap=%27round%27%20stroke-width=%272%27%2F%3E%3C%2Fsvg%3E ");
}		

Custom properties

The now-implemented Sass svg() function works great. But its biggest flaw is that an icon that is needed in multiple places in your code will be duplicated and could increase your compiled CSS file weight by a lot!

To avoid this, we can store all our icons into CSS variables and use a reference to the variable instead of outputting the encoded URI every time.

We will keep the same code we had before, but this time we will first output all the icons from the Sass list into the root of our webpage:

/**
  * Convert all icons into custom properties
  * They will be available to any HTML tag since they are attached to the :root
  */

:root {
  @each $name, $code in $svg-icons {
    --svg-#{$name}: #{svg($name)};
  }
}

Now, instead of calling the svg() function every time we need an icon, we have to use the variable that was created with the --svg prefix.

button::after {
  /* Import inline SVG */
  content: var(--svg-burger);
}

Optimizing your SVGs

This technique does not provide any optimization on the source code of the SVG you are using. Make sure that you don’t leave unnecessary code; otherwise they will be encoded as well and will increase your CSS file size.

You can check this great list of tools and information on how to optimize properly your SVG. My favorite tool is Jake Archibald’s SVGOMG — simply drag your file in there and copy the outputted code.

Bonus: Updating the icon on hover

With this technique, we cannot select with CSS specific parts of the SVG. For example, there is no way to change the fill color of the icon when the user hovers the button. But there are a few tricks we can use with CSS to still be able to modify the look of our icon.

For example, if you have a black icon and you want to have it white on hover, you can use the invert() CSS filter. We can also play with the hue-rotate() filter.

Bonus #2: Updating the icon using CSS mask-image property

Another trick to be able to change the color of your icon, is to use it as a mask on your pseudo-element with a background. Set your pseudo-element as inline-block with a background-color and define a width & height for the size needed.

Once you have a rectangle with the color needed, apply those four values to only keep the shape of the SVG needed:

  • mask-image: var(--svg-burger): The reference to our icon.
  • mask-repeat: no-repeat: To prevent the mask to be duplicated.
  • mask-size: contain: To make the icon fit perfectly in the rectangle.
  • mask-position: center: To center our icon in the pseudo-element.

Don’t forget that all CSS mask properties still need to be prefixed with -webkit- for most browsers as of September 2022.

Thanks to Christopher and Mike for letting me know about this trick in the comments!

That’s it!

I hope you find this little helper function handy in your own projects. Let me know what you think of the approach — I’d be interested to know how you’d make this better or tackle it differently!


How I Made an Icon System Out of CSS Custom Properties originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/how-i-made-an-icon-system-out-of-css-custom-properties/feed/ 6 373111
7 Fresh Links on Performance For March 2022 https://css-tricks.com/performance-links-february-2022/ https://css-tricks.com/performance-links-february-2022/#respond Wed, 02 Mar 2022 21:26:50 +0000 https://css-tricks.com/?p=364350 I have a handful of good links to articles about performance that are burning a hole in my bookmarks folder, and wanna drop them here to share.

The new WebPageTest website design


7 Fresh Links on Performance For March 2022 originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
I have a handful of good links to articles about performance that are burning a hole in my bookmarks folder, and wanna drop them here to share.

Screenshot of the new WebPageTest homepage, a tool for testing performance metrics.
The new WebPageTest website design

7 Fresh Links on Performance For March 2022 originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/performance-links-february-2022/feed/ 0 364350
Which SVG technique performs best for way too many icons? https://css-tricks.com/which-svg-technique-performs-best-for-way-too-many-icons/ https://css-tricks.com/which-svg-technique-performs-best-for-way-too-many-icons/#respond Tue, 23 Nov 2021 19:21:28 +0000 https://css-tricks.com/?p=357412 Tyler Sticka digs in here in the best possible way: by making a test page and literally measuring performance. Maybe 1,000 icons is a little bit of an edge case, but hey, 250 rows of data with four icons in …


Which SVG technique performs best for way too many icons? originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Tyler Sticka digs in here in the best possible way: by making a test page and literally measuring performance. Maybe 1,000 icons is a little bit of an edge case, but hey, 250 rows of data with four icons in each gets you there. Tyler covers the nuances carefully in the post. The different techniques tested: inline <svg>, same-document sprite <symbol>s, external-document sprite <symbol>s, <img> with an external source, <img> with a data URL, <img> with a filter, <div> with a background-image of an external source, <div> with a background-image of a data URL, and a <div> with a mask. Phew! That’s a lot — and they are all useful techniques in their own right.

Which technique won? Inline <svg>, unless the SVGs are rather complex, then <img> is better. That’s what I would have put my money on. I’ve been on that train for a while now.

To Shared LinkPermalink on CSS-Tricks


Which SVG technique performs best for way too many icons? originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/which-svg-technique-performs-best-for-way-too-many-icons/feed/ 0 357412
Accessible SVG Icons https://css-tricks.com/accessible-svg-icons/ https://css-tricks.com/accessible-svg-icons/#comments Mon, 28 Dec 2020 20:34:37 +0000 https://css-tricks.com/?p=331142 The answer to “What is the most accessible HTML for an SVG icon?” isn’t one-size-fits all, because what an icon needs to do on a website varies. I’m partial to Heather Migliorisi’s research on all this, but I can summarize. …


Accessible SVG Icons originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
The answer to “What is the most accessible HTML for an SVG icon?” isn’t one-size-fits all, because what an icon needs to do on a website varies. I’m partial to Heather Migliorisi’s research on all this, but I can summarize. Extremely quickly: hide it if it’s decorative, title it if it’s stand-alone, let the link do the work if it’s a link. Here are those three possibilities:

The icon is decorative

As in, the icon is just sitting there looking pretty but it doesn’t matter if it entirely went away. If that’s the case:

<svg aria-hidden="true" ... ></svg>

There’s no need to announce the icon because the label next to it already does the job. So, instead of reading it, we hide it from screen readers. That way, the label does what it’s supposed to do without the SVG stepping on its toes.

The icon is stand-alone

What we mean here is that the icon is unaccompanied by a visible text label, and is a meaningful action trigger on its own. This is a tricky one. It’s gotten better over time, where all you need for modern browsers is:

<svg role="img"><title>Good Label</title> ... </svg>. 

This works for an SVG inside a <button>, say, or if the SVG itself is playing the “button” role.

…and the link is the meaningful action. What’s important is that the link has good text. If the link has visible text, then the icon is decorative. If the SVG is the link where it’s wrapped in an <a> (rather than am internal-SVG link), then, give it an accessible label, like:

<a href="/" aria-label="Good Label"><svg aria-hidden="true" ... ></svg></a>

…or, have a <span class="screen-reader-only"> text within the link and the hidden SVG.


I believe this syncs up correctly with advice not only from Heather, but with Sara, Kitty, and Florens as well.


Accessible SVG Icons originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/accessible-svg-icons/feed/ 6 331142
Super Tiny Icons https://css-tricks.com/super-tiny-icons/ https://css-tricks.com/super-tiny-icons/#comments Mon, 30 Nov 2020 19:35:28 +0000 https://css-tricks.com/?p=326352 A bunch of SVG icons (of popular things) all under 1KB. SVG is awesome for golfing.

I was going to add a CodePen logo but there is already one in there at 375 Bytes. I’ve got one at 208


Super Tiny Icons originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
A bunch of SVG icons (of popular things) all under 1KB. SVG is awesome for golfing.

I was going to add a CodePen logo but there is already one in there at 375 Bytes. I’ve got one at 208 Bytes, based on a logo update David DeSandro did for us a couple years back.

To Shared LinkPermalink on CSS-Tricks


Super Tiny Icons originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/super-tiny-icons/feed/ 1 326352
SVGBOX https://css-tricks.com/svgbox/ https://css-tricks.com/svgbox/#comments Fri, 13 Nov 2020 00:06:37 +0000 https://css-tricks.com/?p=325632 I’ve been saying for years that a pretty good icon system is just dropping in icons with inline <svg> where you need them. This is simple to do, offers full design control, has (generally) good performance, and means you aren’t …


SVGBOX originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
I’ve been saying for years that a pretty good icon system is just dropping in icons with inline <svg> where you need them. This is simple to do, offers full design control, has (generally) good performance, and means you aren’t smurfing around with caching and browser support stuff.

Along those lines… using <img> isn’t the worst idea for icons either. It doesn’t offer as much fine-grained design control (although you can still filter them) and arguably isn’t quite as fast (since the images need to be fetched separately from the document), but it still has many of the same upsides as inline SVG icons.

Shubham Jain has a project called SVGBOX that offers icons-as-<img> and removes one of the design-control limitations by offering a URL parameter to change colors.

Want an Instagram icon, but in red? Pass in red:

If you’re going to use a bunch of icons, the provided copy-and-paste code offers an “SVG sprite” version where the URL is like this:

<img src="//s.svgbox.net/social.svg?fill=805ad5#instagram">

That is going to increase the download weight of the icon (because it’s downloading all the icons from this set), but possibly be more efficient as it’s a single download not many. Hard to say if that’s more efficient or not these days, with HTTP/2 around.

What’s interesting is the #instagram part at the end of the URL. Just a hash-link, right? No! Fancier! In SVG land, that can be a fragment identifier, meaning it will only show the bit of the SVG that matches the proper <view> element. Don’t see that every day.

To Shared LinkPermalink on CSS-Tricks


SVGBOX originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/svgbox/feed/ 2 325632
Some New Icon Sets https://css-tricks.com/some-new-icon-sets/ https://css-tricks.com/some-new-icon-sets/#comments Tue, 29 Sep 2020 15:03:59 +0000 https://css-tricks.com/?p=321623 I’ve bookmarked some icon sets lately, partly because I can never find a nice set when I need to. I figured I’d even go the extra mile here and blog them so I can definitely find them later. Aside from …


Some New Icon Sets originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
I’ve bookmarked some icon sets lately, partly because I can never find a nice set when I need to. I figured I’d even go the extra mile here and blog them so I can definitely find them later. Aside from being nice, cohesive, and practical sets of icons, I find it interesting that literally all of them:

  • are SVG, and thus easily resizeable
  • are built with rather efficient <path> elements
  • are stroked instead of filled (at least optionally)
  • have a click-to-copy SVG feature on their site
  • are free and open source

Good job, all! Seems like people are coming around to the idea of an SVG icon system where you just… put the SVG in the HTML.

Tabler Icons

Teenyicons

Heroicons

hola svg

This one is from Mariana Beldi who recently shared how hand-drawing SVG can be so much more efficient than using an illustration tool.


Some New Icon Sets originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/some-new-icon-sets/feed/ 12 321623
A Font-Like SVG Icon System for Vue https://css-tricks.com/a-font-like-svg-icon-system-for-vue/ https://css-tricks.com/a-font-like-svg-icon-system-for-vue/#comments Fri, 24 Jul 2020 14:07:35 +0000 https://css-tricks.com/?p=317398 Managing a custom collection of icons in a Vue app can be challenging at times. An icon font is easy to use, but for customization, you have to rely on third-party font generators, and merge conflicts can be painful to …


A Font-Like SVG Icon System for Vue originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Managing a custom collection of icons in a Vue app can be challenging at times. An icon font is easy to use, but for customization, you have to rely on third-party font generators, and merge conflicts can be painful to resolve since fonts are binary files.

Using SVG files instead can eliminate those pain points, but how can we ensure they’re just as easy to use while also making it easy to add or remove icons?

Here is what my ideal icon system looks like:

  • To add icons, you just drop them into a designated icons folder. If you no longer need an icon, you simply delete it.
  • To use the rocket.svg icon in a template, the syntax is as simple as <svg-icon icon="rocket" />.
  • The icons can be scaled and colored using the CSS font-size and color properties (just like an icon font).
  • If multiple instances of the same icon appear on the page, the SVG code is not duplicated each time.
  • No webpack config editing is required.

This is what we will build by writing two small, single-file components. There are a few specific requirements for this implementation, though I’m sure many of you wizards out there could rework this system for other frameworks and build tools:

  • webpack: If you used the Vue CLI to scaffold your app, then you’re already using webpack.
  • svg-inline-loader: This allows us to load all of our SVG code and clean up portions we do not want. Go ahead and run npm install svg-inline-loader --save-dev from the terminal to get started.

The SVG sprite component

To meet our requirement of not repeating SVG code for each instance of an icon on the page, we need to build an SVG “sprite.” If you haven’t heard of an SVG sprite before, think of it as a hidden SVG that houses other SVGs. Anywhere we need to display an icon, we can copy it out of the sprite by referencing the id of the icon inside a <use> tag like this:

<svg><use xlink:href="#rocket" /></svg>

That little bit of code is essentially how our <SvgIcon> component will work, but let’s go ahead create the <SvgSprite> component first. Here is the entire SvgSprite.vue file; some of it may seem daunting at first, but I will break it all down.

<!-- SvgSprite.vue -->

<template>
  <svg width="0" height="0" style="display: none;" v-html="$options.svgSprite" />
</template>

<script>
const svgContext = require.context(
  '!svg-inline-loader?' + 
  'removeTags=true' + // remove title tags, etc.
  '&removeSVGTagAttrs=true' + // enable removing attributes
  '&removingTagAttrs=fill' + // remove fill attributes
  '!@/assets/icons', // search this directory
  true, // search subdirectories
  /\w+\.svg$/i // only include SVG files
)
const symbols = svgContext.keys().map(path => {
  // get SVG file content
  const content = svgContext(path)
   // extract icon id from filename
  const id = path.replace(/^\.\/(.*)\.\w+$/, '$1')
  // replace svg tags with symbol tags and id attribute
  return content.replace('<svg', `<symbol id="${id}"`).replace('svg>', 'symbol>')
})
export default {
  name: 'SvgSprite',
  svgSprite: symbols.join('\n'), // concatenate all symbols into $options.svgSprite
}
</script>

In the template, our lone <svg> element has its content bound to $options.svgSprite. In case you’re unfamiliar with $options it contains properties that are directly attached to our Vue component. We could have attached svgSprite to our component’s data, but we don’t really need Vue to set up reactivity for this since our SVG loader is only going to run when our app builds.

In our script, we use require.context to retrieve all of our SVG files and clean them up while we’re at it. We invoke svg-inline-loader and pass it several parameters using syntax that is very similar to query string parameters. I’ve broken these up into multiple lines to make them easier to understand.

const svgContext = require.context(
  '!svg-inline-loader?' + 
  'removeTags=true' + // remove title tags, etc.
  '&removeSVGTagAttrs=true' + // enable removing attributes
  '&removingTagAttrs=fill' + // remove fill attributes
  '!@/assets/icons', // search this directory
  true, // search subdirectories
  /\w+\.svg$/i // only include SVG files
)

What we’re basically doing here is cleaning up the SVG files that live in a specific directory (/assets/icons) so that they’re in good shape to use anywhere we need them.

The removeTags parameter strips out tags that we do not need for our icons, such as title and style. We especially want to remove title tags since those can cause unwanted tooltips. If you would like to preserve any hard-coded styling in your icons, then add removingTags=title as an additional parameter so that only title tags are removed.

We also tell our loader to remove fill attributes, so that we can set our own fill colors with CSS later. It’s possible you will want to retain your fill colors. If that’s the case, then simply remove the removeSVGTagAttrs and removingTagAttrs parameters.

The last loader parameter is the path to our SVG icon folder. We then provide require.context with two more parameters so that it searches subdirectories and only loads SVG files.

In order to nest all of our SVG elements inside our SVG sprite, we have to convert them from <svg> elements into SVG <symbol> elements. This is as simple as changing the tag and giving each one a unique id, which we extract from the filename.

const symbols = svgContext.keys().map(path => {
  // extract icon id from filename
  const id = path.replace(/^\.\/(.*)\.\w+$/, '$1')
  // get SVG file content
  const content = svgContext(path)
  // replace svg tags with symbol tags and id attribute
  return content.replace('<svg', `<symbol id="${id}"`).replace('svg>', 'symbol>')
})

What do we do with this <SvgSprite> component? We place it on our page before any icons that depend on it. I recommend adding it to the top of the App.vue file.

<!-- App.vue -->
<template>
  <div id="app">
    <svg-sprite />
<!-- ... -->

The icon component

Now let’s build the SvgIcon.vue component.

<!-- SvgIcon.vue -->

<template>
  <svg class="icon" :class="{ 'icon-spin': spin }">
    <use :xlink:href="`#${icon}`" />
  </svg>
</template>

<script>
export default {
  name: 'SvgIcon',
  props: {
    icon: {
      type: String,
      required: true,
    },
    spin: {
      type: Boolean,
      default: false,
    },
  },
}
</script>

<style>
svg.icon {
  fill: currentColor;
  height: 1em;
  margin-bottom: 0.125em;
  vertical-align: middle;
  width: 1em;
}
svg.icon-spin {
  animation: icon-spin 2s infinite linear;
}
@keyframes icon-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(359deg);
  }
}
</style>

This component is much simpler. As previously mentioned, we leverage the <use> tag to reference an id inside our sprite. That id comes from our component’s icon prop.

I’ve added a spin prop in there that toggles an .icon-spin class as an optional bit of animation, should we ever need. This could, for example, be useful for a loading spinner icon.

<svg-icon v-if="isLoading" icon="spinner" spin />

Depending on your needs, you may want to add additional props, such as rotate or flip. You could simply add the classes directly to the component without using props if you’d like.

Most of our component’s content is CSS. Other than the spinning animation, most of this is used to make our SVG icon act more like an icon font¹. To align the icons to the text baseline, I’ve found that applying vertical-align: middle, along with a bottom margin of 0.125em, works for most cases. We also set the fill attribute value to currentColor, which allows us to color the icon just like text.

<p style="font-size: 2em; color: red;">
  <svg-icon icon="exclamation-circle" /><!-- This icon will be 2em and red. -->
  Error!
</p>

That’s it!  If you want to use the icon component anywhere in your app without having to import it into every component that needs it, be sure to register the component in your main.js file:

// main.js
import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon.vue'
Vue.component('svg-icon', SvgIcon)
// ...

Final thoughts

Here are a few ideas for improvements, which I intentionally left out to keep this solution approachable:

  • Scale icons that have non-square dimensions to maintain their proportions
  • Inject the SVG sprite into the page without needing an additional component.
  • Make it work with vite, which is a new, fast (and webpack-free) build tool from Vue creator Evan You.
  • Leverage the Vue 3 Composition API.

If you want to quickly take these components for a spin, I’ve created a demo app based on the default vue-cli template. I hope this helps you develop an implementation that fits your app’s needs!


¹ If you’re wondering why we’re using SVG when we want it to behave like an icon font, then check out the classic post that pits the two against one another.


A Font-Like SVG Icon System for Vue originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/a-font-like-svg-icon-system-for-vue/feed/ 9 317398
SVG, Favicons, and All the Fun Things We Can Do With Them https://css-tricks.com/svg-favicons-and-all-the-fun-things-we-can-do-with-them/ https://css-tricks.com/svg-favicons-and-all-the-fun-things-we-can-do-with-them/#comments Fri, 24 Apr 2020 14:58:29 +0000 https://css-tricks.com/?p=306644 Favicons are the little icons you see in your browser tab. They help you understand which site is which when you’re scanning through your browser’s bookmarks and open tabs. They’re a neat part of internet history that are capable of …


SVG, Favicons, and All the Fun Things We Can Do With Them originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Favicons are the little icons you see in your browser tab. They help you understand which site is which when you’re scanning through your browser’s bookmarks and open tabs. They’re a neat part of internet history that are capable of performing some cool tricks.

One very new trick is the ability to use SVG as a favicon. It’s something that most modern browsers support, with more support on the way.

Here’s the code for how to add favicons to your site. Place this in your website’s <head>:

<link rel="icon" href="/favicon.ico" sizes="any">
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<link rel="manifest" href="/manifest.webmanifest">

And place this code in your site’s web app manifest:

{
  "icons": [
    { "src": "/icon-192.png", "type": "image/png", "sizes": "192x192" },
    { "src": "/icon-512.png", "type": "image/png", "sizes": "512x512" }
  ]
}

Browsers that do support SVG favicons will override the first link element declaration and honor the second instead. Browsers that do not support SVG favicons but do support web app manifests will use the higher-resolution images. All other browsers fall back to using the favicon.ico file. This ensures that all browsers that support favicons can enjoy the experience. 

You may also notice the alternate attribute value for our rel declaration in the second line. This programmatically communicates to the browser that the favicon with a file format that uses .ico is specifically used as an alternate presentation.

Following the favicons is a line of code that loads another SVG image, one called safari-pinned-tab.svg. This is to support Safari’s pinned tab functionality, which existed before other browsers had SVG favicon support. There’s additional files you can add here to enhance your site for different apps and services, but more on that in a bit.

Here’s more detail on the current level of SVG favicon support:

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

ChromeFirefoxIEEdgeSafari
8041No80No

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
126NoNoNo

Why SVG?

You may be questioning why this is needed. The .ico file format has been around forever and can support images up to 256×256 pixels in size. Here are three answers for you.

Ease of authoring

It’s a pain to make .ico files. The file is a proprietary format used by Microsoft, meaning you’ll need specialized tools to make them. SVG is an open standard, meaning you can use them without any further tooling or platform lock-in.

Future-proofing

Retina? 5k? 6k? When we use a resolution-agnostic SVG file for a favicon, we guarantee that our favicons look crisp on future devices, regardless of how large their displays get

Performance

SVGs are usually very small files, especially when compared to their raster image counterparts — even more-so if you optimize them beforehand. By only using a 16×16 pixel favicon as a fallback for browsers that don’t support SVG, we provide a combination that enjoys a high degree of support with a smaller file size to boot. 

This might seem a bit extreme, but when it comes to web performance, every byte counts!

Tricks

Another cool thing about SVG is we can embed CSS directly in it. This means we can do fun things like dynamically adjust them with JavaScript, provided the SVG is declared inline and not embedded using an img element.

<svg  version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
  <style>
    path { fill: #272019; }
  </style>
  <!-- etc. -->
</svg>

Since SVG favicons are embedded using the link element, they can’t really be modified using JavaScript. We can, however, use things like emoji and media queries.

Emoji

Lea Verou had a genius idea about using emoji inside of SVG’s text element to make a quick favicon with a transparent background that holds up at small sizes.

In response, Chris Coyier whipped up a neat little demo that lets you play around with the concept.

Dark Mode support

Both Thomas Steiner and Mathias Bynens independently stumbled across the idea that you can use the prefers-color-scheme media query to provide support for dark mode. This work is built off of Jake Archibald’s exploration of SVG and media queries.

<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg">
  <style>
    path { fill: #000000; }
    @media (prefers-color-scheme: dark) {
      path { fill: #ffffff; }
    }
  </style>
  <path d="M111.904 52.937a1.95 1.95 0 00-1.555-1.314l-30.835-4.502-13.786-28.136c-.653-1.313-2.803-1.313-3.456 0L48.486 47.121l-30.835 4.502a1.95 1.95 0 00-1.555 1.314 1.952 1.952 0 00.48 1.99l22.33 21.894-5.28 30.918c-.115.715.173 1.45.768 1.894a1.904 1.904 0 002.016.135L64 95.178l27.59 14.59c.269.155.576.232.883.232a1.98 1.98 0 001.133-.367 1.974 1.974 0 00.768-1.894l-5.28-30.918 22.33-21.893c.518-.522.71-1.276.48-1.99z" fill-rule="nonzero"/>
</svg>

For supporting browsers, this code means our star-shaped SVG favicon will change its fill color from black to white when dark mode is activated. Pretty neat!

Other media queries

Dark mode support got me thinking: if SVGs can support prefers-color-scheme, what other things can we do with them? While the support for Level 5 Media Queries may not be there yet, here’s some ideas to consider:

Mockup of four SVG favicon treatments. The first treatment is a pink star with a tab title of, “SVG Favicon.” The second treatment is a hot pink star with a tab title of, “Light Level SVG Favicon.” The third treatment is a light pink star with a tab title of, “Inverted Colors SVG Favicon.” The fourth treatment is a black pink star with a tab title of, “High Contrast Mode SVG Favicon.” The tabs are screen captures from Microsoft Edge, with the browser chrome updating to match each specialized mode.
A mockup of how these media query-based adjustments could work.

Keep it crisp

Another important aspect of good favicon design is making sure they look good in the small browser tab area. The secret to this is making the paths of the vector image line up to the pixel grid, the guide a computer uses to turn SVG math into the bitmap we see on a screen. 

Here’s a simplified example using a square shape:

A crisp orange square on a white background. There is also a faint grid of gray horizontal and vertical lines that represent the pixel grid. Screenshot from Figma.

When the vector points of the square align to the pixel grid of the artboard, the antialiasing effect a computer uses to smooth out the shapes isn’t needed. When the vector points aren’t aligned, we get a “smearing” effect:

A blurred orange square on a white background. There is also a faint grid of gray horizontal and vertical lines that represent the pixel grid. Screenshot from Figma.

A vector point’s position can be adjusted on the pixel grid by using a vector editing program such as Figma, Sketch, Inkscape, or Illustrator. These programs export SVGs as well. To adjust a vector point’s location, select each node with a precision selection tool and drag it into position.

Some more complicated icons may need to be simplified, in order to look good at such a small size. If you’re looking for a good primer on this, Jeremy Frank wrote a really good two-part article over at Vidget.

Go the extra mile

In addition to favicons, there are a bunch of different (and unfortunately proprietary) ways to use icons to enhance its experience. These include things like the aforementioned pinned tab icon for Safari¹, chat app unfurls, a pinned Windows start menu tile, social media previews, and homescreen launchers.

If you’re looking for a great place to get started with these kinds of enhancements, I really like realfavicongenerator.net.

Icon output from realfavicongenerator.net arranged in a grid using CSS-Trick’s logo. There are two rows of five icons: android-chrome-192x192.png, android-chrome-384x384.png, apple-touch-icon.png, favicon-16x16.png, favicon-32x32.png, mstile-150x150.png, safari-pinned-tab.svg, favicon.ico, browserconfig.xml, and site.webmanifest.
It’s a lot, but it guarantees robust support.

A funny thing about the history of the favicon: Internet Explorer was the first browser to support them and they were snuck in at the 11th hour by a developer named Bharat Shyam:

As the story goes, late one night, Shyam was working on his new favicon feature. He called over junior project manager Ray Sun to take a look.

Shyam commented, “This is good, right? Check it in?”, requesting permission to check the code into the Internet Explorer codebase so it could be released in the next version. Sun didn’t think too much of it, the feature was cool and would clearly give IE an edge. So he told Shyam to go ahead and add it. And just like that, the favicon made its way into Internet Explorer 5, which would go on to become one of the largest browser releases the web has ever seen.

The next day, Sun was reprimanded by his manager for letting the feature get by so quickly. As it turns out, Shyam had specifically waited until later in the day, knowing that a less experienced Program Manager would give him a pass. But by then, the code had been merged in. Incidentally, you’d be surprised just how many relatively major browser features have snuck their way into releases like this.

From How We Got the Favicon by Jay Hoffmann

I’m happy to see the platform throw a little love at favicons. They’ve long been one of my favorite little design details, and I’m excited that they’re becoming more reactive to user’s needs. If you have a moment, why not sneak a SVG favicon into your project the same way Bharat Shyam did way back in 1999. 


¹ I haven’t been able to determine if Safari is going to implement SVG favicon support, but I hope they do. Has anyone heard anything?


SVG, Favicons, and All the Fun Things We Can Do With Them originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/svg-favicons-and-all-the-fun-things-we-can-do-with-them/feed/ 9 306644
Add Background Colors to SVGs Using the “rect” Element https://css-tricks.com/add-background-colors-to-svgs-using-the-rect-element/ https://css-tricks.com/add-background-colors-to-svgs-using-the-rect-element/#comments Thu, 20 Feb 2020 14:43:48 +0000 https://css-tricks.com/?p=303339 The advantages of using SVGs in web development are well known. SVGs are small in size, can be made quite accessible, are scalable while maintaining their quality, and can be animated. Still, there is a learning curve. Things, like …


Add Background Colors to SVGs Using the “rect” Element originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
The advantages of using SVGs in web development are well known. SVGs are small in size, can be made quite accessible, are scalable while maintaining their quality, and can be animated. Still, there is a learning curve. Things, like the syntax of SVG, can be a little tricky and having to hand-alter SVG code sometimes isn’t out of the question.

Most SVG assets allow styling to be applied in predictable ways. For instance, this circle has a hover state that functions much like any other element in the DOM.

However, a problem I’ve encountered on several front-end projects is being provided a sub-optimal SVG asset by a client, designer, or brand resources site. There isn’t anything “wrong” with these files, but the SVG code requires manual revision to achieve necessary functionality. Instead of requesting new files, it is often easier to tweak them myself. 

Styling SVGs is complicated by the fact that, as XML-based files, they act like HTML in some respects, but not in others. Let’s work with an example provided by Instagram themselves (which is also easily findable on Wikipedia). Because the spaces in between paths act as a sort of transparency this image displays whatever background has been applied behind it.

Why isn’t there a background color on the SVG so we can apply a color change on hover (e.g. svg:hover { background: #888; })? It’s because the paths fill the reverse of the space you would think they would. The negative space renders whatever sits behind this element (<body> in the CodePen examples below). Often this is not a problem and may even be desirable for large background designs to ensure organic transitions between content areas. However, because I am using this SVG as a link, I will need to alter the file so that I can style the space behind it. 

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
  <title>Instagram</title>
  <path d="..." transform="translate(0 0)" fill="#fff"/>
  <path d="..." transform="translate(0 0)" fill="#fff"/>
  <path d="..." transform="translate(0 0)" fill="#fff"/>
</svg>

The Instagram logo is a perfect example of an awkward SVG file that requires more CSS finesse than most. Again, there is nothing wrong with this SVG, but it presents some challenges for styling. In order to add a hover state that alters the background, we will need to change the code above.

There are several ways to go about this, but the easiest fix is to add another element behind the image. Because the Instagram icon is rectangular, we can add a <rect> element behind the three foreground paths that comprise this SVG. If the logo was circular or oval, I would have used the <circle> or <ellipse> element. Be sure to set a height and width to match the viewBox when adding this type of element, and use the rx value to round the corners as needed. Rather than adding a class or fill to every path in the SVG element, we can target the <rect> and <path> elements in the CSS file. 

The advantage of this approach is its simplicity. Instead of having to alter multiple files or use JavaScript or third-party JavaScript libraries, we can add one line of code to the SVG code block and style it. 

If, for some reason, you need or just prefer to leave the SVG file alone, you can revise the CSS to achieve similar functionality. 

We could add a background property on the social-link class but, for this tutorial, I will instead use the slightly more complicated, but equally effective, strategy of revising an SVG by applying a pseudo-element to it. In the example below, I have used the ::before pseudo-class to add a shape and the opacity property to make it visible on hover. To avoid having this shape leave a border around the icon, I have made it slightly smaller than the SVG using the height and width properties (calc(100% - 2px)). Then I center the pseudo-element behind the SVG and match the transition property for both element and pseudo-element.

/* Sets the link's dimensions */
.social-link {
  display: block;
  height: 24px;
  position: relative;
  width: 24px;
}

/* Targets the pseudo-element to create a new layer */
.social-link::before {
  background: #fff;
  border-radius: 2px;
  content: "";
  display: block;
  height: calc(100% - 2px);
  opacity: 0;
  position: absolute;
  transition: all 0.2s ease-in-out;
  width: calc(100% - 2px);
}

/* Changes the background color of the pseudo-element on hover and focus */
.social-link::before:hover, .social-link::before:focus {
  background: #000;
}

/* Makes sure the actual SVG element is layered on top of the pseudo-element */
.social-link svg {
  position: relative;
  z-index: 1;
}

/* Makes the background-color transition smooth */
.social-link svg path {
  transition: all 0.2s ease-in-out;
}

/* SVG paths are initially white */
.social-link path {
  fill: #fff;
}

/* The pseudo-elememt comes into full view on hover and focus */
.social-link:hover::before, .social-link:focus::before {
  opacity: 1;
}

/* Fills the SVG paths to black on hover and focus  */
.social-link:hover svg path, .social-link:focus svg path {
  fill: #000;
}

I recommend the above strategies for a quick fix because using vanilla JavaScript or a JavaScript library like vivus.js or raphaeljs is overkill for adding a hover state to an SVG in most cases. However, there are times when modifying an SVG using JavaScript is preferable. Because JavaScript is undoubtedly the most flexible method to change styles, let’s examine what this might look like.

My example separates the JavaScript file, but if you want to add JavaScript inside the SVG element itself, you will need to add a <script> element, just like an HTML file. Be sure to add a CDATA marker to your <script> element to ensure it is parsed as XML.

I’m using jQuery to simplify things a bit and keep CSS as minimal as possible, although for clarity sake, I have added a background property on the social-link class in the CSS file rather than adding it via JavaScript. Notice that this solution targets svg path when altering the CSS method rather than assigning a class to these paths because, in this case, each path should be treated the same.

There are many many ways to style SVGs, but the examples collected in this article are useful and extensible. Strategies for altering SVG files need to be evaluated by an app’s full functionality, but I suspect most front-end developers will find that the <rect> element offers the simplest and most readable solution.

Acknowledgements

Many thanks to Joe Essey and my front-end pals at Nebo Agency Allison Lewis and Nile Livingston (check out Nile’s article, “SVGs for the Web”). 


Add Background Colors to SVGs Using the “rect” Element originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/add-background-colors-to-svgs-using-the-rect-element/feed/ 2 303339
Creating a Maintainable Icon System with Sass https://css-tricks.com/creating-a-maintainable-icon-system-with-sass/ https://css-tricks.com/creating-a-maintainable-icon-system-with-sass/#comments Wed, 28 Aug 2019 14:05:56 +0000 https://css-tricks.com/?p=294949 One of my favorite ways of adding icons to a site is by including them as data URL background images to pseudo-elements (e.g. ::after) in my CSS. This technique offers several advantages:

  • They don’t require any additional HTTP requests


Creating a Maintainable Icon System with Sass originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
One of my favorite ways of adding icons to a site is by including them as data URL background images to pseudo-elements (e.g. ::after) in my CSS. This technique offers several advantages:

  • They don’t require any additional HTTP requests other than the CSS file.
  • Using the background-size property, you can set your pseudo-element to any size you need without worrying that they will overflow the boundaries (or get chopped off).
  • They are ignored by screen readers (at least in my tests using VoiceOver on the Mac) so which is good for decorative-only icons.

But there are some drawbacks to this technique as well:

  • When used as a background-image data URL, you lose the ability to change the SVG’s colors using the “fill” or “stroke” CSS properties (same as if you used the filename reference, e.g. url( 'some-icon-file.svg' )). We can use filter() as an alternative, but that might not always be a feasible solution.
  • SVG markup can look big and ugly when used as data URLs, making them difficult to maintain when you need to use the icons in multiple locations and/or have to change them.

We’re going to address both of these drawbacks in this article.

The situation

Let’s build a site that uses a robust iconography system, and let’s say that it has several different button icons which all indicate different actions:

  • A “download” icon for downloadable content
  • An “external link” icon for buttons that take us to another website
  • A “right caret” icon for taking us to the next step in a process

Right off the bat, that gives us three icons. And while that may not seem like much, already I’m getting nervous about how maintainable this is going to be when we scale it out to more icons like social media networks and the like. For the sake of this article, we’re going to stop at these three, but you can imagine how in a sophisticated icon system this could get very unwieldy, very quickly.

It’s time to go to the code. First, we’ll set up a basic button, and then by using a BEM naming convention, we’ll assign the proper icon to its corresponding button. (At this point, it’s fair to warn you that we’ll be writing everything out in Sass, or more specifically, SCSS. And for the sake of argument, assume I’m running Autoprefixer to deal with things like the appearance property.)

.button {
  appearance: none;
  background: #d95a2b;
  border: 0;
  border-radius: 100em;
  color: #fff;
  cursor: pointer;
  display: inline-block;
  font-size: 18px;
  font-weight: 700;
  line-height: 1;
  padding: 1em calc( 1.5em + 32px ) 0.9em 1.5em;
  position: relative;
  text-align: center;
  text-transform: uppercase;
  transition: background-color 200ms ease-in-out;

  &amp;:hover,
  &amp;:focus,
  &amp;:active {
    background: #8c3c2a;
  }
}

This gives us a simple, attractive, orange button that turns to a darker orange on the hover, focused, and active states. It even gives us a little room for the icons we want to add, so let’s add them in now using pseudo-elements:

.button {

  /* everything from before, plus... */

  &amp;::after {
    background: center / 24px 24px no-repeat; // Shorthand for: background-position, background-size, background-repeat
    border-radius: 100em;
    bottom: 0;
    content: '';
    position: absolute;
    right: 0;
    top: 0;
    width: 48px;
  }

  &amp;--download {

    &amp;::after {
      background-image: url( 'data:image/svg+xml;utf-8,' );
      }
    }

  &amp;--external {

    &amp;::after {
      background-image: url( 'data:image/svg+xml;utf-8,' );
    }
  }

  &amp;--caret-right {

    &amp;::after {
      background-image: url( 'data:image/svg+xml;utf-8,' );
    }
  }
}

Let’s pause here. While we’re keeping our SCSS as tidy as possible by declaring the properties common to all buttons, and then only specifying the background SVGs on a per-class basis, it’s already starting to look a bit unwieldy. That’s that second downside to SVGs I mentioned before: having to use big, ugly markup in our CSS code.

Also, note how we’re defining our fill and stroke colors inside the SVGs. At some point, browsers decided that the octothorpe (“#”) that we all know and love in our hex colors was a security risk, and declared that they would no longer support data URLs that contained them. This leaves us with three options:

  1. Convert our data URLs from markup (like we have here) to base-64 encoded strings, but that makes them even less maintainable than before by completely obfuscating them; or
  2. Use rgba() or hsla() notation, not always intuitive as many developers have been using hex for years; or
  3. Convert our octothorpes to their URL-encoded equivalents, “%23”.

We’re going to go with option number three, and work around that browser limitation. (I will mention here, however, that this technique will work with rgb(), hsla(), or any other valid color format, even CSS named colors. But please don’t use CSS named colors in production code.)

Moving to maps

At this point, we only have three buttons fully declared. But I don’t like them just dumped in the code like this. If we needed to use those same icons elsewhere, we’d have to copy and paste the SVG markup, or else we could assign them to variables (either Sass or CSS custom properties), and reuse them that way. But I’m going to go for what’s behind door number three, and switch to using one of Sass’ greatest features: maps.

If you’re not familiar with Sass maps, they are, in essence, the Sass version of an associative array. Instead of a numerically-indexed array of items, we can assign a name (a key, if you will) so that we can retrieve them by something logical and easily remembered. So let’s build a Sass map of our three icons:

$icons: (
  'download':    '',
  'external':    '',
  'caret-right': '',
);

There are two things to note here: We didn’t include the data:image/svg+xml;utf-8, string in any of those icons, only the SVG markup itself. That string is going to be the same every single time we need to use these icons, so why repeat ourselves and run the risk of making a mistake? Let’s instead define it as its own string and prepend it to the icon markup when needed:

$data-svg-prefix: 'data:image/svg+xml;utf-8,';

The other thing to note is that we aren’t actually making our SVG any prettier; there’s no way to do that. What we are doing is pulling all that ugliness out of the code we’re working on a day-to-day basis so we don’t have to look at all that visual clutter as much. Heck, we could even put it in its own partial that we only have to touch when we need to add more icons. Out of sight, out of mind!

So now, let’s use our map. Going back to our button code, we can now replace those icon literals with pulling them from the icon map instead:

&amp;--download {

  &amp;::after {
    background-image: url( $data-svg-prefix + map-get( $icons, 'download' ) );
  }
}

&amp;--external {

  &amp;::after {
    background-image: url( $data-svg-prefix + map-get( $icons, 'external' ) );
  }
}

&amp;--next {

  &amp;::after {
    background-image: url( $data-svg-prefix + map-get( $icons, 'caret-right' ) );
  }
}

Already, that’s looking much better. We’ve started abstracting out our icons in a way that keeps our code readable and maintainable. And if that were the only challenge, we’d be done. But in the real-world project that inspired this article, we had another wrinkle: different colors.

Our buttons are a solid color which turn to a darker version of that color on their hover state. But what if we want “ghost” buttons instead, that turn into solid colors on hover? In this case, white icons would be invisible for buttons that appear on white backgrounds (and probably look wrong on non-white backgrounds). What we’re going to need are two variations of each icon: the white one for the hover state, and one that matches button’s border and text color for the non-hover state.

Let’s update our button’s base CSS to turn it in from a solid button to a ghost button that turns solid on hover. And we’ll need to adjust the pseudo-elements for our icons, too, so we can swap them out on hover as well.

.button {
  appearance: none;
  background: none;
  border: 3px solid #d95a2b;
  border-radius: 100em;
  color: #d95a2b;
  cursor: pointer;
  display: inline-block;
  font-size: 18px;
  font-weight: bold;
  line-height: 1;
  padding: 1em calc( 1.5em + 32px ) 0.9em 1.5em;
  position: relative;
  text-align: center;
  text-transform: uppercase;
  transition: 200ms ease-in-out;
  transition-property: background-color, color;

  &amp;:hover,
  &amp;:focus,
  &amp;:active {
    background: #d95a2b;
    color: #fff;
  }
}

Now we need to create our different-colored icons. One possible solution is to add the color variations directly to our map… somehow. We can either add new different-colored icons as additional items in our one-dimensional map, or make our map two-dimensional.

One-Dimensional Map:

$icons: (
  'download-white':  '',
  'download-orange': '',
);

Two-Dimensional Map:

$icons: (
  'download': (
    'white':  '',
    'orange': '',
  ),
);

Either way, this is problematic. By just adding one additional color, we’re going to double our maintenance efforts. Need to change the existing download icon with a different one? We need to manually create each color variation to add it to the map. Need a third color? Now you’ve just tripled your maintenance costs. I’m not even going to get into the code to retrieve values from a multi-dimensional Sass map because that’s not going to serve our ultimate goal here. Instead, we’re just going to move on.

Enter string replacement

Aside from maps, the utility of Sass in this article comes from how we can use it to make CSS behave more like a programming language. Sass has built-in functions (like map-get(), which we’ve already seen), and it allows us to write our own.

Sass also has a bunch of string functions built-in, but inexplicably, a string replacement function isn’t one of them. That’s too bad, as its usefulness is obvious. But all is not lost.

Kitty Giradel gave us a Sass version of str-replace() here on CSS-Tricks in 2014. We can use that here to create one version of our icons in our Sass map, using a placeholder for our color values. Let’s add that function to our own code:

@function str-replace( $string, $search, $replace: '' ) {

  $index: str-index( $string, $search );

  @if $index {
    @return str-slice( $string, 1, $index - 1 ) + $replace + str-replace( str-slice( $string, $index + str-length( $search ) ), $search, $replace);
  }

  @return $string;
}

Next, we’ll update our original Sass icon map (the one with only the white versions of our icons) to replace the white with a placeholder (%%COLOR%%) that we can swap out with whatever color we call for, on demand.

$icons: (
  'download':    '',
  'external':    '',
  'caret-right': '',
);

But if we were going to try and fetch these icons using just our new str-replace() function and Sass’ built-in map-get() function, we’d end with something big and ugly. I’d rather tie these two together with one more function that makes calling the icon we want in the color we want as simple as one function with two parameters (and because I’m particularly lazy, we’ll even make the color default to white, so we can omit that parameter if that’s the color icon we want).

Because we’re getting an icon, it’s a “getter” function, and so we’ll call it get-icon():

@function get-icon( $icon, $color: #fff ) {

  $icon:        map-get( $icons, $icon );
  $placeholder: '%%COLOR%%';

  $data-uri: str-replace( url( $data-svg-prefix + $icon ), $placeholder, $color );

  @return str-replace( $data-uri, '#', '%23' );
}

Remember where we said that browsers won’t render data URLs that have octothorpes in them? Yeah, we’re str-replace()ing that too so we don’t have to remember to pass along “%23” in our color hex codes.

Side note: I have a Sass function for abstracting colors too, but since that’s outside the scope of this article, I’ll refer you to my get-color() gist to peruse at your leisure.

The result

Now that we have our get-icon() function, let’s put it to use. Going back to our button code, we can replace our map-get() function with our new icon getter:

&amp;--download {

  &amp;::before {
    background-image: get-icon( 'download', #d95a2b );
  }

  &amp;::after {
    background-image: get-icon( 'download', #fff ); // The ", #fff" isn't strictly necessary, because white is already our default
  }
}

&amp;--external {

  &amp;::before {
    background-image: get-icon( 'external', #d95a2b );
  }

  &amp;::after {
    background-image: get-icon( 'external' );
  }
}

&amp;--next {

  &amp;::before {
    background-image: get-icon( 'arrow-right', #d95a2b );
  }

  &amp;::after {
    background-image: get-icon( 'arrow-right' );
  }
}

So much easier, isn’t it? Now we can call any icon we’ve defined, with any color we need. All with simple, clean, logical code.

  • We only ever have to declare an SVG in one place.
  • We have a function that gets that icon in whatever color we give it.
  • Everything is abstracted out to a logical function that does exactly what it looks like it will do: get X icon in Y color.

Making it fool-proof

The one thing we’re lacking is error-checking. I’m a huge believer in failing silently… or at the very least, failing in a way that is invisible to the user yet clearly tells the developer what is wrong and how to fix it. (For that reason, I should be using unit tests way more than I do, but that’s a topic for another day.)

One way we have already reduced our function’s propensity for errors is by setting a default color (in this case, white). So if the developer using get-icon() forgets to add a color, no worries; the icon will be white, and if that’s not what the developer wanted, it’s obvious and easily fixed.

But wait, what if that second parameter isn’t a color? As if, the developer entered a color incorrectly, so that it was no longer being recognized as a color by the Sass processor?

Fortunately we can check for what type of value the $color variable is:

@function get-icon( $icon, $color: #fff ) {

  @if 'color' != type-of( $color ) {

    @warn 'The requested color - "' + $color + '" - was not recognized as a Sass color value.';
    @return null;
  }

  $icon:        map-get( $icons, $icon );
  $placeholder: '%%COLOR%%';
  $data-uri:    str-replace( url( $data-svg-prefix + $icon ), $placeholder, $color );

  @return str-replace( $data-uri, '#', '%23' );
}

Now if we tried to enter a nonsensical color value:

&amp;--download {

  &amp;::before {
    background-image: get-icon( 'download', ce-nest-pas-un-couleur );
  }
}

…we get output explaining our error:

Line 25 CSS: The requested color - "ce-nest-pas-un-couleur" - was not recognized as a Sass color value.

…and the processing stops.

But what if the developer doesn’t declare the icon? Or, more likely, declares an icon that doesn’t exist in the Sass map? Serving a default icon doesn’t really make sense in this scenario, which is why the icon is a mandatory parameter in the first place. But just to make sure we are calling an icon, and it is valid, we’re going to add another check:

@function get-icon( $icon, $color: #fff ) {

  @if 'color' != type-of( $color ) {

    @warn 'The requested color - "' + $color + '" - was not recognized as a Sass color value.';
    @return null;
  }

  @if map-has-key( $icons, $icon ) {

    $icon:        map-get( $icons, $icon );
    $placeholder: '%%COLOR%%';
    $data-uri:    str-replace( url( $data-svg-prefix + $icon ), $placeholder, $color );

    @return str-replace( $data-uri, '#', '%23' );
  }

  @warn 'The requested icon - "' + $icon + '" - is not defined in the $icons map.';
  @return null;
}

We’ve wrapped the meat of the function inside an @if statement that checks if the map has the key provided. If so (which is the situation we’re hoping for), the processed data URL is returned. The function stops right then and there — at the @return — which is why we don’t need an @else statement.

But if our icon isn’t found, then null is returned, along with a @warning in the console output identifying the problem request, plus the partial filename and line number. Now we know exactly what’s wrong, and when and what needs fixing.

So if we were to accidentally enter:

&amp;--download {

  &amp;::before {
    background-image: get-icon( 'ce-nest-pas-une-icône', #d95a2b );
  }
}

…we would see the output in our console, where our Sass process was watching and running:

Line 32 CSS: The requested icon - "ce-nest-pas-une-icône" - is not defined in the $icons map.

As for the button itself, the area where the icon would be will be blank. Not as good as having our desired icon there, but soooo much better than a broken image graphic or some such.

Conclusion

After all of that, let’s take a look at our final, processed CSS:

.button {
  -webkit-appearance: none;
      -moz-appearance: none;
          appearance: none;
  background: none;
  border: 3px solid #d95a2b;
  border-radius: 100em;
  color: #d95a2b;
  cursor: pointer;
  display: inline-block;
  font-size: 18px;
  font-weight: 700;
  line-height: 1;
  padding: 1em calc( 1.5em + 32px ) 0.9em 1.5em;
  position: relative;
  text-align: center;
  text-transform: uppercase;
  transition: 200ms ease-in-out;
  transition-property: background-color, color;
}
.button:hover, .button:active, .button:focus {
  background: #d95a2b;
  color: #fff;
}
.button::before, .button::after {
  background: center / 24px 24px no-repeat;
  border-radius: 100em;
  bottom: 0;
  content: '';
  position: absolute;
  right: 0;
  top: 0;
  width: 48px;
}
.button::after {
  opacity: 0;
  transition: opacity 200ms ease-in-out;
}
.button:hover::after, .button:focus::after, .button:active::after {
  opacity: 1;
}
.button--download::before {
  background-image: url('data:image/svg+xml;utf-8,');
}
.button--download::after {
  background-image: url('data:image/svg+xml;utf-8,');
}
.button--external::before {
  background-image: url('data:image/svg+xml;utf-8,');
}
.button--external::after {
  background-image: url('data:image/svg+xml;utf-8,');
}
.button--next::before {
  background-image: url('data:image/svg+xml;utf-8,');
}
.button--next::after {
  background-image: url('data:image/svg+xml;utf-8,');
}

Yikes, still ugly, but it’s ugliness that becomes the browser’s problem, not ours.

I’ve put all this together in CodePen for you to fork and experiment. The long goal for this mini-project is to create a PostCSS plugin to do all of this. This would increase the availability of this technique to everyone regardless of whether they were using a CSS preprocessor or not, or which preprocessor they’re using.

“If I have seen further it is by standing on the shoulders of Giants.”
– Isaac Newton, 1675

Of course we can’t talk about Sass and string replacement and (especially) SVGs without gratefully acknowledging the contributions of the others who’ve inspired this technique.


Creating a Maintainable Icon System with Sass originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/creating-a-maintainable-icon-system-with-sass/feed/ 10 294949
Change Color of SVG on Hover https://css-tricks.com/change-color-of-svg-on-hover/ https://css-tricks.com/change-color-of-svg-on-hover/#comments Mon, 13 May 2019 14:44:21 +0000 http://css-tricks.com/?p=287092 There are a lot of different ways to use SVG. Depending on which way, the tactic for recoloring that SVG in different states or conditions — :hover, :active, :focus, class name change, etc. — is different. …


Change Color of SVG on Hover originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
There are a lot of different ways to use SVG. Depending on which way, the tactic for recoloring that SVG in different states or conditions — :hover, :active, :focus, class name change, etc. — is different.

Let’s look at the ways.

Inline SVG

Inline SVG is my favorite way to use SVG anyway, in part because of how easy it is to access and style the SVG.

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

If you’re used to working with icon fonts, one thing you might enjoy about them is how easy it is to change the color. You’re largely limited to a single color with icon fonts in a way that SVG isn’t, but still, it is appealingly easy to change that single color with color. Using inline SVG allows you to set the fill, which cascades to all the elements within the SVG, or you can fill each element separately if needed.

SVG Symbol / Use

There is such thing as an SVG sprite, which is a group of SVGs turned into <symbol> elements such that any given icon can be referenced easily with a <use> element.

See the Pen
Use SVG Hovers
by Chris Coyier (@chriscoyier)
on CodePen.

You can still set the fill color from outside CSS rather easily this way, but there are caveats.

  • The internal SVG elements (like the <path>) can have no fill themselves. This allows the fill set from the parent SVG to cascade into the Shadow DOM created by <use>. As soon as you have something like <path fill="blue" ... /> in the <symbol>, you’ve lost outside CSS control.
  • Likewise, the fill of individual elements cannot be controlled within the SVG like you could with inline SVG. This means you’re pretty firmly in single-color territory. That covers most use cases anyway, but still, a limitation nonetheless.

SVG background images

SVG can be set as a background image just like PNG, JPG, or whatever other graphics format. At this point, you’ve sort of given up on being able to change the fill. One possibility, which I’d argue isn’t a particularly good one, is to have two versions of every icon, in the respective colors, and swap between them:

See the Pen
Background SVG Hovers
by Chris Coyier (@chriscoyier)
on CodePen.

I don’t blame you if you’d rather not swap sources, so another possibility is to get gnarly with filters.

See the Pen
Background SVG Hovers with Filters
by Chris Coyier (@chriscoyier)
on CodePen.

Trying to finagle the right filters to get the color right is tricky stuff. Fortunately, Barrett Sonntag made a tool to calculate the filters for you! Turning black to red ends up a whacky combination like this: invert(27%) sepia(51%) saturate(2878%) hue-rotate(346deg) brightness(104%) contrast(97%);.

SVG also has object, which is kinda neat in that it had a built-in fallback back in the day — although browser support is so good these days, I honestly have never used it. But if you’re using it, you would probably have to use this filter technique to swap color on hover.

See the Pen
Background SVG Object Hovers
by Chris Coyier (@chriscoyier)
on CodePen.

Use a mask instead of a background image

This way, the SVG is still in charge of essentially drawing the shape, but the color comes from the background-color (or image! or gradient!) behind it rather than the SVG itself.

See the Pen
Background SVG Hovers with Mask
by Chris Coyier (@chriscoyier)
on CodePen.

SVG background images as data URLs

This doesn’t change that much from above, but it does open up one interesting possibility: Using a variable for the internal fills. Here that is with Sass keeping the URLs as variables:

See the Pen
Background SVG Hovers with Data URL variables
by Chris Coyier (@chriscoyier)
on CodePen.

You can also do this with native CSS custom properties!


Change Color of SVG on Hover originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/change-color-of-svg-on-hover/feed/ 12 287092
Inline SVG… Cached https://css-tricks.com/inline-svg-cached/ https://css-tricks.com/inline-svg-cached/#comments Fri, 12 Apr 2019 14:28:38 +0000 http://css-tricks.com/?p=285846 I wrote that using inline <svg icons make for the best icon system. I still think that’s true. It’s the easiest possible way to drop an icon onto a page. No network request, perfectly styleable.

But inlining code has …


Inline SVG… Cached originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
I wrote that using inline <svg> icons make for the best icon system. I still think that’s true. It’s the easiest possible way to drop an icon onto a page. No network request, perfectly styleable.

But inlining code has some drawbacks, one of which is that it doesn’t take advantage of caching. You’re making the browser read and process the same code over and over as you browse around. Not that big of a deal. There are much bigger performance fish to fry, right? But it’s still fun to think about more efficient patterns.

Scott Jehl wrote that just because you inline something doesn’t mean you can’t cache it. Let’s see if Scott’s idea can extend to SVG icons.

Starting with inline SVG

Like this…

<!DOCTYPE html>
<html lang="en">

<head>
  <title>Inline SVG</title>
  <link rel="stylesheet" href="/styles/style.css">
</head>

<body>

  ...
 
  <svg width="24" height="24" viewBox="0 0 24 24" class="icon icon-alarm" xmlns="http://www.w3.org/2000/svg">
    <path id="icon-alarm" d="M11.5,22C11.64,22 11.77,22 11.9,21.96C12.55,21.82 13.09,21.38 13.34,20.78C13.44,20.54 13.5,20.27 13.5,20H9.5A2,2 0 0,0 11.5,22M18,10.5C18,7.43 15.86,4.86 13,4.18V3.5A1.5,1.5 0 0,0 11.5,2A1.5,1.5 0 0,0 10,3.5V4.18C7.13,4.86 5,7.43 5,10.5V16L3,18V19H20V18L18,16M19.97,10H21.97C21.82,6.79 20.24,3.97 17.85,2.15L16.42,3.58C18.46,5 19.82,7.35 19.97,10M6.58,3.58L5.15,2.15C2.76,3.97 1.18,6.79 1,10H3C3.18,7.35 4.54,5 6.58,3.58Z"></path>
  </svg>

It’s weirdly easy to toss text into browser cache as a file

In the above HTML, the selector .icon-alarm will fetch us the entire chunk of SVG for that icon.

const iconHTML = document.querySelector(".icon-alarm").outerHTML;

Then we can plunk it into the browser’s cache like this:

if ("caches" in window) {
  caches.open('static').then(function(cache) {
    cache.put("/icons/icon-wheelchair.svg", new Response(
      iconHTML,
      { headers: {'Content-Type': 'image/svg+xml'} }
    ));
  }
}

See the file path /icons/icon-wheelchair.svg? That’s kinda just made up. But it really will be put in the cache at that location.

Let’s make sure the browser grabs that file out of the cache when it’s requested

We’ll register a Service Worker on our pages:

if (navigator.serviceWorker) {   
  navigator.serviceWorker.register('/sw.js', {
    scope: '/'
  });
}

The service worker itself will be quite small, just a cache matcher:

self.addEventListener("fetch", event => {
  let request = event.request;

  event.respondWith(
    caches.match(request).then(response => {
      return response || fetch(request);
    })
  );
});

But… we never request that file, because our icons are inline.

True. But what if other pages benefitted from that cache? For example, an SVG icon could be placed on the page like this:

<svg class="icon">
  <use xlink:href="/icons/icon-alarm.svg#icon-alarm" /> 
</svg>

Since /icons/icon-alarm.svg is sitting there ready in cache, the browser will happily pluck it out of cache and display it.

(I was kind of amazed this works. Edge doesn’t like <use> elements that link to files, but that’ll be over soon enough. Update, it’s over, Edge went Chromium.)

And even if the file isn’t in the cache, assuming we actually chuck this file on the file system likely the result of some kind of “include” (I used Nunjucks on the demo).

But… <use> and inline SVG aren’t quite the same

True. What I like about the above is that it’s making use of the cache and the icons should render close to immediately. And there are some things you can style this way — for example, setting the fill on the parent icon should go through the shadow DOM that the <use> creates and colorize the SVG elements within.

Still, it’s not the same. The shadow DOM is a big barrier compared to inline SVG.

So, enhance them! We could asynchronously load a script that finds each SVG icon, Ajaxs for the SVG it needs, and replaces the <use> stuff…

const icons = document.querySelectorAll("svg.icon");

icons.forEach(icon => {
  const url = icon.querySelector("use").getAttribute("xlink:href"); // Might wanna look for href also
  fetch(url)
    .then(response => response.text())
    .then(data => {
      // This is probably a bit layout-thrashy. Someone smarter than me could probably fix that up.

      // Replace the <svg><use></svg> with inline SVG
      const newEl = document.createElement("span");
      newEl.innerHTML = data;
      icon.parentNode.replaceChild(newEl, icon);

      // Remove the <span>s
      const parent = newEl.parentNode;
      while (newEl.firstChild) parent.insertBefore(newEl.firstChild, newEl);
      parent.removeChild(newEl);
    });
});

Now, assuming this JavaScript executes correctly, this page has inline SVG available just like the original page did.

Demo & Repo


Inline SVG… Cached originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/inline-svg-cached/feed/ 12 285846
Making SVG icon libraries for React apps https://css-tricks.com/making-svg-icon-libraries-for-react-apps/ Fri, 14 Dec 2018 15:20:56 +0000 http://css-tricks.com/?p=266365 Nicolas Gallagher:

At Twitter I used the approach described here to publish the company’s SVG icon library in several different formats: optimized SVGs, plain JavaScript modules, React DOM components, and React Native components.

There is no One True Way©


Making SVG icon libraries for React apps originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Nicolas Gallagher:

At Twitter I used the approach described here to publish the company’s SVG icon library in several different formats: optimized SVGs, plain JavaScript modules, React DOM components, and React Native components.

There is no One True Way© to make an SVG icon system. The only thing that SVG icon systems have in common is that, somehow, some way, SVG is used to show that icon. I gotta find some time to write up a post that goes into all the possibilities there.

One thing different systems tend to share is some kind of build process to turn a folder full of SVG files into a more programmatically digestible format. For example, gulp-svg-sprite takes your folder of SVGs and creates a SVG sprite (chunk of <symbol>s) to use in that type of SVG icon system. Grunticon processes your folder of SVGs into a CSS file, and is capable of enhancing them into inline SVG. Gallagher’s script creates React components out of them, and like he said, that’s great for delivery to different targets as well as performance optimization, like code splitting.

This speaks to the versatility of SVG. It’s just markup, so it’s easy to work with.

To Shared LinkPermalink on CSS-Tricks


Making SVG icon libraries for React apps originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
266365
Accessible SVG Icons With Inline Sprites https://css-tricks.com/accessible-svg-icons-with-inline-sprites/ Fri, 07 Dec 2018 15:20:18 +0000 http://css-tricks.com/?p=279882 This is a great look at accessible SVG markup patterns by Marco Hengstenberg. Here’s the ideal example:

<button type="button">
  Menu
  <svg class="svg-icon"
     role="img"
     height="10"
     width="10"
     viewBox="0 0 10 10"
     aria-hidden="true"
     focusable="false">
     <path d="m1 7h8v2h-8zm0-3h8v2h-8zm0-3h8v2h-8z"/>
    </svg>
</button>

Notes:

  • It’s not the


Accessible SVG Icons With Inline Sprites originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
This is a great look at accessible SVG markup patterns by Marco Hengstenberg. Here’s the ideal example:

<button type="button">
  Menu
  <svg class="svg-icon"
     role="img"
     height="10"
     width="10"
     viewBox="0 0 10 10"
     aria-hidden="true"
     focusable="false">
     <path d="m1 7h8v2h-8zm0-3h8v2h-8zm0-3h8v2h-8z"/>
    </svg>
</button>

Notes:

  • It’s not the <svg> itself that is interactive — it’s wrapped in a <button> for that.
  • The .svg-icon class has some nice trickery, like em-based sizing to match the size of the text it’s next to, and currentColor to match the color.
  • Since real text is next to it, the icon can be safely ignored via aria-hidden="true". If you need an icon-only button, you can wrap that text in an accessibily-friendly .visually-hidden class.
  • The focusable="false" attribute solves an IE 11 bug.

Plus a handy Pen to reference all the patterns.

To Shared LinkPermalink on CSS-Tricks


Accessible SVG Icons With Inline Sprites originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
279882