Better Linkable Tabs

Avatar of Chris Coyier
Chris Coyier on (Updated on )

I find it’s a common desire with “tab” design patterns that there is a way to link to specific tabs. As in, you could give someone a URL and that URL would lead them to that page with the specific desired tab active and displaying the correct content.

If each tab is a completely different URL, it’s easy. In the HTML that is served, the appropriate class name is applied to the tab to make it look active, and the appropriate content below is served.

Wufoo uses a tab design pattern inside the app. Each tab is a entirely different page and URL.

If these tabs are “same page” tabs that hide/show different panels of content instantly (or via Ajax), it’s a bit harder. A common (and I’d argue semantic) approach to this has been with hash links, e.g.:
Tabs like these are likely “same page” tabs that don’t have unique URL’s to themselves

With CSS3’s :target pseudo class selector, there are ways to make functional tabbed areas with CSS alone (no JavaScript). However, if pure CSS tabs are the goal, this is better.

As much as I’ve experimented with CSS techniques, I think functional tabbed areas are best accommodated by JavaScript. This is functionality territory, and if we adhere to the traditional model of separation of concerns, it should be handled by JavaScript.

Hash links have a few other problems:

  1. When the page loads with a hash link or the hash links changes, the browser will scroll down so that the element with the ID of that hash is at the top of the page. There is a good chance this is not desirable. The latter is easy to fight with preventDefault(). The former is nearly impossible to deal with cleanly.
  2. Changing the hash tag of a page adds an entry to the browser history, so pressing the back button will go back through previous hashes. There is also a good chance this is not desirable.

Let’s solve both problems while accommodating the desire to have a way to link to a specific tab. This is what we are going to do:

  1. Not use hash links but use URL parameters instead (no jump downs).
  2. Use the ultra-hip history.replaceState() so we can change the URL without affecting the back button.

Our URL’s will be like:

Rather than re-write JavaScript based tabs from scratch, let’s use my existing Organic Tabs demo. We need to adjust very little of the plugin to make this work. We’ll add a param parameter so people can choose whatever they want there.

$.organicTabs.defaultOptions = {
    "speed": 300,
    "param": "tab"

Then in the part with all the tab-changing functionality, we’ll add this one line:

// Change window location to add URL params
if (window.history && history.pushState) {
  // NOTE: doesn't take into account existing params
	history.replaceState("", "", "?" + base.options.param + "=" + listID);

The tricky part is that we need to pull in the URL parameter when the page loads and do JavaScript stuff with it. That means we’ll need to use a server-side language, and intermingle some of it with our JavaScript. Here I’ll use PHP:

Update: We can access the query string entirely through JavaScript, and parse out the parts easily. So instead of the PHP that used to be here (not ideal for many reasons) we’ll do this instead:

var queryString = {};
    new RegExp("([^?=&]+)(=([^&]*))?", "g"),
    function($0, $1, $2, $3) { queryString[$1] = $3; }

if (queryString[base.options.param]) {

	var tab = $("a[href='#" + queryString[base.options.param] + "']");

	$("#" + queryString[base.options.param]).show();

This code grabs that URL param and make sure the current tab is highlighted and the correct content is shown.

Functional demo:

View Demo   Download Files



Browser support for history.replaceState (and all the history management stuff) is far from ubiquitous. I’m using a basic feature test here, but not implementing any fallback. The important part, the tab functionality, still works fine. You might wanna do more.

I’m also not taking into account existing URL parameters. If your page is already using them and you also want to use this (e.g. ?foo=bar&tab=tab-one) this would get a bit more complicated. You’d probably snag the entire URL param string and pass it into the plugin. Then parse it apart and re-insert the existing params when you use replaceState().