Utilizing icons in user interface elements is helpful. In addition to element labeling, icons can help reinforce a user element’s intention to users. But I have to say, I notice a bit of icon misalignment while browsing the web. Even if the icon’s alignment is correct, icons often do not respond well when typographic styles for the element change.
I took note of a couple real-world examples and I’d like to share my thoughts on how I improved them. It’s my hope these techniques can help others build user interface elements that better accommodate typographic changes and while upholding the original goals of the design.
Example 1 — Site messaging
I found this messaging example on a popular media website. The icon’s position doesn’t look so bad. But when changing some of the element’s style properties like font-size and line-height, it begins to unravel.
Identified issues
the icon is absolutely positioned from the left edge using a relative unit (rem)
because the icon is taken out of the flow, the parent is given a larger padding-left value to help with overall spacing – ideally, our padding-x is uniform, and everything looks good whether or not an icon is present
the icon (it’s an SVG) is also sized in rems – this doesn’t allow for respective resizing if its parent’s font-size changes
Recommendations
Indicating the issues with aligning the icon and typography.
We want our icon’s top edge to be at the blue dashed line, but we often find our icon’s top edge at the red dashed line.
Have you ever inserted an icon next to some text and it just won’t align to the top of the text? You may move the icon into place with something like position: relative; top: 0.2em. This works well enough, but if typographic styles change in the future, your icon could look misaligned.
We can position our icon more reliably. Let’s use the element’s baseline distance (the distance from one line’s baseline to the next line’s baseline) to help solve this.
With a font-size of 1rem (16px) and line-height of 1.5, our icon will be moved 4 pixels.
baseline distance = 16px * 1.5 = 24px
icon offset = (24px – 16px) / 2 = 4px
Demo: before and after
Example 2 – unordered lists
The second example I found is an unordered list. It uses a web font (Font Awesome) for its icon via a ::before pseudo-element. There have been plenty of great articles on styling both ordered and unordered lists, so I won’t go into details about the relatively new ::marker pseudo-element and such. Web fonts can generally work pretty well with icon alignment depending on the icon used.
Identified issues
no absolute positioning used – when using pseudo-elements, we don’t often use flexbox like our first example and absolute positioning shines here
the list item uses a combination of padding and negative text-indent to help with layout – I am never able to get this to work well when accounting for multi-line text and icon scalability
Recommendations
Because we’ll also use a pseudo-element in our solution, we’ll leverage absolute positioning. This example’s icon size was a bit larger than its adjacent copy (about 2x). Because of this, we will alter how we calculate the icon’s top position. The center of our icon should align vertically with the center of the first line.
So with a font-size of 1rem (16px), a line-height of 1.6, and an icon sized 2x the copy (32px), our icon will get get a top value of -3.2 pixels.
baseline distance = 16px * 1.6 = 25.6px
icon offset = (25.6px – 32px) / 2 = -3.2px
With a larger font-size of 2rem (32px), line-height of 1.2, and 64px icon, our icon will get get a top value of -12.8 pixels.
baseline distance = 32px * 1.2 = 38.4px
icon offset = (38.4px – 64px) / 2 = -12.8px
Demo: before and after
Conclusion
For user interface icons, we have a lot of options and techniques. We have SVGs, web fonts, static images, ::marker, and list-style-type. One could even use background-colors and clip-paths to achieve some interesting icon results. Performing some simple calculations can help align and scale icons in a more graceful manner, resulting in implementations that are a bit more bulletproof.
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.
Shadows are a common design feature that can help elements, like icons, stand out. They could be persistent, or applied in different states (e.g. :hover, :focus, or :active) to indicate interaction to users.
Shadows happen in real life, so they can be used on screens to breathe some life into your elements and add a touch of realism to a design.
Since we’re making lists, there are two primary ways we can apply shadows to an SVG:
Yes, both involve filters! And, yes, both CSS and SVG have their own types of filters. But there is some crossover between these as well. For example, a CSS filter can refer to an SVG <filter>; that is, if we’re working with an inline SVG instead of, say, an SVG used as a background image in CSS.
What you can’t use: the CSS box-shadow property. This is commonly used for shadows, but it follows the rectangular outside edge of elements, not the edges of the SVG elements like we want. Here’s Michelle Barker with a clear explanation:
If you’re using an SVG icon font, though, there is always text-shadow. That will indeed work. But let’s focus on those first two as they’re in line with a majority of use cases.
Shadows with CSS filters
The trick to applying a shadow directly to SVG via CSS filters is the drop-shadow() function :
We can use a CSS filter to call that SVG filter by ID instead of values we saw earlier:
svg { filter: url(#shadow); }
Now that filter is taken from the HTML and referenced in the CSS, which applies it.
Using SVG filter primitives
You might be wondering how we got that SVG <filter> to work. To make a drop shadow with an SVG filter, we make use of a filter primitive. A filter primitive in SVG is an element that takes some sort of image or graphic as an input, then outputs that image or graphic it when it’s called. They sort of work like filters in a graphic editing application, but in code and can only be used inside an SVG <filter> element.
There are lots of different filter primitives in SVG. The one we’re reaching for is <feDropShadow>. I’ll let you guess what to does just by looking at the name.
So, similar to how we had something like this did this with a CSS filter:
…we can accomplish the same with the <feDropShadow> SVG filter primitive. There are three key attributes worth calling out as they help define the appearance of the drop shadow:
dx — This shifts the position of the shadow along the x-axis.
dy — This shifts the position of the shadow along the y-axis.
stdDeviation — This defines the standard deviation for the drop shadow’s blur operation. There are other attributes we can use, such as the flood-color for setting the drop shadow color, and flood-opacity for setting the drop shadow’s opacity.
That example includes three <filter> elements, each with their own <feDropShadow> filter primitives.
Using SVG filters
SVG filters are very powerful. We just looked at <feDropShadow>, which is very useful of course, but there is so much more they can do (including Photoshop-like effects) and the subset of stuff we get just for shadows is extensive. Let’s look at some, like colored shadows and inset shadows.
Let’s take the SVG markup for the Twitter logo as an example :
We’re going to need a <filter> element to do these effects. This needs to be within an <svg> element in the HTML. A <filter> element is never rendered directly in the browser — it is only used as something that can be referenced via the filter attribute in SVG, or the url() function in CSS.
Here is the syntax showing an SVG filter and applying it to a source image :
<svg width="300" height="300" viewBox="0 0 300 300"> <filter id="myfilters"> <!-- All filter effects/primitives go in here --> </filter> <g filter="url(#myfilters)"> <!-- Filter applies to everything in this group --> <path fill="..." d="..." ></path> </g> </svg>
The filter element is meant to hold filter primitives as children. It is a container to a series of filter operations that are combined to create a filter effects.
These filter primitive perform a single fundamental graphical operation (e.g. blurring, moving, filling, combining, or distorting) on one or more inputs. They are like building blocks where each SVG filter can be used to in conjunction with others to create an effect. <feGaussianBlur> is a popular filter primitive used to add a blur effect.
Let’s say we define the following SVG filter with <feGaussianBlur>:
When applied on an element, this filter creates a Gaussian blur that blurs the element on a 1px radius on the x-axis, but no blurring on the y-axis. Here’s the result, with and without the effect:
It is possible to use multiple primitives inside a single filter. This will create interesting effects, however, you need to make the different primitives aware of each other. Bence Szabó has a crazy cool set of patterns he created this way.
When combining multiple filter primitives, the first primitive uses the original graphic (SourceGraphic) as its graphic input. Any subsequent primitive uses the result of the filter effect before it as its input. And so on. But we can get some flexibility on that with using the in, in2 and result attributes on primitive elements. Steven Bradley has an excellent write-up on filter primitives that dates back to 2016, but still hold true today.
There are 17 primitives we can use today:
<feGaussianBlur>
<feDropShadow>
<feMorphology>
<feDisplacementMap>
<feBlend>
<feColorMatrix>
<feConvolveMatrix>
<feComponentTransfer>
<feSpecularLighting>
<feDiffuseLighting>
<feFlood>
<feTurbulence>
<feImage>
<feTile>
<feOffset>
<feComposite>
<feMerge>
Notice the fe prefix on all of them. That stands for filter effect. Understanding SVG filters is challenging. An effect like an inset shadow requires a verbose syntax that is difficult to grasp without a thorough understanding of math and color theory. (Rob O’Leary’s “Getting Deep Into Shadows” is a good place to start.)
Rather than running down the rabbit hole of all that, we’re going to work with some pre-made filters. Fortunately, there are a lot of ready-to-use SVG filters around.
Inset shadows
To use filter effect on the Twitter logo, we need to declare it in our “SVG source document” with a unique ID for referencing in our <filter> tag.
<filter id='inset-shadow'> <!-- Shadow offset --> <feOffset dx='0' dy='0' /> <!-- Shadow blur --> <feGaussianBlur stdDeviation='1' result='offset-blur' /> <!-- Invert drop shadow to make an inset shadow --> <feComposite operator='out' in='SourceGraphic' in2='offset-blur' result='inverse' /> <!-- Cut color inside shadow --> <feFlood flood-color='black' flood-opacity='.95' result='color' /> <feComposite operator='in' in='color' in2='inverse' result='shadow' /> <!-- Placing shadow over element --> <feComposite operator='over' in='shadow' in2='SourceGraphic' /> </filter>
There are four different primitives in there and each one performs a different function. But, taken together, they achieving an inset shadow.
Now that we’ve created this inset shadow filter, we can apply it to our SVG. We’ve already seen how to apply it via CSS. Something like:
.filtered { filter: url(#myfilters); } /* Or apply only in certain states, like: */ svg:hover, svg:focus { filter: url(#myfilters); }
We can also apply an SVG <filter> directly within the SVG syntax with the filter attribute. That’s like:
<svg> <!-- Apply a single filter --> <path d="..." filter="url(#myfilters)" /> <!-- Or apply to a whole group of elements --> <g filter="url(#myfilters)"> <path d="..." /> <path d="..." /> </g> </svg>
More examples
Here are some more shadow examples from Oleg Solomka:
Note that the basic shadows here are probably a bit more complicated than they need to be. For example, a colored shadow can still be done with <feDropShadow> like:
On the first line there, that’s saying: this SVG shouldn’t render at all — it’s just stuff that we intend to use later. The <defs> tag says something similar: we’re just defining these things to use later. That way, we don’t have to repeat ourselves by writing things out over and again. We’ll reference the filter by ID, and the symbols as well, perhaps like:
<svg> <use xlink:href="#my-icon" /> </svg>
SVG filters have wide support (even in Internet Explorer and Edge!) with very fast performance.
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
Chrome
Firefox
IE
Edge
Safari
8
3
10
12
6
Mobile / Tablet
Android Chrome
Android Firefox
Android
iOS Safari
91
89
4.4
6.0-6.1
Wrapping things up
A final comparison:
CSS filters are easier to use, but are much more limited. I don’t think it’s possible to add an inset shadow with the drop-shadow() function, for example.
SVG filters are much more robust, but much more complicated as well, and require having the <filter> somewhere in the HTML.
They both have great browser support and perform well on all modern browsers, though SVG filters have (surprisingly) the deepest browser support.
In this article, we have seen why and how to apply shadow to SVG icons with examples on each. Have you done this, but did it a different way than anything we looked at? Have you tried to do a shadow effect that you found impossible to pull off? Please share!
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.
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 screenreaders. 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:
This works for an SVG inside a <button>, say, or if the SVG itself is playing the “button” role.
The icon is wrapped by a link
…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:
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.
I did a little thing the other day that I didn’t know was possible until then. I had a project folder open in VS Code like I always do, and I added another different root folder to the window. I always assumed when you had a project open, it was one top level root folder and that’s it, if you needed another folder elsewhere open, you would open that in another window. But nope!
We kind of have a “duo repo” thing going on at CodePen (one is the main Ruby on Rails app, and one is our microservices), and now I can open them both together:
Multiple folders open at once. This means I don’t need to deal with my symlinks anymore.
Now I can search across both projects and basically just pretend like it’s one big project.
When you do that for the first time and then close the VS Code window, it will ask you if you want to save a “Workspace.” Meh, maybe later, I always thought. I knew what it meant, but I was too lazy to deal with it. It’ll make a file, I thought, and I don’t really have a place for files like that. (I’d avoid the repo itself, just because I don’t want to force my system on anyone else.)
Well, I finally got over it and did it. I chucked all my .code-workspace files into a local folder. They are actually quite useful as files, because I can put the files in my Dock and one-click open my Workspace just how I like it.
Custom Workspace icons
Workspace files have special little icons like this:
The icon is a little generic, but I like it. A document with a little tiny VS Code icon below it.
Since I’m putting these in my Dock, I saw that as a cool opportunity to make them into custom icons! That’ll make it super clear for me and a little more delightful to use since I’ll probably reach for them many times a day.
Taking a little inspiration from the original, I snagged the SVG logo and plopped it on the bottom-right of my project logos.
Changing logos on macOS is as simple as “Get Info” on the file, clicking the logo in that panel, then pasting the image.
Now I can keep them in my Dock and open everything with a single click:
Launch terminal commands when opening a project
Now that I have these really handy one-click icons for opening my projects, I thought, “How cool would it be if it kicked off the commands to start the project too!” Apparently, that’s what Tasks are for, and it wasn’t too hard to set up (thanks, Andrew!). Right next to that settings file, at .vscode/tasks.json, is where I have this:
That kicks off the command gulp for me whenever I open this Workspace. I guess you have to run the task once manually (Terminal → Run Task) so that it has the right permissions, then it works from there on out.
Overrides
I don’t think this is specific to Workspaces necessarily, but I really like how you can have a file like .vscode/settings.json in a project folder to override VS Code settings for a particular project.
For example, here on CSS-Tricks, I have a super basic Sass setup where Gulp preprocesses .scss into .css. That’s all fine, but it’s likely that I’ll search for a selector at some point. I don’t need to see it in .css because I’m not working in vanilla CSS. Like ever. I can put this in that settings file, and know that it’s just for this project, rather than all my projects:
Here’s a nifty trick from Andy Bell that now seems a little obvious in hindsight: if you set an SVG to have a width and height of 1em then you can change the size of it with the font-size property.
Try and change the font-size on the body element below to see the icon scale with the text:
You pretty much always want your icons to be aligned with text so this trick helps by creating that inherent relationship in your code. Pretty nifty, eh?
Icons are great and all, but as we’ve been shown time and time again, they often don’t do the job all by themselves. Even if you do a good job with the accessibility part and make sure there is accompanying text there for assistive technology, in an ironic twist, you might be confusing people who browse visually, like the situation Matt Wilcox describes with his mother in this linked article.
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.