• mina86.com

  • Categories
  • Code
  • Contact
  • Styling external links, yea or nay?

    Dear Reader, I have a question: should external (or outbound) links be styled differently from internal ones? For example, in a sentence like: ‘the recent events made me reconsider my earlier position on the matter,’ should the first link (to an external website) have a different style than the second (to another page on the same website)? I’d love to hear your thoughts in comments below. I’m particularly interested in existing UX research on the subject.

    At the time of writing, this website uses an icon after the link to indicate it sends the user to a third-party website. This is inspired by Wikipedia which uses the same design. It adds information but also visual clutter and cognitive load. It doesn’t appear to be a common feature on the Internet. Hence my inquiry.


    Rather than leaving you, Dear Reader, with only a question, I’ll now present how to achieve this styling using three different CSS methods. The effect needs two elements: selecting external links and changing their appearance.

    Selecting external links

    Picking external links can be done with a CSS attribute selector. If all internal links are relative, e.g. /path and not https://example.com/path, doing a prefix match on href attribute is sufficient as shown below. Starting links with two slashes is a valid way to point to an external domain, so that’s included as well.

    a[href^="https://"], a[href^="http://"], a[href^="//"] {
      border: 1px solid red;
      padding-inline: 0.125em;
      box-decoration-break: clone;
    }

    If guaranteeing use of relative links is not possible, or there are multiple domains that belong to the website, the :not() selector can be used as shown below. It is supported by 96% of browsers, so using it shouldn’t be an issue.

    a[href^="https://"]:not([href^="https://example.com/"]),
    a[href^="http://"]:not([href^="http://example.com/"]),
    a[href^="//"]:not([href^="//example.com/"]) {
      border: 1px solid red;
      padding-inline: 0.125em;
      box-decoration-break: clone;
    }

    With many domains this may become convoluted. In such case, it might be better to explicitly mark external links with a class, although that’s best done by automated tools.

    Styling the link

    With selector for the link sorted, below are three methods for adding the icon. The first is to use background image, the second uses a mask and the third one uses a custom font.

    Using background image

    This is the method Wikipedia uses. The idea is to add a background image on the right side of the link like so:

    @media screen {
      a[href^="https://"], a[href^="http://"], a[href^="//"] {
        background:
          url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><path fill="%2336c" d="M6 1h5v5L8.86 3.85 4.7 8 4 7.3l4.15-4.16zM2 3h2v1H2v6h6V8h1v2a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1"/></svg>')
          no-repeat 100% 0 / 0.75em;
        padding-right: 0.75em;
      }
    }

    Example link with the icon made using the background image method. Notice underline doesn’t span under the icon and its colour never changes.

    Yes, embedding SVG images in CSS in this way is fine provided a few specific characters are handled. New line characters have to be replaced with spaces or eliminated completely. SVGO can handle that. Furthermore, percent signs, question marks, hash signs and apostrophes have to be replaced with %25, %3f, %23 and \' respectively.

    The colour of the icon is specified via the fill attribute. The hex notation uses a hash sign which had to be escaped resulting in the fill="%2336c" fragment in the code. The last three hexadecimal digits can be changed to customise icon’s colour. no-repeat makes only one copy of the image appear; 100% 0 places it at top right of the link; 0.75em specifies size of the icon (75% of link’s font size); and padding-right adds space so the icon isn’t shown under the text.

    The main downside is inability to control colour of the icon via styles. To address this, a few copies of the image can be prepared or dynamically generated on the server. On the flip side, it’s possible to make a multi-colour or animated icon (although please don’t do the latter).

    Using a mask

    The colour limitation of the background image method can be addressed by using a solid-colour background with a mask. It uses an ::after pseudo-element since the mask mustn’t be applied to the link itself. The CSS code is very similar to one using background image:

    @media screen {
      a[href^="https://"]::after,
      a[href^="http://"]::after,
      a[href^="//"]::after{
        content: '';
        background: currentcolor;
        mask: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><path fill="red" d="M6 1h5v5L8.86 3.85 4.7 8 4 7.3l4.15-4.16zM2 3h2v1H2v6h6V8h1v2a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1"/></svg>') no-repeat 100% 0% / 0.75em;
        padding-right: 0.75em;
      }
    }

    Example link with the icon made using the mask method. Notice underline doesn’t span under the icon and its colour matches, and changes with, the text.

    background: currentcolor sets colour of the icon to match the link text but any other colour or image can be used as well. Note that the fill colour in the SVG image doesn’t matter so long as it’s opaque.

    mask is supported by 87% of browsers; graphical browsers without support show a rectangle of chosen background colour. Depending on one’s threshold for browser support, this may be the best approach.

    Using custom font

    The final option is to use a custom font. Creating one isn’t terribly hard with FontForge or one can grab the ready-made ExternalIcon.woff2. With the font obtained, the external links can be styled with the following:

    @media screen {
      @font-face {
        font-family: ExternalIcon;
        font-style: normal;
        font-weight: 400;
        src: url(/path/to/font.woff2) format("woff2");
      }
    
      a[href^="https://"]::after,
      a[href^="http://"]::after,
      a[href^="//"]::after {
        font-family: ExternalIcon;
        content: '⎋';
        font-size: 0.875em;
        vertical-align: top;
      }
    }

    Example link with the icon added via a custom font. Notice underline spans under the icon and its colour matches, and changes with, the text.

    The method adds ‘⎋’ character after the link which ExternalIcon font replaces with a desired icon. If the font isn’t loaded, the symbol itself is shown so it’s helpful that it resembles the icon. The advantage is that the icon can be styled like any other text element. For example its colour can be adjusted via the color property.

    The main downside is that it’s not possible to remove text decoration (such as underline) from the icon. Using display: inline-block will get rid of it, but introduces line-breaking issues. This is the opposite of the earlier methods where adding underline can only be simulated with an outline or a border (which have their own issues, namely lack of the skip-ink feature). I’m also not sure how screen readers handle the text content.

    Another consideration is size. The SVG image in the first two methods is around 200 bytes which compress easily embedded inside of a CSS file; the custom font is around 900 bytes. Multiple copies of the SVG images will take up less space although if the custom font is used for other glyphs as well, the overhead won’t be as large. (This website uses a 16-glyph font with a few ligature definitions whose file size is 4.5 kB).

    Which method to choose?

    To summarise, below are the three methods together with their browser support (according to caniuse.com) and overview notes:

    MethodBrowser supportNotes
    Background image96%Icon’s colour cannot be styled, text decorations don’t continue under the icon.
    Mask87%Allows colour styling, text decorations don’t continue under the icon, browsers without support show solid rectangle.
    Custom font96%Allows colour styling, text decoration cannot be removed if present on the link text.

    For people not paranoid about browser support, the mask method is probably the best. The background image method is probably the second choice unless the colour matching is an issue in which case font method might be considered instead.

    This website previously used the background image method before temporarily switching to a custom font. I already used a custom font for other icons, so adding one more glyph was trivial. However, due to the underline issues, I eventually reverted to the background image approach (I am paranoid about browser support). To handle lack of colour customisation, I chose a colour with an L* of 50 (50% perceived lightness), ensuring consistent contrast in both light and dark modes.