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.

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

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:
- 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. - 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:
- Not use hash links but use URL parameters instead (no jump downs).
- 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:
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()
.
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 =)
Fixed, and burying comment since irrelevant now. Thanks!
Chris, check the tab variable:
or just create a new variable:
I see no reason why you couldn’t get the initial value of the url-param from javascript instead of using php. Especially since this should be in external js files ;)
I stumbled on this snippet the other day, it might be usable: http://stevenbenner.com/2010/03/javascript-regex-trick-parse-a-query-string-into-an-object/
Maybe you could consider having the tab plugin do this stuff on init, and maybe having the url query be like this:
?tabId=tab-1&tabIdTwo=tab-2
Where tabId and tabIdTwo are the #ids of the elements where the tab plugin is run? That way you can have multiple tab controls on a page, and still having the url thing work :) Just an idea ;)
A side note: please try to always write secure code even if it’s just a proof of concept. People will come and copy&paste your code without understanding the security implications. (Anton, the same goes for your code.)
Try this:
here’s some xss for you
Another reason not to use php for this, when js is just fine :)
The only reason to use server side scripting for this, is if the content of the tabs was loaded with ajax, and you want to have the selected tab’s content shown in the code from load, for SEO purposes. But the selection of the tab itself could still be done in js.
Jeppe, I agree.
Regarding XSS, plain PHP is quite a nightmare. Of course, one can fuck up things with Javascript, too. :)
I got rid of all the PHP and made it pure JavaScript. Thanks to Jeppe for that nice function to make getting the query string easy.
Of course, just using Javascript instead of PHP (or other server-side technology) doesn’t save you from XSS problems. I think that this example would be vulnerable against a similar attack if it was using just a month-old version 1.6.2 of jQuery.
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.
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?
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.
But, and the others browsers that don’t support
history.replaceState()
?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.
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 usedata-
attributes?Tabs work great amazing job!
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
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 :(
I did mention that.
Its not working in opera browser, then what we do?
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.
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.
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.
You can replace the
new RegExp
constructor with a literal:/([^?=&]+)(=([^&]*))?/g
, which, IMHO is much cleaner than using the constructor.Good stuff
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
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.