So you want to put some SVG icons on your 11ty site, hey? This technique lets you include icons in your posts easily. With a little initial investment, adding and using icons should be easy for you and easy on your users.
Step 1: The Collection
Let's start by creating a collection for our icons. In a directory of your
choosing (mine is called /icons
), add an 11ty directory data file (e.g.
icons.json
), and include the following contents:
{
"permalink": false
"tags": [
"icon"
]
}
The permalink: false
setting prevents 11ty from writing the icons to the
output dir, and the tag adds each one to the new icon
collection.
Populate the /icons
directory with your SVG files.
Step 2: The Shortcode
That having been accomplished, let's define the shortcode that we'll use to display icons on our pages. The shortcode will take a name and an optional map of HTML attributes, and works like this:
{% icon 'html5' %}
{% icon 'svg', 'aria-labelledby'='svg-w3c' %}
{% icon '11ty', title='eleventy' %}
By storing a set of icons requested on each page
object, our sprite sheet will
only render those icons as actually are needed, saving your readers' data.
What's more, since we use use href
to actually display the icons, we only need
to render each SVG drawing once per page, further reducing page sizes when there
are multiple instances of the same icon in use.
eleventyConfig.addShortcode('icon', function icon(name, kwargs) {
this.ctx.page.icons ||= new Set();
this.ctx.page.icons.add(name);
const { __keywords, ...attrs } = kwargs ?? {}
const attributes =
Object.entries(attrs)
.map(([name, value]) => `${name}="${value}"`)
.join(' ');
return `<svg ${attributes}><use href="#${name}-icon"></use></svg>`;
});
Abusing the page
object like this kinda gives me the creeps, but 🤷, its
works!
Step 2: The Sprite Sheet
With our icons and shortcodes up and running, we still can't see any icons on our pages, so let's add our sprite sheet to our base HTML. Put the following nunjucks snippet just before the `` tag of your most basic page template:
{% if page.icons %}
<svg id="icon-sprite-sheet">
<defs>
{% for icon in collections.icon %}
{% if page.icons.has(icon.fileSlug) %}
<g id="{{ icon.fileSlug }}-icon">{{ icon.content | safe }}</g>
{% endif %}
{% endfor %}
</defs>
</svg>
{% endif %}
The if
nunjucks tag ensures that only icons that had been requested on this
page via shortcode actually get printed to the final HTML.
Let's now add some visually-hidden styles to our sprite sheet, so it won't take up any space on the page.
#icon-sprite-sheet {
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}
Accessibility Concerns
Make sure that each icon you use either has a <title>
in the SVG, or use
aria-label*
, or remove the icon from the a11y tree.
This technique could be a nice little optimization for your pages, or could turn out to seriously cut down on your bytes-over-the-wire, depending on how you use icons.
If you have any ideas for improvements, let me know on mastodon.