Tabbed Navigation: Adding CSS Classes Dynamically

Tabbed Navigation: Adding CSS Classes Dynamically

Tutorial Details
  • Languages: HTML, CSS, Javascript
  • Difficulty: Intermediate
  • Estimated Completion Time: 30 mins
  • Compatibility: Modern browsers (FF, Safari, IE9, Chrome, didn't check Opera) + IE6-8

I love tabs, I have done for as long as they’ve been around! Let’s see if we can harness some javascript and CSS3 power to build a great tabbed navigation. We’re going to use javascript to auto-detect which tab the visitor is currently on and even make this compatible as far back as IE6. Long live CSS3pie!


Step 1: HTML <head>

The easy part…

As we want our page to be compatible with IE 6-8, we use the “HTML – 4.01 Transitional” doctype. Let’s have a look at the template:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html lang="en">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
		<title>testfile</title>
		<meta name="tutor" content="Bob de Ruiter">
		<meta name="author" content="Your Name">
		<!-- Date: 2011-07-07 -->
	</head>
	<body>
	</body>
</html>

Dead links are better than no links!

Your web editor should have this template, otherwise copy this to index.php in your main folder.

We have three external files which we’ll create or add later. Like my great-grandfather always said: dead links are better than no links. pie.htc will be linked from the css file, so we only need to link the javascript and the css file.


	<head>
		<link href="tabs.css" rel="stylesheet" type="text/css">
		<script type="text/javascript" src="tabs.js"></script>
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
		<title>testfile</title>
		<meta name="tutor" content="Bob de Ruiter">
		<meta name="author" content="Your Name">
		<!-- Date: 2011-07-07 -->
	</head>

Step 2: HTML <body>

This is the plan:

The container, header and the content are div layers. The container contains everything and its function is to prevent the content from shrinking more than 800px. The header is the tab system and the content speaks for itself. The tab system consists of an unordered list which we’ll align horizontally. Each list item contains a link to another page. With this in mind it isn’t hard to come up with the markup:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html lang="en">
    <head>
        <link href="tabs.css" rel="stylesheet" type="text/css">
        <script type="text/javascript" src="tabs.js"></script>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <title>testfile</title>
        <meta name="tutor" content="Bob de Ruiter">
        <meta name="author" content="Your Name">
        <!-- Date: 2011-07-07 -->
    </head>
    <body>
        <div id="container">
            <div id="header">
                <ul><li><a rel="external" href="index.php">DSNR Home</a></li><li><a rel="external" href="protect.php">Protection</a></li><li><a rel="external" href="brain.php">Brainz!</a></li></ul>
            </div>
            <div id="content">
                content
            </div>
        </div>
    </body>
</html>

This is what we have so far:

It has all its functionality, but clients are never going to be happy with such a simple design. Good news for you..


Step 3: CSS CSS2 Only

So we need some basic styling. Create tabs.css and place it in the same folder as index.php

We start with styling the body and the container:

body {
	margin: 12px -12px;
	background-color: #003;
	font-family: Arial, "MS Trebuchet", sans-serif;
	font-size: 16px;
	width:100%;
}
#container {
	width: 800px;
	margin-left: auto;
	margin-right: auto;
}

Neither element has anything special, except for the margin. The container is easy: if margin-left and margin-right of an element (with a defined width) are set to auto, every browser will try to center that element.

The negative horizontal margin of the body isn’t that hard either. The width of the container is 800px for compatibility with old screens, but we are going to add rounded corners to them, each with a radius of 12px. This makes the corners shrink, so we add a padding of 12px to the container.


Step 4: Result Thus Far

Check what you’ve built so far. If the screen is too small (below 800px), you’ll notice the rounded borders at the left disappear. They are unnecessary so our negative margin tucks them nicely out of sight.

To keep things readable, we should also change the background color of the content and the tabs.

#header ul {
	background-color: #566AAA;
}
#content {
	background-color: #FFF;
}

Step 5: JavaScript Domready

Before we continue styling the tabs, we want to add one of the most important (and brilliant, though I say it myself) parts of this: the selected tab should be white. We aren’t going to change the tabs on every page. We let javascript do the dirty work.

if (document.addEventListener)
  document.addEventListener("DOMContentLoaded", function(){ran=1; init()}, false)
else if (document.all && !window.opera){
  document.write('<script type="text/javascript" id="contentloadtag" defer="defer" src="javascript:void(0)"><\/script>')
  var contentloadtag=document.getElementById("contentloadtag")
  contentloadtag.onreadystatechange=function(){
    if (this.readyState=="complete"){
      ran=1
      init()
    }
  }
}
window.onload=function(){
  setTimeout("if (!ran) init()", 0)
}

This code runs the function init (which doesn’t exist yet) when the content is loaded. If you only use window.onload, the javascript will execute after all external files (like images, stylesheets) are loaded. This will result in a naked website for a few seconds, without the cover of javascript. And yes, your host will ban you for publishing adult content…


Step 6: JavaScript init()

Let’s create that init function. This function detects which tab you’re currently viewing, removes its hyperlink (the visitor is there already) and assigns a class.

function init() {
	el = function(q) {return document.getElementById(q)};
	anchors = el("header").getElementsByTagName("a")
	for (a=0;a<anchors.length;a++) {
		if (anchors[a].href == window.location.href || anchors[a].href == window.location.href+"index.php") {
			anchors[a].removeAttribute("href")
			anchors[a].parentNode.className = "selected"
			break;
		}
	}
}

Step 7: JavaScript init() Breakdown

Allow me break it down for you.

el = function(q) {return document.getElementById(q)};

This is a shorthand for the long document.getElementById. It was useless this time, because I only used it once. But using it is a good habit to get into.

anchors = el("header").getElementsByTagName("a")
for (a=0;a<anchors.length;a++) {

This starts a loop through all anchor tags in the header.

if (anchors[a].href == window.location.href || anchors[a].href == window.location.href+"index.php") {

This is the most important part. The href attribute of an anchor returns the absolute path. If the href attribute is the same as the current page the visitor is on, then that’s the current tab. But if the visitor is in the home folder, he actually is on the home page.

anchors[a].removeAttribute("href")
anchors[a].parentNode.className = "selected"

This anchor doesn’t need a href attribute anymore. The parent node is the list item. We assigned the selected class to this item, which doesn’t exist yet. Remember what my great-grandfather said.

break;

If the script finds the current tab, it can stop searching.

This is what we have:

As you can see, the first link has changed to normal text.


Step 8: CSS Header and Content

OK, we’ve done the boring part. I’ve split the final styling in two parts: positioning and coloring.

We need to center the header and set its width to 600px. We can use the same trick as we used with the container. The content layer is as big as the container (coincidence?), so no need to center it.

#header {
	width: 600px;
	margin: 0 auto;
}
#content {
	background-color: #FFF;
	padding: 12px;
}
#header ul {
	background-color: #566AAA;
	margin:0;
}

If you don’t believe we’re making progress:


Step 9: CSS Tab list

I’m sorry you had to wait this long for this part, but we made the necessary preparations and we are ready to roll.

The tabs should be aligned horizontally and have the same width. The anchor tag should cover the whole tab.

#header ul {
	background-color: #566AAA;
	margin: 0;
	width:600px;
	padding: 0;
	list-style-type: none;
	overflow: visible;
}
#header ul li {
	margin:0;
	padding:0;
	display: inline-block;
	width: 33.3%; /*customize this!!!*/
	text-align: center;
	zoom: 1;
	*display: inline;
}
#header ul li a {
	width: 100%;
	height: 100%;
	display: block;
	padding: 12px 0;
}

If you’ve more or fewer tabs than 3, make sure you don’t forget to change the width of the list items!

By default, the display of a list item is set to ‘list-item’ which is very similar to ‘block’, except for the marker. Block elements act like paragraphs and divs; they try to stay off the same line. Anchor tags and most markup tags (strong, em) are inline elements. They don’t force a line break, but we can’t specify their width.

But there’s a third display type: inline-block. Inline-block allows you to change the width, but it doesn’t force the line break. span elements are displayed as an inline-block by default.

That’s exactly what we’re looking for. But there’s one drawback. IE 6 doesn’t support inline-block. zoom: 1; and *display: inline; take care of that. Only IE 6 and lower read rules starting with an asterisk.

No padding and margin for the ul and list items. Our ‘Home’ tab aligns perfectly at the left side and the last tab aligns perfectly at the right.

This looks more like it…


Step 10: CSS Fancy Stuff

We made a clean design. It’s fine, but ze dezign zjould havé impacte, like an italian client once said. And I couldn’t have said it better myself.

First you have to grab pie.htc from here. IE 6, 7 and 8 don’t support CSS 3, this is a fallback. Drop it in the public root folder of your site.


Step 11: CSS Gradients are Great

Instead of styling every list item, we give the whole list one big gradient and change the selected one back to white. Every browser type has a different prefix for gradients and CSS3pie expects the -pie- prefix.

#header ul {
	margin: 0;
	width:600px;
	padding: 0;
	list-style-type: none;
	overflow: visible;
	background-color: #566AAA;
	background: -webkit-gradient(linear, 0 0, 0 bottom, from(#9FB6CD), to(#003F87));
	background: -moz-linear-gradient(#9FB6CD, #003F87);
	background: linear-gradient(#9FB6CD, #003F87);
	-pie-background: linear-gradient(#9FB6CD, #003F87);
	behavior: url(/PIE.htc);
}
.selected {
	background-color: #FFF;
}

The background-color can stay. It’s a fallback for browsers which support neither CSS 3 nor pie.htc.


Step 12: CSS Round Those Corners!

Three elements need to be rounded: the whole navigation bar, the selected tab and the content. And again, every browser type requires a prefix, but pie doesn’t this time.

#content {
	background-color: #FFF;
	-moz-border-radius: 12px;
	-webkit-border-radius: 12px;
	border-radius: 12px;
	padding: 12px;
	behavior: url(/PIE.htc);
}
(...)
#header ul {
	-moz-border-radius: 12px 12px 0 0;
	-webkit-border-radius: 12px;
	border-radius: 12px 12px 0 0;
	margin: 0;
	width:600px;
	padding: 0;
	background-color: #566AAA;
	background: -webkit-gradient(linear, 0 0, 0 bottom, from(#9FB6CD), to(#003F87));
	background: -moz-linear-gradient(#9FB6CD, #003F87);
	background: linear-gradient(#9FB6CD, #003F87);
	-pie-background: linear-gradient(#9FB6CD, #003F87);
	list-style-type: none;
	behavior: url(/PIE.htc);
	overflow: visible;
}
(...)
.selected {
	behavior: url(/PIE.htc);
	box-shadow: 0 -3px 3px -1px #222;
	-moz-border-radius: 12px 12px 0 0;
	-webkit-border-radius: 12px;
	border-radius: 12px 12px 0 0;
	background-color: #FFF;
}

We are almost done…


Step 13: Dark Theme

First we change the color of the text to dark blue, remove the underline and punish it with early hair loss:

#header ul li a {
	text-decoration: none;
	color: #005;
	font-weight: bold;
	width: 100%;
	height: 100%;
	display: block;
	padding: 12px 0;
}

The color difference is pretty big, but the text doesn’t pop out. Stroking the text would be the solution. Safari and Chrome support text-stroke, but other browsers don’t. However, all modern browsers + CSS3pie support text-shadow.

#header ul li a {
	text-decoration: none;
	color: #005;
	font-weight: bold;
	width: 100%;
	height: 100%;
	display: block;
	padding: 12px 0;
	text-shadow: 0 1px 1px #AAA;
	behavior: url(/PIE.htc);
}

Step 14: The Content Files

index.php

<!-- start header.html -->
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html lang="en">
    <head>
        <link href="tabs.css" rel="stylesheet" type="text/css">
        <script type="text/javascript" src="tabs.js"></script>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <title>testfile</title>
        <meta name="tutor" content="Bob de Ruiter">
        <meta name="author" content="Your Name">
        <!-- Date: 2011-07-07 -->
    </head>
    <body>
        <div id="container">
            <div id="header">
                <ul><li><a rel="external" href="index.php">DSNR Home</a></li><li><a rel="external" href="protect.php">Protection</a></li><li><a rel="external" href="brain.php">Brainz!</a></li></ul>
            </div>
            <div id="content">
            	<!-- end header.html, start index.php, protection.php and brain.php -->
                content
                <!-- end content files, start footer.php -->
            </div>
        </div>
    </body>
</html>
<!-- end footer.php -->

tabs.css

body {
	margin: 12px -12px;
	background-color: #003;
	font-family: Arial, "MS Trebuchet", sans-serif;
	font-size: 16px;
	width:100%;
}
#container {
	width: 800px;
	margin-left: auto;
	margin-right: auto;
}
#header {
	width: 600px;
	margin: 0 auto;
	/*background-color: #FFF;*/
}
#content {
	background-color: #FFF;
	-moz-border-radius: 12px;
	-webkit-border-radius: 12px;
	border-radius: 12px;
	padding: 12px;
	behavior: url(/PIE.htc);
}
#header ul {
	-moz-border-radius: 12px 12px 0 0;
	-webkit-border-radius: 12px;
	border-radius: 12px 12px 0 0;
	margin: 0;
	width:600px;
	padding: 0;
	background-color: #566AAA;
	background: -webkit-gradient(linear, 0 0, 0 bottom, from(#9FB6CD), to(#003F87));
	background: -moz-linear-gradient(#9FB6CD, #003F87);
	background: linear-gradient(#9FB6CD, #003F87);
	-pie-background: linear-gradient(#9FB6CD, #003F87);
	list-style-type: none;
	behavior: url(/PIE.htc);
	overflow: visible;
}
#header ul li {
	margin:0;
	padding:0;
	display: inline-block;
	width: 33.3%; /*customize this!!!*/
	text-align: center;
	zoom: 1;
	*display: inline;
}
#header ul li a {
	text-decoration: none;
	color: #005;
	font-weight: bold;
	width: 100%;
	height: 100%;
	display: block;
	padding: 12px 0;
	text-shadow: 0 1px 1px #AAA;
	behavior: url(/PIE.htc);
}
.selected {
	behavior: url(/PIE.htc);
	box-shadow: 0 -3px 3px -1px #222;
	-moz-border-radius: 12px 12px 0 0;
	-webkit-border-radius: 12px;
	border-radius: 12px 12px 0 0;
	background-color: #FFF;
}

tabs.js

if (document.addEventListener)
  document.addEventListener("DOMContentLoaded", function(){ran=1; init()}, false)
else if (document.all && !window.opera){
  document.write('<script type="text/javascript" id="contentloadtag" defer="defer" src="javascript:void(0)"><\/script>')
  var contentloadtag=document.getElementById("contentloadtag")
  contentloadtag.onreadystatechange=function(){
    if (this.readyState=="complete"){
      ran=1
      init()
    }
  }
}
window.onload=function(){
  setTimeout("if (!ran) init()", 0)
}
function init() {
	el = function(q) {return document.getElementById(q)};
	anchors = el("header").getElementsByTagName("a")
	for (a=0;a<anchors.length;a++) {
		if (anchors[a].href == window.location.href || anchors[a].href == window.location.href+"index.php") {
			anchors[a].removeAttribute("href")
			anchors[a].parentNode.className = "selected"
			break;
		}
	}
}

Conclusion

Firstly, a final fiddle.

Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • qwerty

    yuk! is aesthetically scary

  • http://edwinhollen.com edwinhollen

    1. No HTML5 doctype?

    2. Using a div in place of the HTML5 header tag?

    This is a great tutorial, but it’s seriously teaching old markup.

  • http://www.arvag.net/ Gavrisimo

    “As we want our page to be compatible with IE 6-8, we use the “HTML – 4.01 Transitional” doctype.”

    Stopped reading here.

  • Pingback: Tabbed Navigation: Adding CSS Classes Dynamically | Shadowtek | Hosting and Design Solutions

  • http://nightfirecat.right-0n.com/ Jordan

    Sorry, but this method of adding CSS classes seems rather archaic: there’s no need for Javascript. I much prefer Chris Coyier’s method of adding an ID to the body tag, and using CSS to target specific links from there.

  • Lucas Rolff

    In my opinion, I found this post lazy written..

    We use HTML4 Doctype because it needs to be supported in IE6-8 – What I know HTML5 Doctype is supported too, use header { display: block; } and so on.. it’s not very hard to make tags like header, nav, article, footer and so on working in IE..

    Working in IE6.. – Please just stop making websites for that browser.. There is a reason why Microsoft, and every other webdeveloper want IE6 of the run… Please then don’t make support for it.

    Using Javascript for changing active state.. – Could be done easier with PHP (since ur coding in a index.php file)..

    Languages: HTML, CSS, Javascript, .htaccess..

    Where is the .htaccess?

    • http://www.snaptin.com Ian Yates

      Good point about the .htaccess – that should have been removed from the tutorial details as the second half of this tut (which involves .htaccess and more php) is due for publication on Nettuts+

      There are many ways to skin a cat, but I think (ignoring Doctype etc.) the strength of this tut lies in its use of JavaScript to highlight menu items; dynamically detecting the url and assigning classes on the base of that is a great approach.

      You could equally use server side scripting, personal preference I guess.

  • http://www.warrenwebdesigns.com Patrick

    This was a really really awful post from what is usually a reliable site.
    I’m sorry, but there is no good reason to keep catering for old versions of IE. Not to mention, like one of the other commenters said, that there is more than one way to skin a cat however some of the methods you used here are just weird.

  • http://www.jeffadams.co.uk Jeff Adams

    I really hate to post negatively on any TUts sites but I have to agree with some of the comments on here.

    There’s nothing wrong with the tutorial approach and it’s well written as always but the subject just doesn’t really lend itself to modern web development – it just seems way too clunky for my liking.

    I hope there are some folks for which this is exactly what they are looking for but I thought this was meant to be a designers tuts site – this seemed way more of a Nettuts+ tutorial.

    Sorry. I say this through gritted teeth because i LOVE Envatos sites.

  • Pingback: xhtml css templates – Tabbed Navigation: Adding CSS Classes Dynamically | Webdesigntuts+ | XHTML CSS - Style sheet and html programming tutorial and guides

  • http://www.theinternetlaboratory.com/ Paul

    I’m rather disappointed that a reliable tutorial website such as this is teaching old mark up. It doesn’t promote advancement in the industry. Also, tabbed navigations aren’t exactly new to the Internet. Why is there an article on tabbed navigation using fairly old CSS techniques? I hate to post negative comments, but it’s got to be done.

  • Bob de Ruiter

    I’m using public wifi, no time to sign in. You have to believe me: I’m me. Answers:

    We don’t use html5 features, why should we specify a html5 doctype?

    I never thought I’d get criticism on adding too much support. Pie.htc works better in IE6 than IE8 for some reason, so implementing support isn’t very hard.

    The second part, the server-side scripting, will be posted on nettuts if they like it.

    Header tags seem like fun, but why?

    • http://edwinhollen.com edwinhollen

      1. The HTML5 doctype is the shortest doctype which will tell the browser to use standards mode. It also prepares the document for future HTML5 content; future-proof.

      2. A header tag makes more sense than a div. Simply skimming the document, I can tell which part is the header, without having to sift through IDs and classes.

      • Bob de Ruiter

        If the header tag is supported everywhere, I’ll agree. Id=”header” is clear. Google gets it.

        The DOCtype is probably nostalgic, I also use “” around the attributes. Nobody complains about that, luckily.

        • Mark

          Sorry Bob, but from where you say that google ‘get it’???? … google or other robots only see a DIV with an id or class nothing else… they don’t look what’s the name of the id…

          Now robots will see a HEADER tag and say… hey that’s the header…

          Really, this was explain it for a lot of guys…

          And about the “” you can use these or not that’s your choice… this is a specification on the html5 you can use /> to close an or don’t…

          Start reading more articles on nettus and you’ll see.

          Greetings.

          Sorry my poor English.

  • Aadhi Vive

    Thank you very much for this tutorial!. It was very helpful. I learnt to use the location.href properties.

    Best Regards!