Sam Croft

Full-stack developer

Creating a lightweight and semantic jQuery tabs plugin


Filed in: css, jQuery, plugin

Earlier this year I wrote a tiny jQuery accordion plugin, for mobile projects, because I was sick of using non-semantic code snippets to create a very simple ui component. While writing it I realised that it would be reasonably straightforward to create a tabs version using the same principles.

Your average jQuery tabs plugin features a lot of unnecessary markup

When I was using the jQuery ui tabs plugin last year I was rather alarmed at the over engineered use of HTML markup. The jQuery ui tabs, and indeed many others, use an ul to form the tab control buttons and a series of div‘s to hold the content for each tab. Like so:

<div id="tabs">
		<li><a href="#tab-1">Tab 1</a></li>
		<li><a href="#tab-2">Tab 2</a></li>
		<li><a href="#tab-3">Tab 3</a></li>

	<div id="tab-1">
		<h1>Tab 1</h1>
	<div id="tab-2">
		<h1>Tab 2</h1>
	<div id="tab-3">
		<h1>Tab 3</h1>

Ok so it makes sense, and it’s certainly easy to understand, but it seems over engineered to me. It’s not very semantic, with just the id attributes linking the two otherwise unrelated elements—albeit all wrapped in a parent div.

Creating a more semantic jQuery tabs plugin

When I read Bulletproof Web Design by Dan Cederholm in 2005, the main message that I got from it was; most aspects of a website can be created using list elements. Navigation bars, sidebars and image galleries all built using basic HTML markup. Markup that was semantic and made sense. When you break content down, chances are that you can use a list element to mark some of it up.

It was also this book that introduced me to the wonderful, and then underused, dl—the definition list.

Why a definition list is perfect for a jQuery tabs plugin

A definition list consists of a number of titles – dt, and descriptions – dd. And, just like with my lightweight accordion plugin, makes it the perfect semantic element to markup a series of content tabs.

Using a dl means that there is no need to separate the titles/links from the actual related content.

Creating the jQuery tabs plugin

This jQuery tabs plugin takes elements outside of the parent tab element and uses the jQuery height method to determine the current height of the dl when considering only the active tab content.

The HTML markup

	<dt><a href="#">Tab 1</a></dt>
		<h1>Tab 1</h1>
	<dt><a href="#">Tab 2</a></dt>
		<h1>Tab 2</h1>
	<dt><a href="#">Tab 3</a></dt>
		<h1>Tab 3</h1>

As previously discussed, the HTML markup consists of just a dl with a series of titles/links – dt‘s and their corresponding content – dd‘s.

Markup done.

Adding a bit of CSS

For this technique to work I will float each tab title/link dt left and then use absolute positioning to move the the tab content dd below the tab title/links and layer them on top of each other. jQuery will then be used to hide and show the corresponding content tab that is being requested.

dl {
	position: relative;
	float: left;
	width: 500px;
	dl.enhance {
		border: solid #333;
		border-width: 0 0 1px 1px;
		dl.enhance dt {
			width: 150px;
			line-height: 2;
			text-align: center;
			float: left;
			background: #CCC;
			border: solid #333;
			border-width: 1px 1px 0 0;
			dl.enhance dt a {
				height: 30px;
				display: block;
		dl.enhance dd {
			position: absolute;
			top: 30px;
			margin: 0;
			padding: 0 10px;
			border: solid #333;
			border-width: 1px 1px 0 0;
			background: #FFF;

Lines 1-5 – because I will be using absolute positioning for child elements I need to ensure this has a position attribute of relative. And because I will be floating each dt element I need to float the parent—the dl itself. I also set a width to ensure that the content has a fixed dimensions that cannot be resized.

Lines 6-9 – just as with my lightweight jQuery accordion plugin I am using the aptly titled ‘enhance’ class to only style a dl element that has successfully been progressively enhanced by the jQuery plugin. Yes, I am that bothered about the 1% of users that have JavaScript disabled. And within this class I am defining some visual styles for the background and border colours.

Lines 10-12 – here I am setting the style for each dt which will form the clickable area for each tab. What is important within this rule is that the element is floated and that a width is set. The width and the rest of the attributes are of course dependent on your design.

Lines 19-22 – this rule ensures that the href, forming the clickable link for each tab, has a height and a display attribute of block to ensure that the entire area of the title/link for the tab is clickable.

Lines 23-26 – this rule is particularly important. Here I effectively take the dd elements out of the parent dl and position them below the row of floated dt elements by using the position attribute, setting it to absolute and give the rule a top value that is equal to the height of the href title/links in each dt element. In my case this is a value of 30px.

That’s most of the CSS complete, and to understand how it’s working thus far it’s worth looking at things before adding the jQuery plugin.

Displaying how the applied CSS means that the definition list seemingly only contains the definition titles, as the definition descriptions have been effectively moved outside of the element using position absolute
What this image attempts to show is that with the CSS applied on its own the definition list tag itself is the wrong height, matching that of only the definition titles, because the definition descriptions have been moved outside of the element with position: absolute

A few lines of JavaScript to setup the jQuery tabs plugin

The method I have applied to this plugin is very similar to my lightweight jQuery accordion plugin.

this.each(function() {
	var el = $(this);
	var dlHeight = el.height();

	var current = el.find('dt:first').addClass('current');
	var currentHeight ='dd').show().height();
	el.css('height', dlHeight + currentHeight);

Lines 1-4 – first of all I’m looping through every instance of the selectors that have been set to be initialised with the plugin. I then store the $(this) selector in a variable for future use and apply every instance of the tabs with a class of enhance. I also store the height of the dl; note that this value will effectively just be the height of the floated dt‘s.

Lines 6-9 – next up I hide all of the dd‘s within each tab enhanced dl. To make the first dt and dd appear as the selected tab I add a class of current to the first dt using jQuery’s :first selector. Then I store the height of the corresponding dd using jQuery’s next method. I also show this dd at the same time. Finally I modify the CSS for the dl and recalculate its height using the values stored in lines 4 and 8.

This final step of the tabs setup ensures that the dl is the correct height when taking into account the floated dt‘s and the current selected dd.

Handling tab changes on the jQuery tabs plugin

The final part to creating this plugin is to watch and handle each dt being pressed and changing the tab content. This is very straightforward and essentially is exactly the same as the above initial setup with an extra step.

$('dl.enhance dt a').click(function(e){

	var current = $(this).parent('dt').addClass('current');

	var currentHeight ='dd').show().height();
	var dlHeight = $(this).parents('dl').removeAttr('style').height();
	$(this).parents('dl').css('height', dlHeight + currentHeight);	

Lines 1-2 – these two lines look for a click event (an anchor tag being pressed within a dt) and then prevent the default action; the href being followed by the browser.

Lines 4-5 – before changing the current tab I need to remove the current class from the previously used dt and hide its corresponding dd, again by using JQuery’s next method. I then add the current class to the dt element of the href that has been used, using jQuery’s parent method.

Lines 7-8 – now I need to show the corresponding dd by using jQuery’s next method and determine its height, which I store in a variable. I also need to determine the height of the dl, but only once I have removed any inline styles that may have been previously added.

Line 10 – finally, just as with the last part of the setup code, I need to recalculate the overall dl height using the two height values that I have just stored.

This completes the jQuery tabs plugin.

Adding a few more CSS rules for each active tab

To give visual cues that a selected tab is active I need to add a few more lines to the CSS to complete the plugin.

dl.enhance dt.current {
	background: #FFF;
	border-bottom: 1px solid #FFF;
	position: relative;
	z-index: 2;
	dl.enhance dt.current a {
		height: 29px;

These rules use the aforementioned current class and visually differentiate between the selected and non-selected tab controls.

This completes the CSS for the jQuery tabs plugin.

Final thoughts and a few things to note

We’ve been using this lightweight and semantic jQuery plugin at Running in the Halls recently and it’s been a wonderful plugin for integrating into PhoneGap mobile projects where we keep size as small and markup as semantic, as possible.

This plugin has plenty of room for improvement and there are a few things that I am looking to integrate shortly, such as support for URL hash fragments. This jQuery tabs plugin now supports hash fragments.

Finally, there are some aspects that anyone using this should be aware of:

  • images used within each dd must always include a correct width and height attribute
  • the CSS is intended to be modified to fit around your own style
  • the tabs dl must be a fixed width, a fluid width will break the plugin as the height calculations would likely change on resize
  • if JavaScript is not enabled you will see the default appearance of definition lists, so you must write your own fallback styles

About the author

I'm Sam Croft a full-stack developer with over 15 years experience in web and app development. For the last six years I have been a partner of Running in the Halls, an app and game design studio based in Huddersfield, UK. During this time I have developed many web and applications. Highlights include a node.js/ app to create the worlds largest crowd-based game on the first series of Channel 4's Gadget Man with Stephen Fry and Librarygame, a Library gamification platform for Universities. Librarygame is being played by over 6,000 students at The Open University, The University of Manchester, The University of Glasgow and The University of Huddersfield.

In my spare time I enjoy long distance running, watching all sports (especially F1) and playing video games. I live in the Holme Valley in West Yorkshire with my wife, Alex.

Sometimes I tweet.

  • Ted

    Very cool! One thought. You may want to dynamically remove the ID of the clicked tab before you append the hash to avoid the window jumping up. Just make sure to add it back afterward.

  • Sam Croft

    Hey Ted. That’s such a great idea for this annoying issue, many thanks for sharing a solution :)

  • Joemangrove

    Awesome job!  It seems to break in IE7 though.

  • Joemangrove

    Just add left:0; to the content, to fix it in IE7

  • Anonymous

    Thanks for the tip Joe. I must admit, I hadn’t tested this in IE7, just 8 and 9. I’ll add the fix when I get some time. Feel free to fork and send a pull request if you like :)

  • sotoz

    Very good job! Does this work with the latest jquery version?

  • tuyenbui

    Thank you so much. It helps me. Thanks for sharing.