There are many ways to create icons for websites. After this summary of web icon history, I will show you The Way.
(But seriously, the way you do icons is about to change).
In the beginning, icons on the web were small individual JPEGs or single JPEG sprites. The HTML image element or CSS background was used to create these images on a web page. And users saw that it was good.
Then came the PNG image format with their glorious transparent backgrounds. These were implemented in the same way.
Then, the birth of the Icon Font. Monochromatic icons could be implemented as a web font and we could finally color and resize icons without fear of distortion and shame.
Hallejula!
But following the Icon Font there was a hasty adoption of SVGs and SVG sprites implemented in the old forbidden ways. At this time, the false doctrine of icons as SVG elements took hold and developers started encapsulating the huge blocks of SVG markup inside React components. This was peak 2018 when half of the web developer congregation was (let's be honest, still is) blindly making everything a React component. These false idols were given to young engineers and soon Icon Font was no longer cool.
From that time until these latter days, we have been living in the Icon Dark Ages. Developers have been over-engineering their icons for millennia (okay, 5 years but that's like 1,000 years in JavaScript time). Extraneous overhead and downloads, superfluous lines of code, and incompatibility have been plaguing web icons worldwide, but be of good cheer. There is hope.
Icon Font has returned!
Alright, enough sacrilege for one day😇
The god mode icon font component
Let's just do the code first, then an explanation.
In an external stylesheet add the following CSS (or inline it for performance, it's really small):
/* Define the icon font */
@font-face {
font-family: 'x-icons';
font-style: normal;
font-weight: 400;
src: url(https://fonts.gstatic.com/s/materialsymbolsoutlined/v134/kJF4BvYX7BgnkSrUwT8OhrdQw4oELdPIeeII9v6oDMzBwG-RpA6RzaxHMPdY40KH8nGzv3fzfVJU22ZZLsYEpzC_1ver5Y0J1Llf.woff2) format('woff2');
}
/* Define a custom icon tag (I'll explain later) */
x-icon::before {
font-family: 'x-icons';
content: attr(name);
-webkit-font-smoothing: antialiased;
}
/* Filled variation */
x-icon[fill]::before {
font-variation-settings: 'FILL' 1;
}
In your page's <head>
, preload that font file to improve performance:
<link href="https://fonts.gstatic.com/s/materialsymbolsoutlined/v134/kJF4BvYX7BgnkSrUwT8OhrdQw4oELdPIeeII9v6oDMzBwG-RpA6RzaxHMPdY40KH8nGzv3fzfVJU22ZZLsYEpzC_1ver5Y0J1Llf.woff2" rel="preload" as="font" crossorigin>
Then in your app/page/template/component - wherever HTML is supported - add some icons:
<x-icon name="person"></x-icon>
<x-icon name="person" fill></x-icon>
You should see these:
What's happening here?
Miraculously, with just a little bit of CSS, we have created a custom HTML icon component with a meaningful API: the x-icon
tag and name
and fill
attributes. This component makes a single request for its variable font file which contains over 3,000 unique icons and only weighs 323kb. Yes, 3,000 icons plus their filled variations!
The icon's name, as defined by the font, is passed through its name
attribute to the pseudo element's content property like this content: attr(name)
, which gets mapped by the font to the actual glyph. This is a big improvement over having to manually map icon names to codepoints in your CSS. It's a big reduction in file size, maintenance, and the chance of errors too.
The fill
boolean attribute toggles an icon's filled variation. Filled versions are packaged in the same file - another big improvement over regular icon fonts, which would require a second request and more bytes overall.
Other attributes could be defined to support more icon features, like optical size and animations. And because these are real HTML elements, features like the title
attribute and ARIA are already built-in and ready to go.
The icon font happens to be Material Symbols, but this is not exclusive to Material. Any variable icon font can be turned into a god mode icon component. Hopefully, FontAwesome will create a variable font version soon (go upvote). Furthermore, if you did change the font file, there are no CSS changes required because god mode icons don't have icon name to codepoint mappings!
Note about custom HTML tags
For a very long time, browsers have supported custom HTML tags. They will parse and render and style them just like native HTML tags. Although the x-icon
tag looks like a Web Component, it's not. A tag prefix is a requirement when following the TAC CSS methodology, which is what I follow when I create UI elements like this one.
Comparing other icon solutions
If you're unfamiliar with how icon components are often implemented, this all may just seem obvious and logical. This matrix compares god mode icons to other solutions. The worst solution also happens to be the status quo, which is the React solution (click that "over-engineering" link up in the intro to get a taste).
Solution | API | Network requests | Payload | Dependencies | Swap icons |
God mode icon | HTML tag+attributes | 1 | depends, ~1000 icons = 100kb | none | no code change |
Traditional font icon | classes with boilerplate HTML | 2+ | depends, always bigger than god mode because of the extra CSS and less efficient font format | none | remap names to codepoints |
SVG icon images | image element | many | smallest until you use a lot of them, then bigger than icon font | none | no code changes |
React icon | React component | 1 | largest | React, possibly Emotion | refactor tons of JavaScript, miss some, break things, give up and use god mode! |
Closing
If you'd like to see a real-world example of icons built this way, my Mdash design system has them here http://m-docs.org/icons. Also, follow that project to get updates.
If you'd like to learn more about creating other UI elements in a similar way, learn the TAC CSS methodology and follow this blog where I post about leveraging the vanilla web stack.
And don't hesitate to leave a question or complaint here!