Create a TAB Interface with Browser History and URL support

Written by Kevin Liew on 11 Oct 2011
54,577 Views • Tutorials


Today, we are going to build a TAB interface. Well, not your ordinary TAB interface, this version come with history support. Yes, you can use Back and Forward button in your browsers, and also it has an URL for each TAB as well.

Sounds complicated? Don't worry, I always make sure my tutorial is simple and quick. We are going to use a plugin called jQuery Address plugin.

With jQuery Address plugin, we can produce virtual addresses that point to a website section. As a result, each TAB content in this tutorial can be bookmarked in a brwoser or social website, or can be sent via email or instant messenger. It also works with browser history and reload button. Tested with IE7, IE8, IE9, FF, Chrome and Safari.


Basically, we have two sections of HTML.

jQuery TAB Interface with History Support Tutorial
  • #TABS: This section contains TAB items just like a navigation menu. You can set the page title by changing the TITLE attribute of Anchor Tag.
  • #PANELS: This section contains content of each Panel, just like pages. The sequence of the panel must match with the item in TAB. For example, first Tab item displays first panel, second Tab item displays second panel and so on.
<div id="tabs" class="clearfix">
		<li><a href="#tab1" title="Tab1 Title">Tab 1</a></li>
		<li><a href="#tab2" title="Tab2 Title">Tab 2</a></li>
		<li><a href="#tab3" title="Tab3 Title">Tab 3</a></li>

<div id="panels">
	<div class="panel-wrapper">
		<div class="panel">
			<h2>Panel 1</h2>
			<p>Nullam in dui mauris. ......</p> <p>
			<a href="#tab2">Go to Tab2</a></p>
		<div class="panel">
			<h2>Panel 2</h2>
			<p>Nullam in dui mauris. ......</p><p>
			<a href="#tab3">Go to Tab3</a></p>		
		<div class="panel">
			<h2>Panel 3</h2>
			<p>Nullam in dui mauris. ......</p><p>
			<a href="#tab1">Back to Tab1</a></p>		
</div> <!-- //#panels -->


A simple CSS to style up the TAB interface. You can change whatever you want, and selected Tab item is assigned an "active" class.

body {

a {

#container {
	margin:0 auto;

#tabs ul {

	#tabs ul li {
	#tabs ul li a {
		padding:5px 10px;
	#tabs ul a {

#panels {
	#panels .panel-wrapper {

	#panels .panel {

	#panels .panel h2 {
		margin:0 0 10px 0;	

.clearfix:after {
	visibility: hidden;
	display: block;
	font-size: 0;
	content: " ";
	clear: both;
	height: 0;
* html .clearfix             { zoom: 1; } /* IE6 */
*:first-child+html .clearfix { zoom: 1; } /* IE7 */		

* CSS3 Styling

#tabs ul li a {	
    border-top-left-radius : 5px;      
    border-top-right-radius : 5px;      		
#panels {
    border-bottom-left-radius : 5px;      
    border-bottom-right-radius : 5px;      	


Thanks for the $.address plugin, it makes this tutorial less complicated. Basically, we have a function called setPanel that choose the correct Tab item and panel. If there isn't hashtag, this function will set everything to default, which is the first Tab item and first panel. Lastly, this function is attached to $.address plugin's init() and change() events.

To ensure you understand how exactly it works, I have put inline comment to explain what it does for each line of code.

var QTABS = {

	init: function () {
		// attached onload and change event to address plugin
		$.address.init(function(event) {
			// first load, set panel
		}).change(function(event) {

			// if the url changes, set panel

	// the core function to display correct panel
	setPanel: function (event) {
		// grab the hash tag from address plugin event
		var hashtag = event.pathNames[0];
		// get the correct tab item, if no hashtag, get the first tab item
		var tab = (hashtag) ? $('#tabs li a[href=#' + hashtag + ']') : $('#tabs li:first a');

		// reset everything to default
		$('#tabs li').removeClass('active');
		$('#panels .panel').hide();

		// if hashtag is found
		if (hashtag) {
			// set current tab item active and display correct panel
			$('#panels .panel:eq(' + (tab.parent().index()) + ')').show();			
		} else {

			// set the first tab item and first panel				
			$('#tabs li:first').addClass('active');
			$('#panels .panel:first').show();			
		// change the page title to current selected tab
		document.title = tab.attr('title');


// Execute this script!


That's it, quick and easy. I enjoy create my own jQuery script, but sometimes it just not reasonable to reinvent the wheel. In this case, I used address plugin to speed up the development process. I hope you learn something from this tutorial and found this script useful for your project. If you have any question, drop me a comment. :)

Demo Download
Join the discussion

Comments will be moderated and rel="nofollow" will be added to all links. You can wrap your coding with [code][/code] to make use of built-in syntax highlighter.

ikkez 13 years ago
nice tutroial. but isn't it preferable to use a definion list in the html part for better semantic structure? i thought about something like <dl><dt>tab title</dt><dd>tab content</dd>...</dl>
Kevin Liew Admin 13 years ago
Well, depend on what are you trying to do. Tab interface usually contains complex html structure in each panel. So, DIV would be the best. If you want to make FAQ, Q&A , glossary section, I guess DL, DT and DD would be the best. :)
Fanus 13 years ago
Correct me if I'm wrong. This method isn't best for making your website SEO friendly. As far as I know, using # in your URLs mean that you use JavaScript AJAX and search engines do not read JavaScript. I might be wrong. I'm working on something similar to this and I went the SEO friendly route. Perhaps someone can provide me with a SEO friendly and Bookmarkable AJAX solution?
Kevin Liew Admin 13 years ago
Hi Fanus, not in this case though :) This is something different, all content need to be populated, so when you switch between tabs, it's just hide and show, no ajax call. Basically, all content has already loaded but hidden by javascript, so SEO won't be an issue at all.
felipe 12 years ago
I can connect the database tab with forms
Andrew Mof 12 years ago
Litterly just googling this, is there anyway I can modify your code to load ajax content yet retain the same fucntionality? I would like to be able to use the path in the href rather than javascript...
Kevin Liew Admin 12 years ago
Hi Andrew, yes, it will still work. To show the content, we match the hash tag against the value of the href value, and then show the correct content based on the LI index. You can try, I'm pretty sure it will work.
LA 12 years ago
This s exactly what I was looking for. Is it possible to add an effect so that the tabs open more smoothly?
Kevin Liew Admin 12 years ago
Smoothly, I guess you want some animation effect, you can add SlideUp, SlideDown effect:

Line 31: $('#panels .panel').stop().SlideUp('500');
Line38: $('#panels .panel:eq(' + (tab.parent().index()) + ')').stop().SlideDown('500');
LA 12 years ago
Yes, I am looking for an animated effect. I tried replacing lines 31 and 38 with what you suggested but that didn't do anything.
Peter 12 years ago
That's just what I need. But shouldn't there be named anchors like <a name="tabs1"></a> for the link <a href="#tab1">Back to Tab1</a> to work when javascript is disabled?
Kevin Liew Admin 12 years ago
Good idea.
Phillip 12 years ago
I'm having problems trying to get each tab / panel to open up link via a frame.

The script works fine when I load the page by itself However if I try and link to the page / tab via a frame it doesn't open up the tabs.

If you go to stockists and click on the drop down options you'll see what I mean.

Any ideas?

Great tab script btw ;)
Matt 11 years ago
This works great. I'm using the Chrome Google Analytics Debug app and It appears that on the intial load of the page, it fires of 2 page views to google analtyics. So if you intally load this url it fires a page view to google for both /resources/html/jquery-tabs-interface/ AND /resources/html/jquery-tabs-interface/tab2. Is there a way to prevent this incorrect first page view?
Kevin Liew Admin 11 years ago
hmmm, unfortunately, with hashtag, that considered as a different link as well in GA.
Daniel 11 years ago
Seems like it's not working on the latest jQuery packages.