Accessible Pop-up Links

Sometimes we have to use pop-ups — so we might as well do them right. This article will show you how to make them more accessible and reliable while simplifying their implementation.

Article Continues Below

For the purposes of this article, pop-up links are links that open in a new window, and that rely on JavaScript to do so. Ordinarily, all you need for a link to open on a new window is to set its “target” attribute to “_blank”. With pop-up links, JavaScript is used either because special window properties must be set, or because the DOCTYPE in place does not allow the “target” attribute.

Links 101#section2

The concepts here are very simple, but let’s get some terminology clear before we continue:

We’ll be using here the HTML “a” element and dealing with its “href” and “target” attributes. We’ll also use the “window.open” JavaScript method and refer to its arguments “url,” “name,” and “features”. Below is a table describing these items and their equivalence between HTML and JavaScript.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Concept a attribute window.open parameter Description
Link destination href url The URI of a resource to be loaded
Target window target name An identifier of the window where the resource should load
Features N/A features Chrome/UI properties of the target window

The bad#section3

First, let’s put an end to the problem with most JavaScript pop-up links: their HTML. Here’s a representative sample:

<a href="JavaScript:
  raw_popup('http://example.com');
  void(0)">pop me up

This piece of HTML is the root of all that’s unholy in the pop-up world. It sports two serious usability issues that would otherwise be easily avoidable. The first one should be clear: users without JavaScript are unable to follow the link.

The reason why the link won’t load for these users is simple, and stupid: the link destination is set to an action (a JavaScript function), rather than to its actual destination. Depressingly, the purpose of this function is to load the actual link destination.

The ugly#section4

The second issue is not so obvious, but comes with its own interesting irony: the link will fail for the very people who concur with the designer’s decision: users who already planned to open the link in a new window.

When I intentionally open a link in a new tab or window, here’s a simplified description of what the browser does: it grabs the value specified in the link’s “href” attribute and it sets it as the location for the new tab or window.

If JavaScript is disabled, we reach the same issue described before. If JavaScript is enabled, however, the browser will call the pop-up function within the scope of the new window’s location bar — and it’ll fail, because the function only exists within the scope of the original page. In other words, when you open a new window and it tries to run that JavaScript function, it will fail because the new window has never heard of that JavaScript function.

A place for everything…#section5

You’ll notice that both problems stem from the same mistake: they specify something that’s not the link’s destination as the link’s destination.

A better approach would be to specify the link’s destination correctly in the “href” attribute, and use an event handler (which is where you place JavaScript code) to open the new window and set its special properties.

Here’s a simple approach:

<a href="http://example.com">pop me up

We can avoid redundancy by passing the element to the function and having it read the URL from the “href” attribute:

<a href="http://example.com">pop me up

The former example is still useful when you want to provide a separate URL for users without JavaScript. Keep in mind that the URL that will open for users who manually launch the link in a new window will be the one specified in the “href” attribute, not the one passed via script. (You can always use a JavaScript redirect in that page.)

If you were hoping to see some kickass JavaScript, hold on. For now, we’re not interested in the actual JavaScript implementation, we’re just trying to define the best HTML code. Either example above would already solve the accessibility problems because the “href” attribute contains the actual link destination. It’s good enough, but we can make it better.

Do it with HTML#section6

A guideline I use while coding is that I should only script what HTML cannot handle. If it can be done in HTML, then it should be. JavaScript should come as a complement.

So can HTML follow a link? Yes. Can HTML make this link load in a new window? Yes. (With transitional DOCTYPEs at least.) Can HTML determine the chrome features of this window? No. This is where JavaScript should come in.

So here’s our improved HTML code:

<a href="http://example.com"   target="_blank">pop me up

By adding the “target” attribute we make sure that the link will open in a new window even if the user doesn’t have JavaScript enabled, although the window’s features won’t be set.

A note for accessibility aficionados#section7

Our events will not trigger exclusively for mouse users despite the fact that we’re only capturing “onclick”. “Onclick” is triggered by all forms of activation (e.g. pressing enter) on all popular browsers. (It works pretty much the same way as Mozilla’s XUL’s oncommand.)

Implementation#section8

Now that our HTML is tidy, let’s get our hands dirty with the JavaScript implementation. I’ll spare you the simple one-liner (that would be a dull window.open wrapper) and provide you with some full-featured pop-up handling code:

var _POPUP_FEATURES = '
  location=0,
  statusbar=0,
  menubar=0,
  width=400,
  height=300
';function raw_popup(url, target, features) {
  if (isUndefined(features)) {
    features = _POPUP_FEATURES;
  }
  if (isUndefined(target)) {
    target = '_blank';
  }
  var theWindow =
    window.open(url, target, features);
  theWindow.focus();
  return theWindow;
}function link_popup(src, features) {
  return
    raw_popup(src.getAttribute('href'),
    src.getAttribute('target') || '_blank',
    features);
}

There’s a lot of stuff here, so let’s take a closer look:

raw_popup is a fancy “window.open” wrapper with a couple of things to make our life easier:

  • Better defaults: if no target window is provided, it’ll default to _blank. If no features are specified, it’ll default to the global constant _POPUP_FEATURES.
  • Focus: if the target window is already open but in the background, it’s brought to the front. The target window may be open in the background if we are using named windows (by specifying a target other than _blank), and sending more than one link to the same target. It’s good usability to focus the window because otherwise the user would get no apparent feedback for clicking the link.

link_popup was created specifically to open new windows from links. It grabs the URL from the anchor tag’s href attribute and optionally grabs the target from the target attribute. If the latter is not provided, it’ll default to _blank. This makes the function compatible with strict documents while allowing usage of named target windows. You should always specify a target in the HTML code when using a transitional DOCTYPE, to aid users without JavaScript. It’s good practice to write functions that return something, and nothing is more natural than returning the newly opened window on both cases, same as “window.open” does.

The isUndefined function in the code above is a function from my personal library. We’ll use a lot of code from this library hereinafter, and a trimmed-down version is provided at the end of the article and documented in the source. I find it very useful and I hope it aids your future scripting beyond simply popping up windows.

If all you need to do is pop up a couple of windows on a page or two, then this should be more than enough for you. If you plan to make heavy use of them (e.g. for an image gallery or maybe a CMS) and are not intimidated by JavaScript or the DOM, please read on.

Separate logic and presentation#section9

You probably hear it more than you’d like to: it’s important to separate logic from presentation. It’s true. In addition to a warm, fuzzy feeling, it gives you code that is easier to maintain.

So let’s take a look back into that HTML. There’s something in it that doesn’t belong there. What could possibly not belong to HTML? JavaScript! Out with the onclick thingie:

<a id="my-popup-link"
  href="http://example.com"
  target="_blank">pop me up

Now we must add event listeners to the “a”s that will pop up. To attach the event listener via JavaScript we add the following code to our script. Our script should be in the document head (either in an external script or in a script block):

function event_popup(e) {
  link_popup(e.currentTarget);
  e.preventDefault();
}listen('load', window, function() {
  listen('click', 'my-popup-link', event_popup);
  // ... other onload statements
  }
);

If we want to add an event listener to an element in the document, the element must be present in the browser’s internal document tree. When the document is finished loading, it triggers an event, load, on the window object. At this point, we’re sure the tree is complete and all elements are present. Here, we use listen to assign a function to this event, which will then call listen to safely assign event handlers to document nodes.

Listen is a cross-browser implementation of the DOM’s addEventListener(). It takes three parameters: event, elem, and func. To determine elem, it uses getElem, also from my library. getElem is a shorthand for document.getElementById with a special feature: it can take the element’s id, or it can take the element itself. It returns the element on both cases. Thus, elem can be either a string or an Element.

You’ll notice that we’re calling e.currentTarget and e.preventDefault, which IE doesn’t implement. A very useful feature of listen is that, in IE, it’ll wrap func and attach this wrapper to the event. This wrapper calls func passing it a faux Event object which will mimic these two features for IE.

Going wild with the DOM#section10

Adding a listener to an element via id is ok when you have very few links to pop up. If you have lots of links, it gets boring to code each listener one by one. For that reason, I present you with mlisten.

mlisten is the same as listen, but it takes an element list instead of an element, and adds the listener to every element on the list. The list can be a NodeList, or an Array of id strings and/or Element objects.

Here’s an example of using mlisten to pop up all links in a list:

mlisten(
  'click',
  document.getElementById('my-link-list').»
  getElementsByTagName('a'),
  event_popup
);

If your links are not structurally grouped, you can select them by their class using my function getElementsByClass(className [, tagName [, parentNode]]):

mlisten(
  'click',
  getElementsByClass('popup','a'),
  event_popup
);

The function handles elements with multiple space-separated classes well, so you can safely use one class to style the element and another class to denote behavior.

Remember, these calls should be made on the window’s onload event.

One small problem#section11

By using listeners, we’re unable to provide parameters to the functions. That keeps us from passing in individual window features to each link. Right? Not quite. Here’s a quick solution:

function event_popup_features(features) {
  return function(e) { 
    link_popup(e.currentTarget, features); 
    e.preventDefault(); 
  }
}// onload…
listen(
  'click',
  'some-link',
  event_popup_features('width=300,height=800')
);

There’s an important distinction to make here. Say fn is a function. When you write fn you have a reference to fn. When you write fn() you are calling fn and you have whatever it returns.

Here we’re using a function that returns a function. We’re not assigning event_popup_features to the event, we’re calling it and assigning the function that it returns. This function is like a personalized version of event_popup that takes into account the features we provided when calling event_popup_features.

The source#section12

That pretty much sums it up. Now that you know more ways to pop up a link than you ever wanted or needed to, feel free to grab the source code and the examples:

About the Author

Caio Chassot

Caio Chassot is a a web designer/developer and architecture student living in São Paulo, Brazil. He writes about code and design in his weblog.

130 Reader Comments

  1. I think I see the problem with my modification of your code in Opera – I used mlisten but contrary to your example (with features), I used class rather than id. I would like to use mlisten combined with class because id shouldn’t be repeated on a page and I have a planned use for this popup which could be used multiple times on a page.

  2. I think I see the problem with my modification of your code in Opera – I used mlisten but contrary to your example (with features), I used class rather than id. I would like to use mlisten combined with class because id shouldn’t be repeated on a page and I have a planned use for this popup which could be used multiple times on a page.

  3. I think I see the problem with my modification of your code in Opera – I used mlisten but contrary to your example (with features), I used class rather than id. I would like to use mlisten combined with class because id shouldn’t be repeated on a page and I have a planned use for this popup which could be used multiple times on a page.

  4. Julian, I’m not sure what you mean. In the example page I use mlisten with classes:

    mlisten(‘click’, getElementsByClass(‘popup’,’a’), event_popup)

  5. One aspect of accessibility was overlooked and I haven’t seen reference to it in this forum – alerting the user that a popup link will open a new window. I tried to modify popup.js by adding title to the raw_popup function:

    function raw_popup(url, target, title, features) {

    and

    if(isUndefined(title)) title=’Link opens in new Window’;

    and

    var theWindow = window.open(url, target, title, features);

    and title to the link_popup function such as after ‘_blank’

    ,src.getAttribute(‘title’) || ‘Link opens in new window’,

    but I think I am missing something because I am not seeing the tooltip of with the title text. Maybe this is not possible, I don’t know.

    If anyone has a solution, please contact me at julian dot rickards at ndm.gov.on.ca.

  6. I think a better way to warn the user is to provide a small image as an indicator by the side of the link. That could be achieved with a[target=”_blank”] selector (of course not working in IE), or, if we’re using the popup class, with a.popup

    To use the title though, I don’t think the popup functions are the appropriate place to pass the attribute value. A better way would indeed be in the html itself , or, if it should be done by script, then it should be done by the time we assign the event handlers.

    So, say we have an array or NodeList of popup links called popup_list, onload we’ll have the following code:

    mlisten(‘click’,popup_list,link_popup);
    map(popup_list,function(a){
    a.title=’opens in a new window’});

  7. why not this code (as explained in page 3):

    a[href^=”http”] {
    padding-right: 10px;
    background-image: url(external.gif);
    background-position: right !important;
    background-repeat: no-repeat;
    }

  8. Got it!!

    Two variations:

    Variation 1:

    Adds the title attribute where anchor tags have class=”popup”.

    onload = function()
    {
    var i = 0, thislink, poplinks = getElementsByClass(‘popup’,’a’);
    while (thislink = poplinks[i++]) {
    thislink.setAttribute(‘title’, ‘Link opens in new window’);
    }
    }

    Variation 2:

    Adds additional text where anchor tags have class=”popup”.

    onload = function()
    {
    var i = 0, thislink, poplinks = getElementsByClass(‘popup’,’a’);
    while (thislink = poplinks[i++]) {
    var title_text = document.createTextNode(” [Link opens in new window]”)
    thislink.appendChild(title_text);
    }
    }

    Now I am going to try to see if these functions may be built into Caio’s lib.js

  9. Julian, I’d advise against directly setting window.onload, due to imminent script clashing.

    A better way to ensure script interoperability and concurrency would be to use listeners, by using the DOM’s “addEventListener”, or my x-browser wrapper “listen”. This way many scripts may assign different handlers to the same event.

    On an unrelated note and a matter of personal style, I’d suggest that you check out my “map” function, as I find it a much cleaner means to traverse a list and do something to it than for or while loops with an increment counter.

  10. I appreciate your comments Caio. I am for the most part, just a newbie at JS but I am able to understand what I have done. I use the word “I” loosely because it was with some help from another member of SitePointForums.com that I was able to put this together. The other person wrote most of the code and left it for me. I then tried it, found it didn’t work, using the Moz JS console, I deduced the problem, made the fix and offer it. If I have a look at your code (your skills are much greater than mine), I would go dizzy. However, what I/we have done is added a functionality (however poorly) that I felt was missing from your excellent application. As I have mentioned before, if you would like to take what I have done and incorporate it into yours however you feel it should be done, please do so.

    I personally would feel much more comfortable if either the title text or additional link text were added to the application. It fits in much better with checkpoint 10.1 of the WCAG which says (from my memory) that either don’t use popups or open new windows or if you do either, warn the user. Nevertheless, friends of mine argue against any target attribute or JS popups but others are not quite so opposed. For those, including myself, who take the more lenient approach, I feel that this one inclusion now raises this application up to the bar set by 10.1 of the WCAG.

  11. One more thing. I understand what you mean by event listener and most certainly, I can see how it applies to your original code: listen for a click, then run code. However, can the event listener be told to listen to onload? In the case of my first snippet of code, onmouseover would be sufficient to generate the title attribute because you can’t see the title text until the mouse is over the link text (in addition to onmouseover, onfocus should be accounted for too). However, in the second case, the modification of the link text would have to occur at load time.

  12. Julian, you can add a listener to onload, in fact, I do it in the article when I first mention my listen function under the “separate logic and presentation” section:

    listen(‘load’, window, function() {
    listen(‘click’, ‘my-popup-link’, event_popup);
    }
    );

    This listen block sets a function to be called when the document is loaded, which will then call listen again, this time to add the listener to a popup link.

    You probably could use the following code to add the title attribute:

    listen(‘load’, window, function() {
    popup_list = getElementsByClass(‘popup’);
    mlisten(‘click’, popup_list, event_popup);
    map(popup_list, function(a){a.title=’opens in a new window’});
    }
    );

    Regarding adding this functionality to the script core, it’s such a one-liner that I don’t see how it could be “integrated” into it, or how it would be advantageous, specially since I don’t provide specific means to add the popup handlers (we use the general-purpose listen), and thus cannot modify it.

    I guess you just type it when you need it. Or, well, we could just wrap the code I provided above in a function called add_listener_and_title_to_popup_class_links or something shorter.

  13. I joined this late… and I’m a marketer, not a site builder – so I get confused easily and I don’t pretend to grasp much of this.

    I’m going back for a re-read to better understand.

    Meantime, I’m looking for some practical clarity.
    Advice appreciated. Please and thanks.

    At risk of drifting off subject…

    As a marketer I need to advise clients appropriately.

    In liasing with their developers/staff, I often encounter ‘existing practices’ which are ‘perhaps not optimal’. An easily accepted simple example? …some enthusiastically use frames/tables. (Sadly, this includes webdevelopers delivering substandard service – either knowingly or through simple lack of awareness).

    My ‘problem’ is that as a sole practitioner, I have to cover a lot of ground – too much to have all-areas expert knowledge. There’s much I don’t understand and, in efforts to develop a working awareness, I spend a lot of time here – frequently getting lost in tech stuff way beyond my competence and interest.

    I got lost in this one very early on – and even more confused by 12 pages of follow-on. As I said ‘I’m going back for a re-read to better understand.’ And meantime, I want to know what to do – and no tpass-on poor advice.

    I tried the examples at http://www.alistapart.com/d/popuplinks/examples.html

    When clicked normally (on my aging IE5/OS9/Mac)…

    1-3 open new small window.
    4-5 open in main window.
    6-9 open in new full size window.

    When holddown/right click (intentionally open in a new window)…
    1 blank new full size window
    2-3 new small popup.
    4-9 new fullsize window.

    I’m guessing they’ll behave ‘better’ with more modern browsers on my OSX-equpiped laptop.

    Thing is though – only 2 and 3 work on a machine/browser combo still in common use. Do I use them and ignore the others?

  14. gulliver, apparently IE5/OS9 is having a problem with some code of my library that is relevant in setting up event listeners (perhaps it doesn’t support them at all, but I really can’t tell)

    So the short answer is indeed, stick to examples 2 and 3 if the popup behavior for IE5/OS9 is important.

    The long answer is that you can either get a coder to debug my library and find out what in it IE5/OS9 doesn’t like, or you can treat IE5/OS9 as an aging browser and let it fall back on the sub-optimal behavior that you describe: loading on a normal size (either new or same) window.

    Notice that letting this happen does result in an accessibility issue since the documents still open, even if not in the desired popup fashion.

  15. >treat IE5/OS9 as an aging browser and let it fall back on the sub-optimal behavior that you describe…

    Thanks, C. That’s sound advice and something I’m increasingly accepting.

  16. Hi:

    Sorry to burst your bubble, but this script is partly broken in Opera as I had first suggested but I couldn’t see the pattern. From what I was told after asking around, it is a built in permissions thing that Opera is more strict about and other browsers seem not to be as strict. Set a popup link to open a page from a different domain and both the popup and the main window will go to the destination.

    I don’t know JS so I don’t know if there is a fix. However, as long as you are creating popups from pages within your domain, you won’t see this problem.

  17. As a person who consistantly searches news feeds from a variety of sources, some using popups others not, I generally open new windows to view information.

    It is heartening to see people advocating pop up code that will allow me to do my traditional ‘open in new window’ actions on links that use javascript.

    Nothing more frustrating than opening the new window, having it fail, shutting that window down and clicking on the link normally.

    Here’s hoping everyone takes this up.

  18. Gulliver/Caio, some information i found concering event listeners and IE5/OS9 at http://developer.apple.com/internet/webcontent/eventmodels.html

    “If you plan to support IE5/Mac, you can dismiss the attachEvent() and addEventListener() methods because IE5/Mac supports neither of these.”

    There is only on way left to apply a function to an event (beside coding in the document structure):
    element.onclick = myFunc;

    I extended Caios listen function:

    function listen(event, elem, func) {
    elem = getElem(elem);
    if (elem.addEventListener) // W3C DOM
    elem.addEventListener(event,func,false);
    else if (elem.attachEvent) // IE DOM
    elem.attachEvent(‘on’+event, function(){ func(new W3CDOM_Event(elem)) } );
    // for IE we use a …
    else // IE5/OS9 elder browsers
    eval(“elem.on” + event + “= func”);
    }

    The examples all work fine now.

    Thanks a lot for your great article and the cclib.js!

  19. michael, actually the thing (handling ie5/os9) is a bit more complicated than that: the function handling the event will expect an event object, but when a function is assigned to handle an event via “element.onevent = fn” it element itself will be passed as parameter to the function, so we must use the same wrapper we used for IE/win:

    elem[‘on’+event] = function(){ func(new W3CDOM_Event(elem)) }

    (also see there’s no need to use eval)

  20. This was an excellent article, which demonstrated a lot of very usefull techniques, especially the listener model.

    And, yes, I’m one of those users who usually opens up links in new windows and gets REALLY frustrated when some lame webdeveloper has javascript’ed their links.

    Thanks again

    Terry

  21. Well, I came to this article hoping to find a way to stay XHTML 1.0 validated, but open offsite links in a new window (can’t be validated with target in your links) saddly, you can’t be validated with onclick in your links, either… anyone know of any other solutions?

  22. Haven’t tried this way yet. I’m interested in what happens, when you click on this right-way-made popup link with popups banned in your browser (such as Mozilla or some commercial popup-blocking proxies for IE).

    Does it open at least the normal href link?

    In my opinion, popus should not be used, unless necessary, and should be announced in advance (with some icon like for those abroad-targeting links). For example, it’s nice to use popups for internet radios’ “Now playing” windows.

    User just should now that it is a popup.

Got something to say?

We have turned off comments, but you can see what folks had to say before we did so.

More from ALA

I am a creative.

A List Apart founder and web design OG Zeldman ponders the moments of inspiration, the hours of plodding, and the ultimate mystery at the heart of a creative career.
Career