Quick Tip: Dynamically add an icon for external links

read 58 comments

A common feature I've seen on “web 2.0” sites and wikis is the "external link" icon: external link. While I'm not crazy about the idea of sticking these little images all over the HTML, they're a great candidate for using progressive enhancement. In our case, we can use jQuery to add the images pretty easily.

Test the hostname

To identify the external links, we test for the location.hostname against the link's hostname, which will be represented by this.hostname once we have the selector in place, and make sure the two don't match. We should also test for the mere existence of this.hostname to avoid problems or false positives with "mailto" links. our tests will look like this: this.hostname && location.hostname !== this.hostname.

Use the filter function

Now let's wrap that test in a filter function. For our example, we'll test all links inside an "extlinks" element that match the above test. Here is what it looks like:

JavaScript:
  1. $(document).ready(function() {
  2.   $('#extlinks a').filter(function() {
  3.     return this.hostname && this.hostname !== location.hostname;
  4.   }).after(' <img src="/images/external.png" alt="external link"/>');
  5. });

A $(document).ready() is wrapped around the script so that it will execute when the DOM has loaded. Line 4 shows the insertion of the image after each of the external links.

Demo

Here is a little demo using the above code. Of the three links, only the second points to a different site. We should see the external-link icon next to it.

So, that's it. Short and sweet.

comment feed

58 comments

  1. Personally I prefer doing this with pure CSS (using background images and padding with a special class, which is what MediaWiki [for example] does), but JavaScript is another way to go, I suppose.

  2. @Voyagerfan, using css is great if all browsers could support a[href^=http].

  3. Is this also possible for set the favicon on the externel link?

  4. Ben

    If you wanted to implement a CSS version then this is simply a case of doing the following:

       $(document).ready(function() {
            $('#extlinks a').filter(function() {
             return this.hostname && this.hostname !== location.hostname;
            }).addClass('externalLink');
          });
    

    And the CSS would go something like:

    
    .externalLink {
      background:url(/myiconpath/myicon.gif) top left no-repeat;
    // set background position to suit your needs
      padding: 15px 0 0 0;
    // set padding to width of icon plus the size of gap you would like
    }
    
  5. @Voyagerfan5761: the only way to do this consistently across browsers without using JavaScript is to add a class to the link manually or write some sort of server-side script that will do it for you. Doing it manually isn't always practicable, especially for content-managed sites. But if you have total control of the site's HTML, and you're vigilant about adding those classes, then pure CSS is probably the way to go.

    @James Chan: in a content-managed situation, the a[href^=http], while certainly a good start, isn't completely reliable. What's to stop someone from adding the fully qualified URL to a same-site link? If the attribute selector were bulletproof, it would certainly be preferable to the filter function I recommend in the post.

    @Ben: yep, that's another way to do it for sure. Not "pure CSS" as Voyagerfan5761 was suggesting, but definitely a nice option. If your links have text decoration, the underline will extend underneath the icon, so it might not be a viable option, depending on the look you're trying to achieve.

    @Frank: sure, it's possible. Google is your friend here. First result for "external link" favicon is for a jQuery external link favicon plugin.

  6. I played around with this task a while back and came up with an exhaustive, js-based solution which accounts for accessibility and additional usability, with a built in new window toggler.
    More recently, I added a jQuery-based version.

    So, fwiw…

    Flag & toggle external links with jQuery

    It hasn't been touched for quite a while, so may not be taking advantage of recent improvements in the jQuery library.

  7. There's also the straight css way. Doesn't work in old browsers:

    a[rel="external"]{
    background:url(images/external.png) top right no-repeat;
    padding: 12px 0 0 0;
    }

    Of course that means you'd have to add the rel to your links. But I'm a fan of rel, and you could add the rel with jquery if you wish.

    In a perfect world.

  8. Chad Cross

    I like the adding a class part you mention in comment #4. Gives me a lot more control. It does not work in IE6/7 though for me.

  9. Stan

    Karl,
    Will calling after('') for each external link result in a new image download for each link? I could see this being an expensive process for a site with 10-15 external links on it, or even more for that matter. I wonder if generating that image and storing it in a var outside of the filter call would be a speedier solution?

    Thanks for the article. Pax,
    - Stan

  10. @Karl Swedberg: I use the CSS suggested by Ben for links that have :hover text-decoration set, and the underline doesn't go past the end of the text. Is the behavior of that different in browsers like IE7 (which I don't have)?

    @Jethro Larson: The rel method works, but if you're adding something using JavaScript it won't show up to robots, so really you're using the same method as I do (and what Ben suggested), just with a different attribute of the <a> tag.

    @Bill Posters: Looks like an interesting idea there. I'll have to remember that in case I ever want or need that behavior for a future site. Thanks!

  11. @Voyagerfan5761: hmm. Could be that my recollection was faulty. Regardless, if you're using JavaScript to display the icon, does it really matter if you're using a CSS background image or inserting the image into the DOM? As I already said, there isn't a pure CSS way (that I know of) to show the icons without adding a class manually or having some server-side script take care of it—not cross-browser anyway.

    Seems to me that for accessibility reasons, it might actually be preferable to insert the image rather than use a background image, because then screen readers can read something like "(external)" from the alt attribute.

    Of course, I could be wrong about all this. Or, it could be just a matter of taste. I'm interested in hearing your reasons for preferring the CSS background image method.

  12. @Karl Swedberg: I tend to prefer doing things without JavaScript whenever possible. If I can make something work in a browser whose user has disabled JS, why do it in a way that would deny that user the experience? People do browse without JS (though I can't understand why ;-), and I want them to get as much of the experience as possible.

    With regards to accessibility: At least on the main site I work on, having a screen reader read out "(external)" isn't important, because the site uses external links that are clearly external from the context.

    I think it's a matter of personal preference and site-specific reasoning. Your preferred method of doing things would make perfect sense to me if the site involved was different. For example, I could see how it could be a good thing to do here on Learning jQuery because it would provide a practical example of jQuery code.

  13. @Voyagerfan5761: Yes, I prefer doing things without JavaScript whenever possible, too. But, as I've noted more than once now, it's not possible to do this with pure CSS in a way that IE6 will understand, unless you manually add a class (or perhaps use some server-side logic).

    You say that you use the CSS suggested by Ben. That's great. But Ben is applying that class with JavaScript.

    It should be pretty obvious from my posts here as well as from my books and my suggestions on the jQuery discussion list that I don't advocate using JavaScript for presentation when a cross-browser, pure-CSS solution is available.

    Still, do you agree that there are times when pure CSS is insufficient for this task? If not, show me the pure CSS selector that will add a background image to all external links on a page — keeping in mind that many sites are content-managed by less-technical users who won't be adding a class manually.

  14. @Karl Swedberg: Yes, there are times when pure CSS is insufficient for a lot of things. This is only one of them. I'm just saying that I haven't found any situations in which my method has proven to be inadequate. Not to say that such situations don't exist. ;-)

    Re your views being obvious, I honestly haven't been following the comments here much, nor do I regularly read the jQuery mailing list. I'm also sorry to say that I haven't bought any Web development books in over a year, and the last ones were on the subject of PHP. So I didn't know of your similar preferences. But I do now. :-)

  15. I want to not display the external image if the link or tag contian the tag of image (img)
    how do you do for make it?
    sorry for my bad english...

  16. Hi Mte90, you just need to make the selector more specific. So, instead of, for example, $('#extlinks a'), you could do something like, $('#extlinks a:not(:has(img))'). Hope that helps.

  17. Thanks!
    It's perfect!

  18. Matt!

    Hey! This is basically awesome, thanks. One thing I'm wondering about, though, is how easy it would be to add target="_blank" inside the <a> tag as well as the image after it.

    I know very little JavaScript, and even less jQuery, but it seems to me that if there is a .after possibility, there must be some sort of .inside equivalent. Trouble is, "after" isn't a word you can just search for in Google. Does anybody here know how to make this happen?

    Cheers!
    Matt

  19. Hi Matt!

    Use the .attr() method: .after( ... ).attr('target', '_blank');

  20. One other thing, Matt -- even though I'm not crazy about the idea of forcing a new window to appear, you can certainly do it without the target="_blank". Something like this should work:

    $(document).ready(function() {
      $('#extlinks a').filter(function() {
    	return this.hostname && this.hostname !== location.hostname;
      }).after(' <img src="/images/external.png" alt="external link">')
      .click(function() {
        window.open(this.href);
        return false;
      });
    });
    
  21. Matt!

    Thanks a lot, Karl! Yeah, I know I know, user-customizability, etc. The website I work on is not my own though, and I have to do what I'm told. We're in the lengthy process of manually adding target="_blank" to every external link, so this solution is a lot better, thank you!

  22. Hey Matt! Glad the solution is going to work for you. I definitely understand the bind you are in with external constraints determining what you do with the site. I just felt the need to put in that note about forcing a new window to avoid the wrath of others who might decry such an outrage.

  23. Karl - great tip!

    I'm using this on a web site I've built successfully using your method. One thing- how would you make it so the icon is part of the link so when a user hovers over the icon or the link they are both clickable? Just like is done on Wikipedia and MediaWiki? Thanks in advance.

  24. Hi James,

    You would do it the same way, except instead of using .after(), you would use .append():

    $(document).ready(function() {
      $('#extlinks a').filter(function() {
    	return this.hostname && this.hostname !== location.hostname;
      }).append(' <img src="/images/external.png" alt="external link">');
    });
    
  25. That's great Karl, couldn't have been simpler. Thanks for that simple tip. However, is there a way to make it so the icon is not underlined. I should have stated better how I have my links setup. text-decoration is underline by default and on hover text-decoration is none. Was kinda hoping I could not have the icons underlined at all. I want them just hanging out there as an afterthought, no decoration.

  26. Hi James,
    I suppose the best way, in that case, is to simply set a class on the link and then use CSS to position the icon as a background image. So, for the jQuery:

    $(document).ready(function() {
      $('#extlinks a').filter(function() {
    	return this.hostname && this.hostname !== location.hostname;
      }).addClass('external');
    });

    And for the CSS, something like this should do:

    a.external {
      background: url(/images/external.png) no-repeat 100% 50%;
      padding-right: 24px;
    }
    
  27. Josh Sherman

    Karl,
    Great tip, but I found an issue with non-standard ports and Safari (mac)...

    I am using mampstack (bitnami.org) for my development server, and it runs apache on port 8080, so I connect to my page using http://localhost:8080.

    Using this as my test HTML:

    <p><a href="http://www.yahoo.com">Yahoo</a></p>
    <p><a href="/test">Test</a></p>

    The first link should have class="external" added, but in Safari (mac) I found that both links were getting the class added. With a few alert statements, I found that in Safari (mac)
    this.hostname returns localhost:8080 and
    location.hostname returns localhost.

    Obviously, this makes the script flag the second link as external when it shouldn't.

    Note, this is only an issue in Safari (mac) (FF and IE are fine) and ONLY if you specify a port in the URL

    To fix it, I made this change:

    return this.hostname && this.hostname !== location.hostname;
    becomes:
    return this.hostname && (this.hostname).split(":")[0] !== (location.hostname).split(":")[0];

    This way the script only looks at the hostname and discards the port.

    Just thought I'd share this in case anyone else had the same problem, and see if anyone has a different approach.

  28. Josh,

    Thanks a lot for that tip. Looks like another edge case I'll have to take into account.

  29. Okay, so my question would be: How do you do this, but apply it to only text links. In other words, make it ignore links that might be wrapped around images.

    • Hi Michael,
      You can specify that in the selector. Something like this should work:

      $('a:not(:has(img))') // and so on...
      
      • Thanks, that seems to have gotten me partway there, but it doesn't seem to be ignoring images quite right. I have:

        if($(this).not(":has(img)")) {
          $(this).addClass('psuExternalLink');
        }

        Any thoughts on what I'm doing wrong here?

      • Nevermind, I got it!, it was:
        $(this).not(":has(img)").addClass('psuExternalLink');

  30. Thanks for the great tip.

    Personally I think i will implement and use this icon only when a external link is opened in a new window. As mentioned this isn't liked by many people, so an icon to show the use of this action would be very helpful.

  31. Where the hell do I put the .js code? I can't get this to work...

    • Nazim, how about asking a bit more politely next time?

      You can put the .js code in the <head> in a separate file and then include that file in a <script> tag. You'll always need to include the jQuery core file first. Here is an example (you'll have to change the paths/file names to match the ones particular to your server):

      <head>
        <!-- etc. -->
        <script src="/scripts/jquery.js" type="text/javascript"></script>
        <script src="/scripts/yourfile.js" type="text/javascript"></script>
        <!-- etc. -->
      </head>
      
  32. Jan

    Hi Karl,
    I am using your very useful script on one site and I wonder know how to add between anchor and external image non breakable space in order to not divide it in end of the line.
    I have tried this

    
    $(document).ready(function() {
    $('#content a:not(:has(img))').filter(function() {
      return this.hostname && this.hostname !== location.hostname;
    }).after(' ');
    });
    

    but it doesn't work.
    Could you please help? I am not so familiar with javascript.

    • Instead of putting a space there, non-breaking or otherwise, you could just add margin-left to the image. But still, since the image is another inline element, I'm not sure how you can prevent it from breaking to a new line. Anyone else have any ideas?

  33. stk

    Karl - From a useability POV - as a visitor, I don't really care if a link is "external" or "internal". (It seems a very author-centric feature and the graphic just makes reading text more difficult).

    However, I can see using the icon to differentiate between opening links in a "new window" (i.e., clicking the linked word opens the link in the same window and clicking the icon opens the link in a new window).

    This behavior might be differentiated upon internal/external links, but there remains some situations in which you'd want to defeat that, so the method would need to have some HTML override (certain class name or rel value, perhaps).

    Not being a jQuery pro, how difficult would it be to construct such an animal?

    (Of course, not everyone has JS enabled, but it seems the method degrades nicely to "normal link behavior").

    Cheers,
    -stk

  34. I was always curious about this. Now I see how easy it is. Thank you guys, will tell it to friends who asked before...

  35. Hi Karl, great tip, thx.
    I got in trouble wince in my web page it doesn't work. Is there any reason about that?
    You can see the page here: http://www.4sct.net/piloti_wb.php
    Look at the web link close to tartar username

    • Hi Frederico,

      Yes, the problem is that you are using the same selector that I used in my demonstration: $('#extlinks a'). Since your links do not have a containing element with id="extlinks" it isn't matching anything. You might want to try $('.pilot a') instead.

  36. Hello, not sure if you still reading this post, but I will get a chance...

    I would like to use your code for my website. However, Im not really familiar with the jQuery yet.

    Here is my problem. I have some external link that I do not want anchor next to them. I would like to use a CLASS inside the A tag to identifiate them... ex :

    <a href=www.externalsite.com class="noAnchor">LinkHere</a>

    I also do not want to have the anchor on the Image link.

    And I want to add _Blank to those link

    Here is the code I tought that would work...

    $(document).ready(function() { $('a:not((.nojqueryanchor)|(:not(:has(img)))').filter(function() { return this.hostname && this.hostname !== location.hostname; }).after(' ').attr('target', '_blank');});

    The part with ":not(:has(img)) work perfectly... but When i want to add a 2nd condition, this do not work anymore...

    this code work perfectly...

    $(document).ready(function() { $('a:not(:has(img))').filter(function() { return this.hostname && this.hostname !== location.hostname; }).after(' ').attr('target', '_blank');});

    but it add the icon on EVERY link on my website... and instead of putting a class inside all link that I want an icon... i would like to add a class inside all link that i dont want and icon..

    any clues ? thanks

    • I think I got the solution. This look like working perfectly. Maybe there is a shortcut.

      $(document).ready(function() { $('a:not(.nojqueryanchor):not(:has(img))').filter(function() { return this.hostname && this.hostname !== location.hostname; }).after(' ').attr('target', '_blank');});

      • $(document).ready(function() { $('a:not(.nojqueryanchor):not(:has(img))').filter(function() { return this.hostname && this.hostname !== location.hostname; }).after(' ').attr('target', '_blank');});

        This is my latest code. Unfortunatly, im getting an error about "has" that is malformed, in the Firefox's console.

        Do i need to add something ? like.... :not(":has(img)")... or :not(":has("img")")

        Thanks

        • this is the error message i got.

          Warning: Unknown pseudo-class or pseudo-element 'has'. Malformed simple selector as negation pseudo-class argument 'has'.

          On some forum, peoples are saying this :
          it's not an error, it's just a warning.
          And this particular warning tells us that Firefox is not aware of
          jQuery selectors, at least not of the :gt() selector, but, as long as
          you know that it is a valid selector (and it works), you should just
          ignore the warning....

          Is there something we can do for this ? or just wait for the jQuery 1.5 to solve that issue ? or maybe next version of FireFox...

          Thanks

        • My recommendation is to take the advice from the people on that forum: ignore those Firefox warnings. They're meaningless within the context of JavaScript.

  37. @Karl - thanks for the non-image-containing-link selector. Tried every syntax EXCEPT that one ;)

  38. Hi Karl, another great post from you! keep up with the good work.. I have a question regarding this post, lets say I have a subdomain of help.whatever.com and I don't want the external link icon to appear on links of my main domain which could be http://www.whatever.com I want them both to be consider as one domain.. How can I do that? Any recommendation?

    Cheers,
    E.K

    • Hi there,
      You could write a little function that strips the subdomain before comparing. The function could look something like this:

      function stripSub(str) {
        var parts = str.split('.');
        if (parts.length === 3) {
          parts.shift();
        }
        return parts.join('.');
      }
      

      Then your return statement would look like this:

      return this.hostname &&
         stripSub(this.hostname) !== stripSub(location.hostname);
      
  39. Thanks for sharing this useful script.

    Do you have any piece of code which uses jquery to let me

    1) assign rel="nofollow" to all external links
    2) open in new window

15 Pings

  1. [...] to know how to add fancy “this is an external link” icons to his page.  Since that was basically written for me by Karl Swedberg at Learning jQuery, I decided to take it up a notch (using some AJAX and JSON [...]

  2. [...] tutorial é uma adptação do original de: Learning jQuery. Categorias: jQuery Tags: JavaScript, [...]

  3. [...] Create a dynamic FAQ: open link - Dynamically add an icon for external links: open link - jQuery Google feed plugin: open link - jwysiwyg text editor plugin: open link - jquery corners [...]

  4. [...] 22. Quick Tip: Dynamically add an icon for external links [...]

  5. [...] usability. If links are linking to external website, an image will be displayed next to the link. Dynamically add an icon for external links view plaincopy to [...]

  6. [...] this from scratch, as there are some good examples out there, such as the one from Karl Swedberg (Quick Tip: Dynamically add an icon for external links) and others.  However, I wanted to try to mirror my earlier implementation exactly, which was a [...]

  7. [...] This trick is pretty simple and good for usability. If links are linking to external website, an image will be displayed next to the link. Dynamically add an icon for external links [...]

  8. [...] Quick Tip: Dynamically add an icon for external links » Learning jQuery – Tips, Techniques, T... (tags: jquery javascript tutorial webdesign external links icon) [...]

  9. [...] Dynamically add an icon for external links – Link. [...]

  10. [...] Quick Tip: Dynamically add an icon for external linksA common feature on Web 2.0 sites and wikis is the “external link” icon: external link. This is a great candidate for using progressive enhancement. By using just a little jQuery, you can easily add the images. So, that’s it. Short and sweet, a quick tip, how-to and code to help you accomplish it fast. [...]

  11. [...] problem, you can do like me and borrow this trick from Karl Swedberg: $('a').filter(function() { return this.hostname && this.hostname [...]

  12. [...] This trick is pretty simple and good for usability. If links are linking to external website, an image will be displayed next to the link. Dynamically add an icon for external links [...]

Sorry, but comments for this entry are now closed.