Creating a Tabbed Interface with CSS and jQuery

Here’s a piece I put together recently for a client who wanted a tabbed interface on one of their pages. My goal in doing it was to make it as accessible and semantic as possible. One requirement I gave myself was to NOT use redundant elements (like one list for the tabs, and another for the content).

I chose to use an HTML Definition List (DL) (the unsung hero of semantic markup). The one problem I have with DL's is that the relationship between DT and DD is implied by their position in the markup. Unlike label elements that have the for= attribute to associate themselves with a form element (or which can wrap a form element, thereby creating the association), the DT and DD elements are more loosely connected.

My solution to this was to add a target attribute to the DT's and a corresponding ID attribute to the DD. This would enable me to programmaticly associate the DT's and their DD's. With that out of the way, the rest of it is some simple jQuery functionality to drive the tabs themselves.

View a demo and/or review the code below.

The HTML markup:


<div id="program_detail_tabs">
	<dl id="program_details">
		<dt target="pg_overview" id="tab_overview">Overview</dt>
		<dd id="pg_overview">This is how you could create a tabbed interface using nothing but CSS and an HTML Definition List (DL).
		</dd>

		<dt target="pg_works" id="tab_works">How it Works</dt>
		<dd id="pg_works">Starting out with a basic <abbr title="Definition List">DL</abbr>, I've added a target attribute to the the <abbr title="Definition Term">DTs</abbr>, then given each <abbr title="Definition Definition">DD</abbr> an ID that matches its corresponding DT's title.
		</dd>

		<dt target="pg_var" id="tab_var">Varieties</dt>
		<dd id="pg_var">Section 3 content
		</dd>

		<dt target="pg_billing" id="tab_billing">Billing Info</dt>
		<dd id="pg_billing">Section 4 content
		</dd>

		<dt target="pg_legal" id="tab_legal">Legal Info</dt>
		<dd id="pg_legal">Section 5 content
		</dd>
	</dl>
</div>

So, here we have the DL with the afformentioned targets and IDs. The containing div was legacy from the original project, and was really there just to serve as a container for the whole thing. A little too divitis for my taste, but alas, I put it there for a reason that I can’t remember now. As you can see, the target attribute of the DT matches the ID attribute of the DD. This is really important when we get to the JavaScript portion of the show.

With the markup in place, we can now drape the CSS over the top:


dl#program_details {
	width: 380px;
	position: relative;
	padding: 0;
	height: 200px;
	margin: 0;
	padding: 0;

}

dl#program_details dt {
	position: absolute;
	display: block;
	width: 76px;
	height: 40px;
	text-align: center;
	font-size: 12px;
}

dl#program_details dt#tab_overview {
	left: 0;
}
dl#program_details dt#tab_works {
	left: 76px;
}
dl#program_details dt#tab_var {
	left: 152px;
}
dl#program_details dt#tab_billing {
	left: 228px;
}
dl#program_details dt#tab_legal {
	left: 304px;
}

dl#program_details dd {
	position: absolute;
	top: 45px;
	overflow: auto;
	margin: 5px;
	width: 370px;
	height: 150px;
	padding-right: 5px;

}

.tab_here {
	background-color: #EFEFEF;
}

We start with the DL itself. The important piece to take away here is the position: relative declaration. This sets a non-empty position attribute on the DL itself, thereby making anything inside of it absolutely positionable if we wish (and we do wish).

Next, we’ve got the DT. As you can see, I’ve set this to position: absolute. This enables us to remove the DT from the document flow and use it as the tab itself. Following the generic DT are the specific left positions of each DT. Basically, those magic numbers are the overall width of the DL divided by the number of tabs (380/5 = 76) times the tab position (76 * i).

The last important piece is the DD styles. Again here, I’ve got the position set to absolute, and then I’ve placed it within the DL so that there’s enough room for the tabs, and I’ve set the height to a static size, and overflow to auto. This will add a scrollbar to the DD if the there’s more content than the height will allow (a requirement from creative).

With the CSS in place, we can add the interactivity with a jQuery block:


$(document).ready(function() {

	/* set up program details tabs */
	$("#program_details dd").hide();

	$("#program_details dt").click(function() {
		var tgt = $(this).attr("target");
		$("#program_details dd:visible").hide();
		$("#program_details dt.tab_here").removeClass("tab_here");
		$("#"+tgt).show();
		$(this).addClass("tab_here");
	});

});

The best thing about jQuery is that it’s pretty self-explanatory. First, we hide all of the DD's inside of the element whose ID is “program_details”. Then, we add a click handler to each DT inside of said “program_details” element.

Grab the value of the target attribute and stash it in the tgt variable. Hide any DD's that are visible inside of the “program_details” element. Remove the class “tab_here” from any DT that’s already classed “tab_here”. Show the ID that has the same name as the tgt variable from the beginning, and then add the “tab_here” class to the DT that was clicked.

There you have a pretty straightforward, easy-to-implement, and fairly accessible tabbed content interface driven by a single HTML Definition List. Turn CSS and/or Javascript off, and it degrades nicely to a regular definition list, but with standard bells & whistles, it works out pretty well. I hope this can be helpful to someone out there, and if you use it or expand upon it, post a comment—I’d love to hear about it.

5 thoughts on “Creating a Tabbed Interface with CSS and jQuery

  1. Quick question (for someone just getting into jQuery)–On the demo, no cursor change is visible to alert someone to the different tabs (and that they are ‘links’ to different sections). This could be solved some by the use of graphics, I would think. How would you imagine that be addressed?

    I’m currently trying to work a solution for a personal site that uses tabs, and your method is thus far the best I’ve come upon (especially as I already have the jQuery loaded for other functions).

    Nice demonstration technique with the example up top and naked down below.

  2. Don- The easiest way would be to add a cursor declaration to your CSS for the DTs:

    #tabset dt { cursor: pointer; }

    You needn’t worry about doing it on hover, as the only time the cursor is activated is when the user is interacting (hovering over, clicking on) the object.

    Thanks for the great feedback about the tutorial! I plan on updating this one to make it less ID-centric, by using more of the jQuery DOM traversing methods, but haven’t gotten around to it yet.

  3. It’s hard to find your page in google. I found it on 21 spot, you should build quality backlinks ,
    it will help you to rank to google top 10. I know how to
    help you, just type in google – k2 seo tips

  4. I see a lot of interesting articles on your blog.
    You have to spend a lot of time writing, i know how to save you a lot of time,
    there is a tool that creates readable, google
    friendly articles in couple of minutes, just type in google – k2 unlimited content

  5. I read a lot of interesting content here. Probably you spend a lot of time writing, i know
    how to save you a lot of work, there is an online tool that creates unique,
    google friendly articles in seconds, just type in google –
    laranitas free content source

Leave a Reply

Your email address will not be published. Required fields are marked *