This website uses cookies to personalise ads and to analyse traffic ok
web design

HTML Tabs: Complete tutorial and plugin

Detailed tutorial on creating, styling and scripting your own HTML/CSS tabs, while making code re-usable creating a lightweight jQuery plugin.

The following post is a step by step, in-depth tutorial, containing the actual code in great detail and explanation for each step in the development process. In short, it’s pretty long. If you just want to grab the plugin and use it right away, skip to the next page.

HTML tabs tutorial

Our objective is to create cross-browser HTML tabs and automate implementation as much as possible, so we can easily re-use the code on multiple pages and/or elements. We’ll go for a perfectly clean markup, pure CSS for styling and scripting using the jQuery library. We’ll use jQuery (instead of pure CSS or vanilla javascript) cause we can do all the cool stuff with minimum coding and ultimately, create a proper plugin that’s easy to implement on any project 😉

The markup

Tabs markup should be as minimal as possible and pretty straight forward:

<div class="tabs">
  <ul>
    <li class="active-tab">
      <a href="#tab-1">First</a>
    </li>
    <li>
      <a href="#tab-2">Second</a>
    </li>
    <li>
      <a href="#tab-3">Third</a>
    </li>
    <li>
      <a href="#tab-4">Fourth</a>
    </li>
  </ul>
  <div>
    <div id="tab-1" class="active-tab-content">
      <h2>First</h2>
      <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
    </div>
    <div id="tab-2">
      <h2>Second</h2>
      <p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
    </div>
    <div id="tab-3">
      <h2>Third</h2>
      <p>Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</p>
    </div>
    <div id="tab-4">
      <h2>Fourth</h2>
      <p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo.</p>
    </div>
  </div>
</div>

The tabbed area is wrapped by a div with class name “tabs” and consists an unordered list (ul) representing the clickable tab labels and a div containing tabs content. Each tab content resides within a div that has a unique id value and each list item (li) contains an anchor with href value the id of its content. Notice that we don’t give any classes to the unordered list or the div below it. Since all our tabs markup sits within .tabs, we can target them in our CSS and script without the need of additional class names or ids.

The active-tab and active-tab-content classes indicate the currently active/visible tab. In our markup, by adding those 2 classes to the first list item and the #tab-1 div, we make the first tab and its content visible. We could of course add those classes to any other li and div for having other tab visible on page load.

The CSS

Next, we need to transform the simple HTML markup to actual tabs. Add the following CSS rules:

.tabs{
  width:380px;
}
.tabs>ul,.tabs>div{
  position:relative;
}
.tabs>ul{
  overflow:hidden;
  margin:0;
  padding:0;
  list-style-type:none;
  z-index:2;
}
.tabs>ul>li{
  float:left;
  position:relative;
  margin:0 -1px 0 0;
}
.tabs>ul>li>a{
  display:inline-block;
  padding:5px 10px;
  background:#eee;
  border:1px solid #999;
}
.tabs>ul>li.active-tab>a{
  background:#fff;
  border-bottom-color:#fff;
}
.tabs>div{
  background:#fff;
  z-index:1;
  border:1px solid #999;
  top:-1px;
}
.tabs>div>div{
  position:relative;
  top:0;
  left:0;
  display:none;
  overflow:hidden;
  padding:10px;
}
.tabs>div>div.active-tab-content{
  display:block;
}

Nothing fancy here, just enough CSS to turn that plain list and divs to tabs. We “hide” tabs content by applying display: none on the divs and display: block; on the .active-tab-content which indicates the initially visible tab.

The tabs certainly look plain (or just ugly!) but we’ll make them pretty later. First, we need to make them work.

The script

We’ll write some simple javascript (more specifically jQuery) that’ll toggle .active-tab & .active-tab-content upon clicking a tab. What we want, is to remove those active classes from the currently visible tab (both from tab label and content) and add them to the one that’s being clicked.

First, include the jQuery library:

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8/jquery.min.js"></script>

The code above can be inserted inside the head tag or at the very bottom of the document, before the closing body tag (which is normally recommended for better performance). In either case, it’s more efficient to include the CSS before any javascript (usually inside the head tag). We’re using Google’s CDN to get jquery library (why?).

After jQuery inclusion, we can start coding the script that toggles tabs. We want our script to run on document ready (meaning the code will execute after the DOM is ready), so we start by adding:

<script>
    (function($){
        $(document).ready(function(){
            /* ... */
        });
    })(jQuery);
</script>

We’re wrapping our jquery code with (function($){ ... })(jQuery);. This ensures no conflict between jquery and other libraries using $ shortcut. See Using jQuery with Other Libraries for more info.

If your tabs content include elements with external sources (e.g. images, objects etc.) you should use window load ($(window).load()) instead of document ready, so the code executes after all page elements are fully loaded:

<script>
    (function($){
        $(window).load(function(){
            /* ... */
        });
    })(jQuery);
</script>

By this point, the full page markup will look like this:

<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<title>HTML Tabs</title>
<style type="text/css">
.tabs{
  width:380px;
}
.tabs>ul,.tabs>div{
  position:relative;
}
.tabs>ul{
  overflow:hidden;
  margin:0;
  padding:0;
  list-style-type:none;
  z-index:2;
}
.tabs>ul>li{
  float:left;
  position:relative;
  margin:0 -1px 0 0;
}
.tabs>ul>li>a{
  display:inline-block;
  padding:5px 10px;
  background:#eee;
  border:1px solid #999;
}
.tabs>ul>li.active-tab>a{
  background:#fff;
  border-bottom-color:#fff;
}
.tabs>div{
  background:#fff;
  z-index:1;
  border:1px solid #999;
  top:-1px;
}
.tabs>div>div{
  position:relative;
  top:0;
  left:0;
  display:none;
  overflow:hidden;
  padding:10px;
}
.tabs>div>div.active-tab-content{
  display:block;
}
</style>
</head>
<body>
<div class="tabs">
  <ul>
    <li class="active-tab">
      <a href="#tab-1">First</a>
    </li>
    <li>
      <a href="#tab-2">Second</a>
    </li>
    <li>
      <a href="#tab-3">Third</a>
    </li>
    <li>
      <a href="#tab-4">Fourth</a>
    </li>
  </ul>
  <div>
    <div id="tab-1" class="active-tab-content">
      <h2>First</h2>
      <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
    </div>
    <div id="tab-2">
      <h2>Second</h2>
      <p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
    </div>
    <div id="tab-3">
      <h2>Third</h2>
      <p>Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</p>
    </div>
    <div id="tab-4">
      <h2>Fourth</h2>
      <p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo.</p>
    </div>
  </div>
</div>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8/jquery.min.js"></script>
<script>
    (function($){
        $(document).ready(function(){
            /* ... */
        });
    })(jQuery);
</script>
</body>
</html>

Continue with the script by binding the click event on each anchor element inside the unordered list:

<script>
    (function($){
        $(document).ready(function(){
            var tabs=$(".tabs"),
                tabsLabels=tabs.children("ul"),
                tabLabel=tabsLabels.find("a");
            tabLabel.click(function(e){
                e.preventDefault();
            });
        });
    })(jQuery);
</script>

First, we define and cache our selectors in 3 variables: tabs, tabsLabels and tabLabel and then, we bind the click event on the anchor inside each list item which we’ve stored in tabLabel. Adding e.preventDefault(); inside the click function prevents the default browser behavior when a link is clicked (e.g. page jumping on the anchor point of the href value).

At first, it might seem unnecessary that we define all those variables and store every selector in one. We could certainly do something like this:

<script>
$(".tabs>ul a").click(function(e){
  e.preventDefault();
});
</script>

But we won’t. It’s actually good to make a habit of storing selectors in variables. Firstly, they’re always cached resulting in better performance (non-repeated jQuery selection operations) and secondly, you can easily access them from everywhere in your code, making much easier to change any selector.

What we need now, is to get the href value of the clicked anchor, find the equivalent tab content via its id attribute and store both in 2 variables: tabID and targetTab

<script>
    (function($){
        $(document).ready(function(){
            var tabs=$(".tabs"),
                tabsLabels=tabs.children("ul"),
                tabLabel=tabsLabels.find("a"),
                tabsContent=tabs.children("div");
            tabLabel.click(function(e){
                e.preventDefault();
                var tabID=$(this).attr("href"),
                    targetTab=tabsContent.find(tabID);
            });
        });
    })(jQuery);
</script>

We also define another variable: tabsContent, in order to cache it and access it later. We can now easily toggle .active-tab and .active-tab-content on the element we want:

<script>
    (function($){
        $(document).ready(function(){
            var tabs=$(".tabs"),
                tabsLabels=tabs.children("ul"),
                tabLabel=tabsLabels.find("a"),
                tabsContent=tabs.children("div");
            tabLabel.click(function(e){
                e.preventDefault();
                var tabID=$(this).attr("href"),
                    targetTab=tabsContent.find(tabID),
                    activeTab=tabsLabels.find(".active-tab"),
                    activeTabContent=tabsContent.find(".active-tab-content");
                activeTab.add(activeTabContent).removeClass("active-tab active-tab-content");
                $(this).parent().addClass("active-tab");
                targetTab.addClass("active-tab-content");
            });
        });
    })(jQuery);
</script>

What we do here is find .active-tab and .active-tab-content elements, store them in same-name variables and remove those same classes (via $.removeClass function). Then, we add .active-tab class to the (parent) list item that contains the clicked link and add .active-tab-content to the element we’ve stored in targetTab variable previously (using the $.addClass jQuery function).

This is the most basic script for fully functional tabs (live example). The complete page markup should be:

<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<title>HTML Tabs</title>
<style type="text/css">
.tabs{
  width:380px;
}
.tabs>ul,.tabs>div{
  position:relative;
}
.tabs>ul{
  overflow:hidden;
  margin:0;
  padding:0;
  list-style-type:none;
  z-index:2;
}
.tabs>ul>li{
  float:left;
  position:relative;
  margin:0 -1px 0 0;
}
.tabs>ul>li>a{
  display:inline-block;
  padding:5px 10px;
  background:#eee;
  border:1px solid #999;
}
.tabs>ul>li.active-tab>a{
  background:#fff;
  border-bottom-color:#fff;
}
.tabs>div{
  background:#fff;
  z-index:1;
  border:1px solid #999;
  top:-1px;
}
.tabs>div>div{
  position:relative;
  top:0;
  left:0;
  display:none;
  overflow:hidden;
  padding:10px;
}
.tabs>div>div.active-tab-content{
  display:block;
}
</style>
</head>
<body>
<div class="tabs">
  <ul>
    <li class="active-tab">
      <a href="#tab-1">First</a>
    </li>
    <li>
      <a href="#tab-2">Second</a>
    </li>
    <li>
      <a href="#tab-3">Third</a>
    </li>
    <li>
      <a href="#tab-4">Fourth</a>
    </li>
  </ul>
  <div>
    <div id="tab-1" class="active-tab-content">
      <h2>First</h2>
      <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
    </div>
    <div id="tab-2">
      <h2>Second</h2>
      <p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
    </div>
    <div id="tab-3">
      <h2>Third</h2>
      <p>Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</p>
    </div>
    <div id="tab-4">
      <h2>Fourth</h2>
      <p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo.</p>
    </div>
  </div>
</div>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8/jquery.min.js"></script>
<script>
    (function($){
        $(document).ready(function(){
            var tabs=$(".tabs"),
                tabsLabels=tabs.children("ul"),
                tabLabel=tabsLabels.find("a"),
                tabsContent=tabs.children("div");
            tabLabel.click(function(e){
                e.preventDefault();
                var tabID=$(this).attr("href"),
                    targetTab=tabsContent.find(tabID),
                    activeTab=tabsLabels.find(".active-tab"),
                    activeTabContent=tabsContent.find(".active-tab-content");
                activeTab.add(activeTabContent).removeClass("active-tab active-tab-content");
                $(this).parent().addClass("active-tab");
                targetTab.addClass("active-tab-content");
            });
        });
    })(jQuery);
</script>
</body>
</html>

From here on, we’ll continue optimizing and extending the script, make tabs look much better and explore different ways of loading tabs content.

Extending the script

Adding the active tab classes automatically

Instead of adding the active tab classes manually in the markup, we’ll add few extra lines of code in the script to automate the process, avoiding that extra step each time we create a tabbed area.

We’ll create a condition that checks if .active-tab exists (cause we might need to have other than the first tab visible when page loads) and if it does, we apply the .active-tab-content on the content div with the equivalent id. If .active-tab does not exist in the markup, we add the active classes on the first list item and content div:

<script>
    (function($){
        $(document).ready(function(){
            var tabs=$(".tabs"),
                tabsLabels=tabs.children("ul"),
                tabLabel=tabsLabels.find("a"),
                tabsContent=tabs.children("div");
            if(tabsLabels.find(".active-tab").length===0){
                /* .active-tab does not exist. Make the 1st tab active */ 
                tabsLabels.children("li:first").addClass("active-tab");
                tabsContent.children("div:first").addClass("active-tab-content");
            }else{
                /* .active-tab exists. Find tab content id based on its children anchor href value */ 
                tabsContent.find(tabsLabels.find(".active-tab a").attr("href")).addClass("active-tab-content");
            }
            tabLabel.click(function(e){
                e.preventDefault();
                var tabID=$(this).attr("href"),
                    targetTab=tabsContent.find(tabID),
                    activeTab=tabsLabels.find(".active-tab"),
                    activeTabContent=tabsContent.find(".active-tab-content");
                activeTab.add(activeTabContent).removeClass("active-tab active-tab-content");
                $(this).parent().addClass("active-tab");
                targetTab.addClass("active-tab-content");
            });
        });
    })(jQuery);
</script>

The script now activates the first tab by default. If for example we want to have the second tab active on page load, we simply add the .active-tab to the second li (we don’t bother at all with the .active-tab-content).

Making the script bulletproof

Within our script we can do some pretty cool tricks and one of them is connecting each tab label (li) to its content (div) by their DOM index number. By doing this, the tabs will be able to function more indipendently from a strict markup and we also avoid the need of having href and id values in the HTML. In order to make it as versatile as possible, we should consider a situation where we might want to keep some or all href and ids, so we’re going to make the script “smart” enough to work regardless of attributes existence.

We create an additional condition that checks a)if the .active-tab anchor has an href attribute, b)if its value is not “#”, c)if its value starts with “#” and d)if it has an equivalent div (a content div with an id attribute equal to its href). If all those conditions are met, the script will use the href value that corresponds to a div id and if not, it’ll find the “correct” div by its index number within the DOM.

We add this condition in 2 places: inside the click event (see location 1 comment) and inside our previous condition (location 2 comment):

<script>
    (function($){
        $(document).ready(function(){
            var tabs=$(".tabs"),
                tabsLabels=tabs.children("ul"),
                tabLabel=tabsLabels.find("a"),
                tabsContent=tabs.children("div");
            if(tabsLabels.find(".active-tab").length===0){
                tabsLabels.children("li:first").addClass("active-tab");
                tabsContent.children("div:first").addClass("active-tab-content");
            }else{
                /* location 1 */
                var activeTabVal=tabsLabels.find(".active-tab a").attr("href");
                if(activeTabVal && activeTabVal!="#" && activeTabVal.substring(0)==="#" && tabsContent.find(activeTabVal).length!=0){
                    /* find content based on id */
                    tabsContent.find(activeTabVal).addClass("active-tab-content"); 
                }else{
                    /* find content based on DOM index */
                    tabsContent.children("div").eq(tabsLabels.find(".active-tab").index()).addClass("active-tab-content");
                }
            }
            tabLabel.click(function(e){
                e.preventDefault();
                var tabID=$(this).attr("href"),
                    targetTab, /* we remove the previous value to avoid js errors */
                    activeTab=tabsLabels.find(".active-tab"),
                    activeTabContent=tabsContent.find(".active-tab-content");
                /* location 2 */
                if(tabID && tabID!="#" && tabID.substring(0)==="#" && tabsContent.find(tabID).length!=0){
                    /* find content based on id */
                    targetTab=tabsContent.find(tabID); 
                }else{
                    /* find content based on DOM index */
                    tabID=$(this).parent().index(); 
                    targetTab=tabsContent.children("div").eq(tabID);
                }
                activeTab.add(activeTabContent).removeClass("active-tab active-tab-content");
                $(this).parent().addClass("active-tab");
                targetTab.addClass("active-tab-content");
            });
        });
    })(jQuery);
</script>

The script now is able to tell which tab label corresponds to which content, even if the markup doesn’t make much sense. For example, our tabs will work as expected despite the following sloppy markup (example):

<div class="tabs">
  <ul>
    <li>
      <a href="#tab-1">First</a>
      <!-- this has an href value that corresponds to an existing id -->
    </li>
    <li>
      <a href="#">Second</a>
      <!-- this doesn't have an href value that corresponds to any id -->
    </li>
    <li>
      <a href="#tab-3">Third</a>
      <!-- this has an href value that targets to a non-existing id -->
    </li>
    <li>
      <a>Fourth</a>
      <!-- this doesn't have href attribute at all -->
    </li>
  </ul>
  <div>
    <div id="tab-1">
      <h2>First</h2>
      <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
    </div>
    <div>
      <h2>Second</h2>
      <p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
    </div>
    <div>
      <h2>Third</h2>
      <p>Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</p>
    </div>
    <div id="tab-4">
      <h2>Fourth</h2>
      <p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo.</p>
    </div>
  </div>
</div>

Styling the tabs

Moving CSS to external stylesheet

In order to make tabs implementation easier and have a maintainable stylesheet, we need to move tabs CSS to an external file. Create a new .css document, save it as tabs.css and copy all CSS rules inside it. Link the external CSS file by inserting <link href="tabs.css" rel="stylesheet" type="text/css" /> inside the head tag of the HTML, replacing the style tag.

Design!

By applying some CSS gradients, rounded corners and shadows, we can make the tabs look pretty cool, without the need for background images:

.tabs{
  margin:40px;
  width:380px;
  font-family:georgia,serif;
  font-size:13px;
  font-style:italic;
  color:#666;
  text-shadow:0 1px rgba(255,255,255,0.9);
}
.tabs>ul,.tabs>div{
  position:relative;
}
.tabs>ul{
  overflow:hidden;
  margin:0;
  padding:0;
  list-style-type:none;
  margin:0 4px;
  padding:3px 0 0 0;
  z-index:2;
}
.tabs>ul>li{
  float:left;
  position:relative;
  margin:-3px -1px 0 0;
}
.tabs>ul>li>a{
  display:inline-block;
  padding:5px 10px;
  text-decoration:none;
  color:#f5f5f5;
  font-weight:bold;
  text-shadow:0 -1px rgba(0,0,0,0.5);
  background:#bbb;
  background:-o-linear-gradient(top, #ddd, #999);
  background:-moz-linear-gradient(top, #ddd, #999);
  background:-webkit-gradient(linear, left top, left bottom, color-stop(0, #ddd), color-stop(1, #999));
  outline:none;
  -webkit-border-radius:4px 4px 0 0;
  -moz-border-radius:4px 4px 0 0;
  border-radius:4px 4px 0 0;
  border:1px solid #888;
  -webkit-box-shadow:inset 0 1px 0 #fff,inset 0 -2px 3px -1px rgba(0,0,0,0.3);
  -moz-box-shadow:inset 0 1px 0 #fff,inset 0 -2px 3px -1px rgba(0,0,0,0.3);
  box-shadow:inset 0 1px 0 #fff,inset 0 -2px 3px -1px rgba(0,0,0,0.3);
}
.tabs>ul>li>a:hover{
  color:#fff;
  background:#ccc;
  background:-o-linear-gradient(top, #eee, #999);
  background:-moz-linear-gradient(top, #eee, #999);
  background:-webkit-gradient(linear, left top, left bottom, color-stop(0, #eee), color-stop(1, #999));
}
.tabs>ul>li.active-tab>a{
  color:#555;
  text-shadow:0 1px rgba(255,255,255,0.9);
  background:#e5e5e5;
  background:-o-linear-gradient(top, #ddd, #e5e5e5);
  background:-moz-linear-gradient(top, #ddd, #e5e5e5);
  background:-webkit-gradient(linear, left top, left bottom, color-stop(0, #ddd), color-stop(1, #e5e5e5));
  -webkit-box-shadow:inset 0 2px 0 #fff;
  -moz-box-shadow:inset 0 2px 0 #fff;
  box-shadow:inset 0 2px 0 #fff;
  border-bottom-color:#e5e5e5;
}
.tabs>div{
  z-index:1;
  top:-1px;
  -webkit-border-radius:4px;
  -moz-border-radius:4px;
  border-radius:4px;
  border:1px solid #888;
  background:#e5e5e5;
  background:-o-linear-gradient(top, #e5e5e5, #f5f5f5);
  background:-moz-linear-gradient(top, #e5e5e5, #f5f5f5);
  background:-webkit-gradient(linear, left top, left bottom, color-stop(0, #e5e5e5), color-stop(1, #f5f5f5));
  -webkit-box-shadow:inset 0 -2px 0 rgba(0,0,0,0.15),0 3px 4px -2px rgba(0,0,0,0.25); 
  -moz-box-shadow:inset 0 -2px 0 rgba(0,0,0,0.15),0 3px 4px -2px rgba(0,0,0,0.25);
  box-shadow:inset 0 -2px 0 rgba(0,0,0,0.15),0 3px 4px -2px rgba(0,0,0,0.25);
}
.tabs>div>div{
  position:relative;
  top:0;
  left:0;
  display:none;
  overflow:hidden;
  padding:10px 15px;
}
.tabs>div>div.active-tab-content{
  display:block;
}

The shadows and light gradients give tabs a subtle 3D look & feel. Adding negative top margin on the list items, removes the unwanted bottom space that would appear between 2 or more rows of tab labels. Moreover, it makes our tabs look more realistic as if they’re stacked on top of each other.

Tab labels (li) are floated left, so having many means that the ones overflowing the width of .tabs, will normally wrap to a new line. This will cause empty areas on the right side of tab labels rows. Since there’s no float:bottom in CSS, it can be visually “wrong” having a narrow tabs row below a wider one. Normally, we’d always want our widest row at the bottom and we can do that manually, by using CSS nth-child to apply a clear:left property on any tab we want. For example, if we want to place the 3rd tab label on a new line, we can do:

.tabs>ul>li:nth-child(3){
  clear:left;
}

See example

Turning script into plugin

In order to have multiple tabbed areas on a single page, we have to give them different class names or ids (e.g. .tabs, .tabs-2, .tabs-3 etc.) and manually repeat the script for each one, making implementation slow and frustrating. Fortunatelly, we can transform our script into a proper plugin with few extra lines of code.

The first step is to move the entire jQuery code to an external javascript file (same as we did with css). Create a new document, save it as tabs.js. and insert the following code:

(function($){
  $.fn.Tabs=function(){
    return this.each(function(){
      /* ... */
    });
  }
})(jQuery);  

We created a function called Tabs that’ll hold and run the script on each element we pass it as its selector (more on this later).

Next, we insert our script inside jQuery $.each function, changing var tabs=$(".tabs") to var tabs=$(this) (so the script runs the same code on each selector):

(function($){
  $.fn.Tabs=function(){
    return this.each(function(){
      var tabs=$(this),
          tabsLabels=tabs.children("ul"),
          tabLabel=tabsLabels.find("a"),
          tabsContent=tabs.children("div");
      if(tabsLabels.find(".active-tab").length===0){
        tabsLabels.children("li:first").addClass("active-tab");
        tabsContent.children("div:first").addClass("active-tab-content");
      }else{
        var activeTabVal=tabsLabels.find(".active-tab a").attr("href");
        if(activeTabVal && activeTabVal!="#" && activeTabVal.substring(0)==="#" && tabsContent.find(activeTabVal).length!=0){
          tabsContent.find(activeTabVal).addClass("active-tab-content");
        }else{
          tabsContent.children("div").eq(tabsLabels.find(".active-tab").index()).addClass("active-tab-content");
        }
      }
      tabLabel.click(function(e){
        e.preventDefault();
        var tabID=$(this).attr("href"),
            targetTab,
            activeTab=tabsLabels.find(".active-tab"),
            activeTabContent=tabsContent.find(".active-tab-content");
        if(tabID && tabID!="#" && tabID.substring(0)==="#" && tabsContent.find(tabID).length!=0){
          targetTab=tabsContent.find(tabID);
        }else{
          tabID=$(this).parent().index();
          targetTab=tabsContent.children("div").eq(tabID);
        }
        activeTab.add(activeTabContent).removeClass("active-tab active-tab-content");
        $(this).parent().addClass("active-tab");
        targetTab.addClass("active-tab-content");
      });
    });
  }
})(jQuery);  

Include tabs.js in the document below the jQuery inclusion and call the Tabs function on the element(s) you want:

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8/jquery.min.js"></script>
<script src="tabs.js"></script>
<script>
    (function($){
        $(document).ready(function(){
            $(".tabs").Tabs();
        });
    })(jQuery);
</script>

This will tranform all .tabs within the page into tabbed areas. Additionally, we can add multiple selectors by separating them with comma:

$("#tabs-1,#tabs-2,#tabs-3").Tabs(); 

Multiple tabbed areas example

Extras

Adding transition effects

We can further enhance the look ↦ feel of tabbed areas by adding fade and height animations when switching between tabs (see it in action). Clicked tab content will fade-in and its container height will animate accordingly. We’ll use jQuery $.fadeIn and $.fadeOut, as well as $.animate functions. For convenience, We will also set the transition speed via a function parameter. Edit tabs.js and add the effects:

(function($){
  $.fn.Tabs=function(speed){
    return this.each(function(){
      var tabs=$(this),
          tabsLabels=tabs.children("ul"),
          tabLabel=tabsLabels.find("a"),
          tabsContent=tabs.children("div");
      if(!speed){
        speed=0; /* default transition speed */
      }
      if(tabsLabels.find(".active-tab").length===0){
        tabsLabels.children("li:first").addClass("active-tab");
        tabsContent.children("div:first").addClass("active-tab-content").fadeIn(speed); /* fade-in first tab */
      }else{
        var activeTabVal=tabsLabels.find(".active-tab a").attr("href");
        if(activeTabVal && activeTabVal!="#" && activeTabVal.substring(0)==="#" && tabsContent.find(activeTabVal).length!=0){
          tabsContent.find(activeTabVal).addClass("active-tab-content").fadeIn(speed); /* fade-in active tab */
        }else{
          tabsContent.children("div").eq(tabsLabels.find(".active-tab").index()).addClass("active-tab-content").fadeIn(speed);
        }
      }
      tabsContent.css({"height":tabs.find(".active-tab-content").outerHeight()});
      tabLabel.click(function(e){
        e.preventDefault();
        var tabLink=$(this),
            tabID=tabLink.attr("href"),
            targetTab,
            activeTab=tabsLabels.find(".active-tab"),
            activeTabContent=tabsContent.find(".active-tab-content");
        if(tabID && tabID!="#" && tabID.substring(0)==="#" && tabsContent.find(tabID).length!=0){
          targetTab=tabsContent.find(tabID);
        }else{
          tabID=tabLink.parent().index();
          targetTab=tabsContent.children("div").eq(tabID);
        }
        /* tabs switching */
        activeTabContent.stop().fadeOut(speed,function(){
          activeTab.add(activeTabContent).removeClass("active-tab active-tab-content");
          tabLink.parent().addClass("active-tab");
          targetTab.addClass("active-tab-content").css({"opacity":0,"display":"block"}); 
          tabsContent.stop().animate({height:targetTab.outerHeight()},speed,function(){ 
            targetTab.stop().animate({opacity:1},speed); 
          });
        });
      });
    });
  }
})(jQuery);  

In the new code above, we chained the $.fadeIn function after each $.addClass and added the speed parameter in our Tabs function. At the end of the click event, you can see all the fade and height animation functions that do the following:

  1. Fade-out current tab content
  2. Remove active classes after fade-out animation is complete
  3. Add active-tab class to the clicked tab
  4. Add active-tab-content class to targetTab, set its display to block and apply zero opacity so we can fade it in later
  5. Animate tabs content div height according to targetTab height
  6. Fade-in the clicked tab content by animating its opacity from zero to one

To set the transition speed, we pass the value on the speed parameter:

<script>
    (function($){
        $(document).ready(function(){
            $(".tabs").Tabs("fast");
        });
    })(jQuery);
</script>

The value can be “fast”, “slow” or an integer (e.g. 700) for milliseconds. If we don’t pass any parameter value, the speed defaults to zero (if(!speed){ speed=0; }) which will make tabs switching instantaneous.

Ajax!

There are times when we need to load tabs content dynamically via ajax, so this is what we’re going to do next. We’re going to integrate ajax functionality in the plugin so we can have static or dynamic content on each tab.

It’s always a good practice to have some kind of indicator that the content requested is loading, so we’ll need an animated gif image as a preloader icon for the ajax calls. You can create your own image or you can use the one on the demo.

Open tabs.css and add the preloader styling:

.tabs .ajax-loader{
  position:absolute;
  display:none;
  width:24px;
  height:24px;
  line-height:24px;
  text-align:center;
  -webkit-border-radius:4px;
  -moz-border-radius:4px;
  border-radius:4px;
  background:url(ajax-loader.gif) no-repeat center center;
  overflow:hidden;
  top:25px;
  left:50%;
  margin-left:-12px
}

The preloader has the class “ajax-loader” and the element itself will be inserted in the markup (only if needed) by the script. We’ve set the ajax-loader.gif as its background but if you’re using another image change it accordingly.

Next, we need to set each tab content by adding their paths in our tab anchors href attributes:

<div class="tabs">
  <ul>
    <li>
      <a href="ajax-content/tab_1_content.html">First</a>
    </li>
    <li>
      <a href="ajax-content/tab_2_content.html">Second</a>
    </li>
    <li>
      <a href="ajax-content/tab_3_content.html">Third</a>
    </li>
    <li>
      <a href="ajax-content/tab_4_content.html">Fourth</a>
    </li>
  </ul>
</div>

The first tab will load ajax-content/tab_1_content.html, the second ajax-content/tab_2_content.html and so on… We’ve also removed the tabs content divs below the ul, since we no longer have static tabs content in our page. All required markup for the ajax content will be generated by the script.

Since we’ll be integrating static and dynamic content for the tabs, we’re gonna change few parts of the script by creating a couple of functions that we’ll use for both methods in addition to the parts where we generate the markup for the content and preloader. We’ll write the code in such way that we’ll be able to have both static and dynamic content in a single tabbed area.

Open tabs.js and insert the new code:

(function($){
  $.fn.Tabs=function(speed){
    return this.each(function(){
      var tabs=$(this),
          tabsLabels=tabs.children("ul"),
          tabLabel=tabsLabels.find("a"),
          tabsContent=tabs.children("div"),
          tabLink,
          tabsLoader=tabsContent.children(".ajax-loader"); 
      if(!speed){
        speed=0; 
      }
      if(tabsContent.length===0){
        tabs.append("<div />");
        tabsContent=tabs.children("div");
        tabsLabels.children("li").each(function(){
          tabsContent.append("<div class='ajax-tab-content' />");
        });
      }else{
        tabsContent.children("div").each(function(){
          if($.trim($(this).text())===""){
            $(this).addClass("ajax-tab-content");
          }
        });
      }
      if(tabsLoader.length===0 && tabsContent.children("div.ajax-tab-content").length>0){
        tabsContent.append("<span class='ajax-loader' />");
        tabsLoader=tabsContent.children(".ajax-loader");
      }
      if(tabsLabels.find(".active-tab").length===0){
        tabsLabels.children("li:first").addClass("active-tab");
      }
      tabLink=tabsLabels.find(".active-tab a");
      tabsContent.css({"height":tabsContent.height()}); 
      LoadTabContent(FindTabContent(tabLink),tabLink);
      tabLabel.click(function(e){
        e.preventDefault();
        tabLink=$(this);
        LoadTabContent(FindTabContent(tabLink),tabLink);
      });
      function FindTabContent(tabLink){ 
        var targetTab,
            tabID=tabLink.attr("href");
        if(tabID && tabID!="#" && tabID.substring(0)==="#" && tabsContent.find(tabID).length!=0){
          targetTab=tabsContent.find(tabID);
        }else{
          tabID=tabLink.parent().index();
          targetTab=tabsContent.children("div").eq(tabID);
        }
        return targetTab;
      }
      function LoadTabContent(targetTab,tabLink){
        var activeTab=tabsLabels.find(".active-tab"),
            activeTabContent=tabsContent.find(".active-tab-content");
        if(activeTabContent.length===0){
          activeTabContent=targetTab;
        }
        activeTabContent.stop().fadeOut(speed,function(){ 
          activeTab.add(activeTabContent).removeClass("active-tab active-tab-content"); 
          tabLink.parent().addClass("active-tab"); 
          if(targetTab.is(".ajax-tab-content")){
            tabsLoader.stop().fadeIn(speed); 
            targetTab.load(tabLink.attr("href"),function(){
              var targetTabImages=targetTab.find("img"),
                  targetTabImagesLoaded=0;
              if(targetTabImages.length>0){
                targetTabImages.bind("load",function(){
                  targetTabImagesLoaded++
                  if(targetTabImagesLoaded>=targetTabImages.length){
                    tabsLoader.stop().fadeOut(speed);
                    ShowTabContent();
                  }
                });
              }else{
                tabsLoader.stop().fadeOut(speed);
                ShowTabContent();
              }
            });
          }else{
            ShowTabContent();
          }
          function ShowTabContent(){
            targetTab.addClass("active-tab-content").css({"opacity":0,"display":"block"});
            tabsContent.stop().animate({height:targetTab.outerHeight()},speed,function(){ 
              targetTab.stop().animate({opacity:1},speed); 
            });
          }
        });
      }
    });
  }
})(jQuery); 

What we roughly did here is define and set .ajax-loader, add tabs content markup, apply the .ajax-tab-content class to the div(s) that’ll hold ajax content and create 2 main functions: FindTabContent & LoadTabContent. Those functions will trigger on page load and click event. Inside the LoadTabContent function we’ve added the jQuery $.load() function that loads the external content.

I’ve made a fully commented script available on github:gist (https://gist.github.com/3812245) so you can see exactly what each code line does.

The new script allows us to have static and ajax tabs content in the same tabbed area (see example). The markup for this, is adding empty divs for the ajax content and divs with actual content for the static ones. For example:

<div class="tabs">
  <ul>
    <li>
      <a href="ajax-content/tab_1_content.html">First</a>
    </li>
    <li>
      <a href="#">Second</a>
    </li>
    <li>
      <a href="ajax-content/tab_3_content.html">Third</a>
    </li>
    <li>
      <a href="#">Fourth</a>
    </li>
  </ul>
  <div>
    <div>
      <!-- empty container for ajax content -->
    </div>
    <div>
      <h2>Second</h2>
      <p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
    </div>
    <div>
      <!-- empty container for ajax content -->
    </div>
    <div>
      <h2>Fourth</h2>
      <p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo.</p>
    </div>
  </div>
</div>

Additionally, you can apply the ajax-tab-content class to any div you want to load content via ajax, even if it’s not empty. For instance, the following div will load ajax content instead of displaying the static one in it:

<div class=".ajax-tab-content">
  <h2>Second</h2>
  <p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</div>

That’s the end of the tutorial. The next page contains a quick guide on implementing the plugin included in the archive.

Pages: 1 2


10 Comments

Post a comment
  1. Pico
    Posted on March 28, 2014 at 02:38 Permalink

    marvelous. so happy you’re around1

    Reply
  2. Chad
    Posted on November 9, 2013 at 22:10 Permalink

    Thank you for the fantastic scripts!

    Any chance you might have an example of this working along with your custom horizontal scrollbar? I really need a flexible width tabbed display that works with a horizontal scrollbar.

    I’ve been trying to implement a series of Jquery-UI tabs along with your scrollbar and I haven’t been having much luck.

    Reply
  3. Brian
    Posted on September 12, 2013 at 16:41 Permalink

    Hi

    I’m new at all this fancy code, so maybe someone could help.

    I have used (http://manos.malihu.gr/tuts/tabs/tutorial/step-8.html) and it’s pretty nice. I have tryed to combine it with the scrollbar (http://manos.malihu.gr/tuts/custom-scrollbar-plugin/infinite_scroll_example.html) without any luck. Can these work together?

    /Brian

    Reply
  4. Nikita Radaev
    Posted on June 18, 2013 at 22:19 Permalink

    Hi Malihu,

    Just wanted to thank you for you excellent tutorials. I’ve used your plugins in most of my projects. Not to say how much I learned from them. Again, thank you and keep up good work.

    Cheers, Nikita!

    Reply
  5. Mareno
    Posted on December 3, 2012 at 02:05 Permalink

    very useful. excellent tutorial

    How do I get this tab to show content in two different places.

    for example, the right and left in two boxes

    One box text, the second picture.

    thanks in advance
    sry bd eng

    Reply
    • Ana Leonne
      Posted on September 3, 2015 at 11:56 Permalink

      I also plan to implement HTML tab menu in sidebar on the web pages.

      My only problem for now is how to keep page 100% responsive as it is now and is it complicated to implement that?
      You can find my page is here

      Reply
  6. John Inglessis
    Posted on October 20, 2012 at 22:35 Permalink

    Dictionary Word : explanation
    Definition: go and read malihu’s tutorials.
    Nice work ! !

    Reply
  7. Peter
    Posted on October 20, 2012 at 03:50 Permalink

    Nice work. This is my first time seeing someone create a tabbed piece that supports ajax by default. It’s nice and clean, good work! Then again, you always do good work! 🙂

    Reply
  8. Yoosuf
    Posted on October 4, 2012 at 17:36 Permalink

    Oh hell no, i will use the jQuery UI

    Reply
    • malihu
      Posted on October 4, 2012 at 18:31 Permalink

      Yes, of course you could do that.

      In addition to just using the plugin itself on your project(s), the tutorial serves the purpose of learning how to do it yourself and understanding how tabs actually work.

      Moreover, someone might not want to utilize the jQuery ui just for few simple tabbed areas. The plugin provided here is 1.5kb and can handle static as well as ajax content.

      Reply

Post a comment

Your e-mail is never published nor shared. Required fields are marked *

You may use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>
You can write or copy/paste code directly in your comment using the <code> tag:
<code>code here...</code>
You may also use the data-lang attribute to determine the code language like so:
<code data-lang-html>, <code data-lang-css>, <code data-lang-js> and <code data-lang-php>

css.php