Fun with SVG masks and link states
A recent project called for indicator icons for outbound links. (You know, the box with the arrow pointing up and right, like the ones on wikipedia). I’m a little on the fence about these in general, but the way the content on this site is written there are a number of outbound links that you might assume are local, so the added clarity is probably a good thing.
I assumed by now that there would be a unicode character for that, but apparently not. No problem though, we can use an SVG (Specifically this one: , taken from that page I just linked to) and put it in in the CSS.
.outbound::after {
content: url('data:image/svg+xml;utf-8,<svg viewBox="0 0 768 1024" xmlns="http://www.w3.org/2000/svg"><path d="M640 768H128V257.90599999999995L256 256V128H0v768h768V576H640V768zM384 128l128 128L320 448l128 128 192-192 128 128V128H384z"/></svg>');
display: inline-block;
height: 1em;
margin: 0 0 0 0.4em;
vertical-align: middle;
width: 0.75em;
}
Perfect, but the design also calls for different colors for visited and unvisited links. Now we need to add our SVG into the CSS twice, with different fill colors for each link state. That’s annoying. If we later add hover states, or need links to use a different color (maybe for contrast accessibility, or for a darkmode) then this adds up to a lot of extra embedded SVGs. Byte-size it’s probably fine, this SVG is only about 170 bytes, but it would be a pain to maintain all of them. So instead of using the SVG directly, we’re going to apply it as a mask.
a:link {
color: #00f;
}
a:visited {
color: #800080;
}
@supports (mask-image: var(--outbound-icon)) or (-webkit-mask-image: var(--outbound-icon)) {
.outbound::after {
--outbound-icon: url('data:image/svg+xml;utf-8,<svg viewBox="0 0 768 1024" xmlns="http://www.w3.org/2000/svg"><path d="M640 768H128V257.90599999999995L256 256V128H0v768h768V576H640V768zM384 128l128 128L320 448l128 128 192-192 128 128V128H384z"/></svg>');
background-color: currentColor;
content: '\2003';
display: inline;
font-family: Arial, sans-serif;
line-height: 1em;
margin-left: 0.3em;
-webkit-mask-image: var(--outbound-icon);
mask-image: var(--outbound-icon);
-webkit-mask-size: 1em calc(1em + 2px);
mask-size: 1em 1em;
vertical-align: middle;
}
}
OK, so we’ve done a few interesting things here.
- Wrap the styles for the icon inside a
@supports
block. This feature is non-critical, so we can treat it as an enhancement. Using@supports
prevents older browsers from rendering a block of color if they can’t use masks. - Put the SVG inside a CSS custom property. This is because we still need to put it in the CSS in two places, (the
-webkit-mask-image
and themask-image
) but we only want to actually type it out once. - Set
display: inline
. It seems like this should beinline-block
, but that can occasionally do some weird stuff when the icon is the last thing on a line. Instead, useline-height
to set theheight
and use an em-space for thecontent
value to set the width. Use Arial for the font because it has the em-space character and is about as web-safe as we can get with a font. - After moving to
display: inline
Safari sometimes has some difficulty with theline-height
so we need to add acalc
to ourmask-size
property to compensate.
With this in place, the mask-image
hides everything that isn’t part of the SVG icon and setting the background to currentColor
makes sure we reflect the current state of the link correctly.
Result #
Optionally, refactor to use shorthand syntax:
@supports (mask-image: var(--outbound-icon)) or (-webkit-mask-image: var(--outbound-icon)) {
.outbound::after {
--outbound-icon: url('data:image/svg+xml;utf-8,<svg viewBox="0 0 768 1024" xmlns="http://www.w3.org/2000/svg"><path d="M640 768H128V257.90599999999995L256 256V128H0v768h768V576H640V768zM384 128l128 128L320 448l128 128 192-192 128 128V128H384z"/></svg>');
background-color: currentColor;
content: '\2003';
display: inline;
font-family: Arial, sans-serif;
line-height: 1em;
margin-left: 0.3em;
-webkit-mask: var(--outbound-icon) center right / 1em calc(1em + 2px) no-repeat;
mask: var(--outbound-icon) center right / 1em 1em no-repeat;
vertical-align: middle;
}
}