Appending External Link Icons with CSS

Automatically handle those pesky external link icons throughout your app with a small bit of CSS.

Issue at Hand

A common practice in web and native applications is to append an external link icon to links that leave the current site's domain. This is a great practice from a user experience perspective because it forewarns the user that they are about to leave the current site to visit an external resource. For example...

Here is an external link to REI.com.

While a great practice it can be particularly problematic to maintain. If not done automatically then it must be done manually which requires continuous observation from a developer to assign classes to these links or if content is being added via CMS then the content editor or copywrite also needs to make efforts to class these links correctly.

This is not future proof and will most likely be missed.

The :after pseudo class

The easiest way to append this icon to any link is using the :after pseudo class in combination with the content style attribute.

For this approach I'm using SVG data. It's worth noting that you'll need to url-encode your SVG data for this to work. Here's an excellent resource for doing just that.

a:after {
  content: url("data:image/svg+xml,%3Csvg x='0px' y='0px' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' style='enable-background:new 0 0 16 16;'%3E%3Cpath fill='%23000000' d='M10,0C9.4,0,9,0.4,9,1s0.4,1,1,1h2.6L6.3,8.3c-0.4,0.4-0.4,1,0,1.4s1,0.4,1.4,0L14,3.4V6c0,0.6,0.4,1,1,1s1-0.4,1-1V1 c0-0.6-0.4-1-1-1H10z M2.5,1C1.1,1,0,2.1,0,3.5v10C0,14.9,1.1,16,2.5,16h10c1.4,0,2.5-1.1,2.5-2.5V10c0-0.6-0.4-1-1-1s-1,0.4-1,1 v3.5c0,0.3-0.2,0.5-0.5,0.5h-10C2.2,14,2,13.8,2,13.5v-10C2,3.2,2.2,3,2.5,3H6c0.6,0,1-0.4,1-1S6.6,1,6,1H2.5z'/%3E%3C/svg%3E");
}

This will now append the icon after all anchor tags. Close, but we need to refine this with some more specific selectors so that only external links get the icon. So we need to think about the properties of an external link:

  • External links won't contain my domain (robertcreates.com)
  • External links won't be relative

And that is pretty much it. Everything else would signify an external link.

A CSS selector for links that won't include my domain

Our first task is to create a CSS selector that won't match my domain. For this we can use an attribute selector in combination with the includes operator: *= and the :not pseudo-class function:

a:not([href*='robertcreates.com'])

This selector simply says any anchor element that does not have an href attribute that contains 'robertcreates.com'.

CSS selectors for relative links

There are few things to consider with relative links, you can have a root link: / a same path link ./ or prior directory links ../. You can even have multiple prior directory links ../../.

Considering that all of these types of relative links are prepended with the content we're looking for, we can use an attribute selector with the begins with operator: ^= to rule out relative links. This would look something like this:

a:not([href^='/'])
a:not([href^='./'])
a:not([href^='../'])

These three selectors cover all the cases described above. The multiple prior directory link is covered by the single prior directory link, since both start with ../.

Bringing it all together

Now that we've defined how to create selectors for all our instances of external links, we have to put them together in the CSS. At first glance it may appear we could do something like this:

a:not([href*='robertcreates.com']):after,
a:not([href^='/']):after,
a:not([href^='./']):after,
a:not([href^='../']):after {
  ...
}

However, this won't work. This syntax works similarly to an or condition and would ultimately append the icon to every single link. We need our selectors to function more like an and condition.

The :is() pseudo-class Function

This is where the :is() pseudo-class function comes in. Using this we can chain these requirements.

a:not(:is([href*='robertcreates.com'], [href^='/'], [href^='./'], [href^='../']))::after {
  ...
}

The :is() CSS pseudo-class function takes a selector list as its argument, and selects any element that can be selected by one of the selectors in that list. This gives us what we need in order to target our external links.

Note: you may wish to also include selectors specific to other environments like local development or staging such as [href*='localhost'].

The final CSS

And here's the final css including a bit of styling for size and margin.

a:not(:is([href*='robertcreates.com'], [href^='/'], [href^='./'], [href^='../']))::after {
  width: 14px;
  height: 14px;
  display:inline-block;
  margin-left: 5px;
  content: url("data:image/svg+xml,%3Csvg x='0px' y='0px' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' style='enable-background:new 0 0 16 16;'%3E%3Cpath fill='%23000000' d='M10,0C9.4,0,9,0.4,9,1s0.4,1,1,1h2.6L6.3,8.3c-0.4,0.4-0.4,1,0,1.4s1,0.4,1.4,0L14,3.4V6c0,0.6,0.4,1,1,1s1-0.4,1-1V1 c0-0.6-0.4-1-1-1H10z M2.5,1C1.1,1,0,2.1,0,3.5v10C0,14.9,1.1,16,2.5,16h10c1.4,0,2.5-1.1,2.5-2.5V10c0-0.6-0.4-1-1-1s-1,0.4-1,1 v3.5c0,0.3-0.2,0.5-0.5,0.5h-10C2.2,14,2,13.8,2,13.5v-10C2,3.2,2.2,3,2.5,3H6c0.6,0,1-0.4,1-1S6.6,1,6,1H2.5z'/%3E%3C/svg%3E");
}

You can see a full working example of this technique on Codepen.io.