SVG triangle of compromise (resolved)
This post was updated on 2024-07-30 in response to feedback from Hacker News and Mastodon. It turns out that I was wrong about the original thrust of my post ?. I've added new sections and updated existing ones, and I hope the current version makes sense whether you saw the original version or not.
The original title was "SVG triangle of compromise", because I didn't know about the <use>
tag which hits the sweet spot of stylable, cacheable, and dimensional.
A complete list of updates is at the end of the post.
Thanks to everyone on HN and Mastodon for the discussion, and especially to Scott Jehl for making a great interactive demo that shows how the <use>
tag works.
Table of Contents
With SVGs on the web, you might care about a few different properties:
- Stylable: can be colored with CSS (including foreground, background,
:hover
states, etc) - Cacheable: load once, use on other pages is free
- Dimensional: has an intrinsic width and height (especially useful for inline text so you can do
height:1em
and have it scale in the correct proportions to fit the current text size)
stylable cacheable dimensional <iframe> with inline
- stylable
- cacheable
- dimensional
The nice little Finder icon in the next paragraph is from IcoMoon. It can be styled with CSS, including setting the icon's color to the color of the surrounding text with fill:currentColor
; it could be used elsewhere on the page but will be downloaded only once; and it scales proportionally with the surrounding text size with height:1em
. The SVG even supports the :hover
state, try it!
Nice icon :)
How it works
To get all three properties, create an SVG file with the icon(s) you want to use with each one wrapped in a <symbol>
tag with an id
attribute. I'll use this SVG file, which also includes the check- and x- mark icons and . If you load that in the browser, it won't display anything, because it only defines the icons without showing them, but you can see its contents below, along with the CSS that styles it.
styles and icons.svg
CSS
span.example-svg-use {
color: blueviolet;
}
span.example-svg-use svg {
height: 1em;
fill: currentColor;
}
span.example-svg-use svg:hover {
fill: aquamarine;
}
HTML
<p>
<span class="example-svg-use">
Nice icon
<svg viewBox="0 0 100 100"><use href='icons.svg#icon-finder'/></svg>
:)
</span>
</p>
SVG
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
<symbol id="icon-check" viewBox="0 0 512 512">
<!-- Font Awesome Free 5.15.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) -->
<title>Check mark icon</title>
<path d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"/>
</symbol>
<symbol id="icon-times" viewBox="0 0 352 512">
<!-- Font Awesome Free 5.15.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) -->
<title>X icon</title>
<path d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"/>
</symbol>
<symbol id="icon-finder" viewBox="0 0 32 32">
<!-- IcoMoon - https://icomoon.io/ - GPL -->
<title>Finder icon</title>
<path d="M17.788 24.32c-0-0.001-0-0.003-0-0.004 0 0.001 0 0.003 0 0.004z"></path>
<path d="M17.831 25.454c-0-0.007-0.001-0.015-0.001-0.022 0 0.007 0.001 0.015 0.001 0.022z"></path>
<path d="M17.807 24.885c-0-0.006-0-0.011-0.001-0.017 0 0.006 0 0.011 0.001 0.017z"></path>
<path d="M30 0h-28c-1.1 0-2 0.9-2 2v28c0 1.1 0.9 2 2 2h15.432c0.001 0 0.003 0 0.004 0s0.003-0 0.004-0h12.56c1.1 0 2-0.9 2-2v-28c0-1.1-0.9-2-2-2zM6 7c0-0.552 0.448-1 1-1s1 0.448 1 1v2c0 0.552-0.448 1-1 1s-1-0.448-1-1v-2zM30 30h-11.721c-0.209-1.315-0.341-2.671-0.418-3.988 0 0.004 0 0.007 0.001 0.011-0.616 0.067-1.237 0.102-1.862 0.102-4.177 0-8.199-1.52-11.328-4.281-0.466-0.411-0.51-1.122-0.099-1.588s1.122-0.51 1.588-0.099c2.717 2.398 6.211 3.718 9.839 3.718 0.596 0 1.189-0.036 1.776-0.107-0.069-3.694 0.215-6.622 0.22-6.669 0.028-0.281-0.064-0.561-0.254-0.77s-0.459-0.329-0.741-0.329h-2.974c0.043-1.082 0.159-2.932 0.467-5.006 0.589-3.961 1.625-7.055 3.005-8.994h12.502v28z"></path>
<path d="M25 10c-0.552 0-1-0.448-1-1v-2c0-0.552 0.448-1 1-1s1 0.448 1 1v2c0 0.552-0.448 1-1 1z"></path>
<path d="M16.89 26.101c-0.114 0.006-0.228 0.011-0.342 0.014 0.114-0.004 0.228-0.008 0.342-0.014z"></path>
<path d="M16 26.125c0.147 0 0.293-0.002 0.439-0.006-0.146 0.004-0.292 0.006-0.439 0.006z"></path>
<path d="M16.846 23.85c0.024-0.001 0.049-0.003 0.073-0.004-0.024 0.002-0.049 0.003-0.073 0.004z"></path>
<path d="M16.409 23.869c0.034-0.001 0.067-0.002 0.101-0.003-0.034 0.001-0.067 0.003-0.101 0.003z"></path>
<path d="M27.427 20.256c-0.411-0.466-1.122-0.51-1.588-0.099-2.27 2.004-5.083 3.254-8.063 3.612 0.013 0.727 0.040 1.484 0.086 2.255 3.499-0.382 6.806-1.832 9.466-4.18 0.466-0.411 0.51-1.122 0.099-1.588z"></path>
<path d="M17.859 26.023c-0.144 0.016-0.288 0.029-0.433 0.041 0.145-0.012 0.289-0.026 0.433-0.041z"></path>
<path d="M17.359 26.070c-0.122 0.010-0.244 0.018-0.367 0.025 0.122-0.007 0.245-0.015 0.367-0.025z"></path>
</symbol>
</svg>
The CSS properties that apply to SVGs are sometimes different than those that apply to HTML elements, like fill
instead of color
, but it's all CSS in the end, and you can use the same stylesheet to style SVG elements along with the rest of your page.
As mentioned, the SVG file must contain <symbol>
tags around each icon you want to use, and a single SVG file can contain multiple icons defined this way. FontAwesome does this. This will let you define all your site's icons in one place and reference them on every page, although whether this is a good idea depends on your use pattern.
It's worth playing with Scott Jehl's interactive demo, too.
Caveats
There are a few things to consider with this method.
- Most significantly, it requires the SVG file to be hosted on the same domain as the page. CORS has no effect at all.
- Because it will not work without
<symbol>
tags in the SVG, you may need to modify the SVG file to use this method. - Web feed (RSS) readers don't seem to support this method at all, which means that any posts with SVGs referenced this way will appear broken in feed readers.
- The SVG is loaded into the shadow DOM, which means you can't interact with it directly in JavaScript, and you don't have direct styling control over every element the way an inline SVG allows. As we've seen above, the limitations do permit setting
fill
color, and they also permit things likefont-family
and a few other properties. However, they don't permit CSS selectors for individual elements inside the SVG like<circle>
etc.
Displaying an external SVG with the <img>
tag
- stylable
- cacheable
- dimensional
An SVG referenced via an <img>
tag works like any other image. If you load the same image on multiple pages, it will be cached and not re-downloaded. It can be used without specifying width and height to be displayed at its natural size, or with only one of the two specified for it to be displayed proportionally, or can be made to automatically fit its container. Other image types like PNG or JPEG files only support being referenced this way in the first place.
Displaying an inline SVG with the <svg>
tag
- stylable
- cacheable
- dimensional
HTML supports an <svg>
tag that can contain entire SVG elements directly in the document. Unlike <svg>
with a <use>
tag, inline <svg>
can be styled directly by the regular DOM's CSS without limitations, and can be manipulated with JavaScript, just like any other element! This is how the circles of the Venn diagram above are highlighted when you hover over the text labels.
Of course, placing the SVG inside of the HTML means it cannot be cached across multiple uses, any more than a <p>
or a <div>
is cached when it's repeated on multiple pages.
- stylable
- cacheable
- dimensional
There's nothing to recommend this method over the others, but I include it because I mentioned it in the first version of this post, and I think it's neat.
The idea is to create an HTML file that contains only the <svg>
tag, and reference it on the site via an <iframe>
. This can be styled with CSS by including a <style>
tag in the HTML file, which means it can share styles with the rest of the site, although it's not as good as direct stylablility with CSS from the parent document. However, an <iframe>
does not have an intrinsic width or height, so you need to either measure the frame in JavaScript after it has loaded or set the dimensions in advance.
If the above does not dissuade you, you might be interested to see the frame measuring solution I have implemented in the past for archiving tweets to HTML.
Other options
It's also worth mentioning a few other ways to work with SVGs:
-
You could create two SVG files, one for the normal state and one for the hover state, reference them via
<img>
tags, and swap them via CSS when the user hovers over the element. At least as of the time of this writing, this is what I do for my site logo in the header. This works well if all you need are the two states of hover and normal. This is kind of an outdated way of doing things, though. -
You could use the
<object>
tag, which is like an<iframe>
but for multimedia objects like SVGs. It has the same dimensionality problem as the<iframe>
tag, and is not stylable with CSS from the parent document, but can be manipulated with JavaScript from the parent. -
SMIL can be used to animate SVGs. It can be used alongside CSS, and each has capabilities that the other does not.
-
2024-07-31: Fix incorrect references to
<def>
, which should read<symbol>
. Thanks to Craig in the comments for pointing this out. -
2024-07-30:
- Add new section on
<use>
tags - Update the diagram to reflect the new section
- Updated wording in most of the other sections
- Add a copy of the diagram in its original form in the
<details>
element below - Move updates to the bottom of the page
- Add a table of contents
- Add new section on
-
2024-07-29:
- Add a note about pending updates
- Add a link to Scott Jehl's
<use>
demo - Remove nested CSS styles (they were only ever on this page); some HN commenters said these didn't work for them, which surprised me considering they're now in all major browsers... some of you need to apply your software updates!
-
2024-07-25: Add list of properties to each subheading, show
<use>
tags
The WRONG but improved version of the original diagram
This diagram is not accurate! It comes from a suggestion on HN, and I couldn't help but make the change in light of the words of another commentor: "You must have some pretty important work to do with this level of procrastination ongoing." I, too, have some pretty important work to do.
stylable cacheable dimensional <iframe> with inline