Grow your CSS skills. Land your dream job.

Better Linkable Tabs

Published by Chris Coyier

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.:

http://example.com/#tab-three

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:

http://example.com/?tab=tab-three

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 = {};
window.location.href.replace(
    new RegExp("([^?=&]+)(=([^&]*))?", "g"),
    function($0, $1, $2, $3) { queryString[$1] = $3; }
);

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

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

	tab
		.closest(".nav")
		.find("a")
		.removeClass("current")
		.end()
		.next(".list-wrap")
		.find("ul")
		.hide();
	tab.addClass("current");
	$("#" + 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

Video:

Incomplete-ish

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().

Comments

  1. spawn
    Permalink to comment#

    Hey man, you got two links “View demo” there.
    Second one must be “Download Files” i guess.

    sorry for my bad english
    reading you for last 3 years
    cheers =)

  2. Permalink to comment#

    Chris, check the tab variable:

    $("#<?php echo $_GET['tab']; ?>").show();

    or just create a new variable:

    var tabId = '<?php echo isset($_GET["tab"]) ? $_GET["tab"] : "" ?>';
    var tab = $("a[href='#"+tabId+"']");
    $("#"+tabId).show();
  3. I liked the idea you used for back button, the idea of history.replaceState();. I didn’t know about it. However, I don’t think that having hash links (fragment identifiers in links) is a bad idea from the point of user experience. Stack Overflow has used this technique in many places, and I haven’t seen a complaint about it at almost any place.

    I’m gonna ask a question about this at User Experience from Stack Exchange network.

    Thanks.

  4. Permalink to comment#

    In Google+, tabs load almost instantly (like they’re part of the same page) but when I click on other tabs the URL changes instantly to another and it doesn’t involve parameters.

    Example:
    I’m at plus.google.com/103982534066/about
    Then, I click on the ‘Pictures’ button.
    Then the URL instantly changes to plus.google.com/103982534066/pictures and some pictures are visible (the tab gets ‘activated’ instantly).

    And I don’t have some 400 Gbps connection.

    How is that done?

  5. S_gonzales
    Permalink to comment#

    normallly for that i just go jquery and do a addClass // removeClass function onClick at least is how i did here at my job in where the devs hate php, lol.

    not sure if i should do in some other way or if there’s any reason for me do differently.

  6. Jonathan [JCM]
    Permalink to comment#

    But, and the others browsers that don’t support history.replaceState() ?

  7. Permalink to comment#

    I love the way you revisit topics over and over. It’s how I learn as well, and I really appreciate your process.

    You mentioned that a problem with the has solution is that the browser will scroll down so that the element with the ID. I found that if I use a shebang #! that prevents the scrolling, a second benefit is that it provides groundwork for google friendly ajax tabs.

    • Scott
      Permalink to comment#

      You can use a hash link that is *not* the ID of the tab. For example you could only put an ID on the wrapper element like tab-list then use a hash link of #tab-list-3 for the 3rd tab. Or maybe use data- attributes?

  8. Permalink to comment#

    Tabs work great amazing job!

  9. Permalink to comment#

    Putting the querystring into the page using Javascript will still open the page to the use of a self referenced iframe XSS attack as listed in the document below. That being said, the problem is one that has to be fixed at the browser level, not at the site level.

    https://sitewat.ch/files/Bypassing%20Internet%20Explorer's%20XSS%20Filter.pdf

  10. Permalink to comment#

    Again a great Tutorial, thank you.

    But i think it is important to mention that all curent IE don’t support the history.replaceState() and therefore this solution is only meaningful to a limited extent for professional websites.

    IE10 will support the function… but if i consider how many users still browse my sites with IE7, I will have to wait another 10 years to use it :(

  11. Its not working in opera browser, then what we do?

    • Permalink to comment#

      You must have done something wrong sir, using Opera and it works just fine.

      As far as this goes, I still need to find good use for this great replacement, maybe using it within internal menu, still got no idea on how to submit it for site use,

      Would like to hear your ideas on how to use this feature.. beside given options in tutorial.

  12. Permalink to comment#

    So, with using the history.replaceState(), would it track in analytics which tabs were viewed? I don’t think the #tag is recorded as a seperate page in GA (maybe I just haven’t found it yet). Because thinking about it from a data recording stand point, tabs aren’t good if you can’t track which tabs people viewed.

  13. I stumbled into this post by accident.

    I was originally looking for something else and the article was so informative that I went to your home page to see what else you were talking about. I was excited to find your helpful information on linkable tabs – there seems to be very little written about it, at the moment.

    One issue that I struggle with: form vs. function. http://www.example.com/#reports just “feels” better to me than “http://www.example.com/?tab=reports – especially from an end-user perspective.

    Never-the-less, great article and site in general.

  14. You can replace the new RegExp constructor with a literal: /([^?=&]+)(=([^&]*))?/g, which, IMHO is much cleaner than using the constructor.

  15. Jim
    Permalink to comment#

    Good stuff

  16. Permalink to comment#

    I’m itching for the day we can do this with CSS alone.

    But it’s still jQuery for me at the moment…..

    Thanks,
    David

  17. Aamir
    Permalink to comment#

    hi,
    I was searching for a good tab related topic and saw this one which is great.
    First of all thanks for creating such a great and simple jquery tabs.
    Secondly I want to ask a question.
    Sorry for my ignorance.
    I used 2 tabs in one page and now I want to use 3 tabs in other page but the problem Im facing is the width defined in css.
    On first page (2 tabs) I used width 380px
    On 2nd page (3 tabs) I want to use width 295px.
    I wanna use example one but Im not sure how will I apply it.
    Can anyone please help me out regarding this?

    Thanks in advance.

This comment thread is closed. If you have important information to share, you can always contact me.

*May or may not contain any actual "CSS" or "Tricks".