Andreas Blixt

Tabbed Navigation Using CSS

  1. Introduction
  2. Step 1: The structure
  3. Step 2: The image
  4. Step 3: Design
  5. Bonus: JavaScript
  6. Feedback

Introduction

The contents of this tutorial – including the images, HTML, JavaScript and CSS – is licensed under an MIT license and is therefore free for you to use. Attribution is appreciated, but not required.

Hello, fellow web designer! My name is Andreas Blixt. I'm glad you decided to take the time to read my tutorial! Hopefully you'll be on your way to creating tabs on your website in no time!

This tutorial will teach you how to create low-bandwidth tab navigation on a web page using CSS. As an extra bonus you'll also learn how to switch tabs without loading the page more than once.

Throughout the tutorial, I try to explain most of the things I am showing you, at a somewhat basic level. I have left some things out on purpose, because I assume you can figure them out on your own (for example that the CSS property border-color: #4488ff; will change the color of the border to #4488ff [and again, I am assuming you know how an RGB color in hexadecimal works.])

This tutorial follows a few guidelines of mine, which mostly revolve around the accessibility of the web page. Here they are:

If your browser is capable of styling content, you'll notice that the text in some paragraphs stand out more. These are paragraphs that I've marked as "important", so that you can skip some text without worrying you missed anything really important. Look at the next paragraph for an example:

Now that we've got that out of the way, let's get started!

Other languages

This article is also available in the following languages:

Step 1: The structure

Cooking up the HTML

If you were to make a copy of this page in your favorite HTML editor/generator, how would you do it? Hopefully not with tables, in-line styles or anything that implies the current look of the page. Sure, it works, but it generally means less compatability across media, more bandwidth spendage and less readable code. Which in turn leads to angry users, bigger bills and grumpy developers. Right now you're probably wondering what I'm suggesting instead. Well, imagine that this page is an ordinary text document. You'd want a title, a table of contents and then the content. Now you need to put that into HTML.

The title

Not relevant to this tutorial, but I'll cover it because I mentioned it. HTML has six elements that each represent a level of heading. For level one, you use <h1>. For level two, <h2>, and so on. The HTML for a title heading might be <h1>My Homepage</h1>. Easy.

The table of contents

On a web page, the navigation can be thought of as a TOC. You expect the TOC to direct you to the content you want to access.

In HTML, the element which best represents a TOC is the <ol> element. OL stands for "ordered list". There is also <ul>unordered list. You can use <ul> if your navigation doesn't have a defined order, but I'll be using <ol>. No matter which one you choose, the entries in the list are represented by <li>list item.

Here's the code for making a simple ordered list:

<ol>
    <li>Item 1</li>
    <li>Item 2</li>
    <li>Item 3</li>
</ol>

Result:

  1. Item 1
  2. Item 2
  3. Item 3

What we'll want is an item with a link for each section (i.e., tab.) A simple TOC could look something like this:

<ol id="toc">
    <li class="current"><a href="page1.html">Page 1</a></li>
    <li><a href="page2.html">Page 2</a></li>
    <li><a href="page3.html">Page 3</a></li>
</ol>

That's it! The HTML above conveys that we want an ordered list of items that contain links to other sections of a site. You'll see that a couple of the elements above have some attributes set. Namely, the class and id attributes. The class attribute should be considered as metadata for the element it is applied to. For example, one of the list items has the class current. This tells us that that item is the one that is currently selected. The id attribute uniquely identifies a particular element. The <ol> tag has the ID toc. This lets us access it programmatically. Another feature of uniquely identified elements is that the browser scrolls to them if you add their IDs as an anchor to the URL (Example of an anchor: http://www.example.com/path/to/page.html#this_is_the_anchor).

The current list item could be uniquely identified using the id attribute because there's currently only one at a time, but since it's not a constant element and we might, for some reason, have two links (tabs) active at the same time, I chose to use the class attribute.

The content

Content is generally made up of one or more headers, some paragraphs with text and every now and then some other stuff such as images, lists, quotes etc. We'll want to encapsulate the content so that we can put other things outside it without having trouble identifying it later on. A <div> (division, section) element is good for this, because it's sole purpose is to hold a collection of elements. We'll give it an identifying class, because there might be other <div> elements on the page:

<div class="content">
    <h2>Page 1</h2>
    <p>Text...</p>
</div>

Putting it together

In order for the HTML snippets to work, we'll have to make a HTML file. I'll go through the document in parts and explain each one. Here we go:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

For the file to validate, it needs a document type. The document type specifies what rules the document must follow for it to be valid. I usually go with XHTML 1.0 Strict, but there are other document types that are more... forgiving. I won't be covering them in this tutorial, however.

<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>My Homepage</title>
    <link href="tabs.css" rel="stylesheet" type="text/css" />
</head>

The <meta> tag holds metadata about the document. In this particular case, it holds the character encoding of the page, so that the browser doesn't have to guess. The <link> element is used for linking to external resources, in this case a stylesheet. We'll be making the stylesheet in step 3.

<body>
<h1>My Homepage</h1>
<ol id="toc">
    <li class="current"><a href="page1.html">Page 1</a></li>
    <li><a href="page2.html">Page 2</a></li>
    <li><a href="page3.html">Page 3</a></li>
</ol>
<div class="content">
    <h2>Page 1</h2>
    <p>Text...</p>
</div>
</body>
</html>

With all the HTML put together, the page should look like this: page1.html.

Save the HTML code as page1.html. In the next step, we'll be going through how to make an image for the tabs.

Step 2: The image

That's right, we'll only be dealing with one image. It doesn't matter if we have five different states for the tab, it can all be done with one image. I'll start by answering the obvious question: Why?

Splitting the image into pieces left, middle and right requires six separate images for two states. If we were to add another state, for example a hover effect, the number of images required would increase to nine. Other than extra file management and more work when editing the images, what does this mean?

Hopefully I've convinced you that putting everything into one image is much better than using several images. Moving on...

Each state will take up one row of the image and will be 500 pixels wide. The idea is that the left side of the tab will be drawn in one element, then the rest will be drawn on top of it in a child element. I will explain how in the next step. Each row is 60 pixels high. This will allow the tab to be up to 60 pixels high, but it can be smaller.

Now, you might be worried. Worried about the "500 pixel" bit of my previous paragraph. Don't be, though. We will be saving this rather big image as a GIF image, which in my case took it down to just a couple of kilobytes. If you know that you will never have tabs even close to 500 pixels wide, you can of course shrink it. Even 200 pixels might be enough. But thanks to compression, the difference probably won't be more than a few bytes.

Here's the image I made:

Save this image where you saved page1.html, or make your own image. Name it tabs.gif. All we have to do now is to put it to use. Continue on with step 3.

Step 3: Design

Making it spicy with CSS

First let's go over the syntax of CSS. It's pretty simple. In CSS, you first select an element by specifying a filter, then inside curly braces you specify its styles in the form name: value;. For example, you can make all <p> elements red by specifying p { color: red; }.

Adding a # (number sign) after the element name followed by an element identifier (whatever you put in the id attribute of an element) will only style that one element. For example: img#logo { border: #000 solid 3px; } would give the image <img alt="Acme Corporation" id="logo" src="logo.jpg" /> a black border.

You can also add a . (period) after the element name and specify a class name to filter out only the elements with that particular class name. For example: p.error { color: red; } changes the color of <p class="error">Error!</p>.

It's also possible to filter by a hiearchy of elements which means you require the selected element to be inside another element. For example: div.error p { color: red; } changes the color of the <p> element in <div class="error"><p>Error!</p></div>.

That covers the CSS syntax you'll be seeing on this page. For information about CSS properties and syntax, you can read more at W3Schools, CSS2 Reference.

We'll be making the CSS file tabs.css that is referenced by page1.html, which you made in step 1. Feel free to create tabs.css now and save it where you saved page1.html and tabs.gif, and fill it in as we go. That way you can open page1.html in your web browser and see the changes as we go through the CSS below.

The tabs

The first thing we'll do is to style the ordered list that represents our TOC (<ol id="toc">):

ol#toc {
    height: 2em;
    list-style: none;
    margin: 0;
    padding: 0;
}

So, what's happening here? I'm setting the height of the TOC (which will be our bar of tabs) to 2 em units (which is twice the height of the current font.) In most browsers, this is automatically calculated because all the list items (tabs) will be 2 em units in height, but in Microsoft Internet Explorer 6 it isn't.

Next, I remove the little bullets next to the list by removing its list style.

The margin and padding are both set to 0 to avoid any spacing inside and outside the list. You can read more about how margin and padding works in CSS2 Specification, Box Model.

Next we'll style the default state for each list item (tab) inside the TOC:

ol#toc li {
    background: #bdf url(tabs.gif);
    float: left;
    margin: 0 1px 0 0;
    padding-left: 10px;
}

The background property sets anything that has to do with the background (You can also use separate properties such as background-color.) In this case, I'm setting the background color to something similar to the color of the inactive state of the tab for when the background image, tabs.gif, isn't available (it hasn't loaded, browser has images turned off, etc.)

Each tab should float to the left. This means that instead of being placed one by one vertically, the tabs will be placed one by one horizontally, to the left of any content inside their container (the other tabs, in this case.)

margin: 0 1px 0 0; means the same as margin-top: 0; margin-right: 1px; margin-bottom: 0; margin-left: 0;. I am resetting all margins of the tab because some browsers give the <li> element a margin by default. The 1 pixel to the right leaves a tiny gap between the tabs. You can adjust this value to your liking.

Last, but not least, I set the left padding to 10 pixels. This is an important value which depends on the tab image. It has to be at least as wide as the left part of the tab. Without the padding, the left part will be covered by the background image of the <a> element. Which brings us to...

ol#toc a {
    background: url(tabs.gif) 100% 0;
    color: #008;
    float: left;
    line-height: 2em;
    padding-right: 10px;
    text-decoration: none;
}

For the background, I'm using the same thing as for the <li> element, with added position coordinates. 100% 0 means 100% to the right, no offset vertically. Since the <a> element is inside the <li> and thus on top of it, this background image will be drawn over the background image of the <li> element. So, the only visible part of the <li> element will be the 10 pixels of padding that we specified earlier. Together, these background images form a whole tab which can be anywhere from 20 to 490 pixels wide.

Next I specify a dark blue color for the text inside the tab. Since the text is a link, we have to specify the color specifically for the <a> element, because otherwise the default color (usually clear blue) would overwrite it.

The element should have display: block; because it should act as a block element, rather than an inline element (i.e. text.) This means that I can give it padding, margin, width, height and all that. However, since MSIE6 gives the element the wrong width, float: left; is used because it implies display: block; with the addition of making the element shrink to its smallest possible size (taking content and padding into account.) We don't have to worry about the element "floating" since it has no sibling elements.

I give the element a line height because it's essentially the same as height (as long as the text stays on one line), with the difference that it is vertically centered.

outline: none; is there simply to hide the outline around links when they have focus in certain browsers, for example Mozilla Firefox.

The padding to the right should be just as much as it was for the <li> element, so that the text will be centered. If you want more spacing to the left and/or right, you modify the left padding in the <li> element and the right padding in this one.

Phew... All that's left of the tabs now is to offset our tab image when a tab is selected and tweak the font styles a bit! Go go go!

ol#toc li.current {
    background-color: #48f;
    background-position: 0 -60px;
}

For the currently selected tab, I'm changing the background color to something that fits the color of the active state of the tab. More importantly, I'm changing the offset, or position, of the background image to move 60 pixels up. This is because one row in the tab image was 60 pixels high, and I want the second row, so I move the image up, revealing its lower part.

Notice that I'm not using the background shortcut here. That's because I only want to override some of the properties, and leave the background-image property as it was.

ol#toc li.current a {
    background-position: 100% -60px;
    color: #fff;
    font-weight: bold;
}

Same thing as the last one, except this time it's the right part of the tab. I've also set the text to be bold and white.

We're almost there...

Now we just have to finish off by styling the content container we put in our HTML code earlier (<div class="content">.)

div.content {
    border: #48f solid 3px;
    clear: left;
    padding: 1em;
}

First we give it a border. border: #48f solid 3px; is shorthand for border-color: #4488ff; border-style: solid; border-width: 3px;.

The next property, clear: left; tells the browser to not let any element with float: left; to float next to it. Since our list items have float: left;, the content (which comes after the tabs) must have clear: left; or it would be placed to the right of the tabs, rather than below them.

padding: 1em; gives the content a padding of 1 * [current font height].

The result

Okay so we've styled all the elements now. Didn't need much CSS for that now did we? Let's put the CSS together into tabs.css and see what our HTML looks like now: page1.html. Tabs! I've added page2.html and page3.html so you can click them too. Each page has the current class set for the appropiate <li> element. Here's all the CSS in one go for a better overview:

ol#toc {
    height: 2em;
    list-style: none;
    margin: 0;
    padding: 0;
}

ol#toc li {
    background: #bdf url(tabs.gif);
    float: left;
    margin: 0 1px 0 0;
    padding-left: 10px;
}

ol#toc a {
    background: url(tabs.gif) 100% 0;
    color: #008;
    display: block;
    float: left;
    height: 2em;
    line-height: 2em;
    padding-right: 10px;
    text-decoration: none;
}

ol#toc li.current {
    background-color: #48f;
    background-position: 0 -60px;
}

ol#toc li.current a {
    background-position: 100% -60px;
    color: #fff;
    font-weight: bold;
}

div.content {
    border: #48f solid 3px;
    clear: left;
    padding: 1em;
}

You can download all the files as a zip-file: tabs.zip

Supporting hover effects

There is one more thing you need to do, if you want a hover effect on your tabs that works in MSIE6 as well as the other browsers.

Since MSIE6 only supports the :hover pseudo-class on the <a> element, we have to adapt our code to that fact. Adding the :hover pseudo-class to the <a> element right now would only give us access to change the middle and right part of the tab, not the left part, since it's in the <li> element. The solution is pretty simple: We add another element inside the <a> element. The best element for this task is the <span> element; it is made to only contain text. Here's the new HTML code for the TOC:

<ol id="toc">
    <li class="current"><a href="page1.html"><span>Page 1</span></a></li>
    <li><a href="page2.html"><span>Page 2</span></a></li>
    <li><a href="page3.html"><span>Page 3</span></a></li>
</ol>

Unfortunately, this doesn't make much sense structurally, but if you want cross-browser :hover support, this is probably the best way to do it.

Next up is adding another state to the tab image. This shouldn't need much explanation. Here's my new image:

Finally, we need to move everything down a step in the stylesheet, to accommodate for the new <span> element. Here's the whole CSS again, modified for our needs:

ol#toc {
    height: 2em;
    list-style: none;
    margin: 0;
    padding: 0;
}

ol#toc li {
    float: left;
    margin: 0 1px 0 0;
}

ol#toc a {
    background: #bdf url(tabs.gif);
    color: #008;
    display: block;
    float: left;
    height: 2em;
    padding-left: 10px;
    text-decoration: none;
}

ol#toc a:hover {
    background-color: #3af;
    background-position: 0 -120px;
}

ol#toc a:hover span {
    background-position: 100% -120px;
}

ol#toc li.current a {
    background-color: #48f;
    background-position: 0 -60px;
    color: #fff;
    font-weight: bold;
}

ol#toc li.current span {
    background-position: 100% -60px;
}

ol#toc span {
    background: url(tabs.gif) 100% 0;
    display: block;
    line-height: 2em;
    padding-right: 10px;
}

div.content {
    border: #48f solid 3px;
    clear: left;
    padding: 1em;
}

View demonstration page

I've added ol#toc a:hover and ol#toc a:hover span for the hover effect, and moved most of the properties down one step (liaspan).

You can download all the files as a zip-file: tabs-hover.zip

The end?

That concludes this tutorial. Please leave your feedback in the feedback section.

As you might have suspected, this page is using the same styling methods for its tabs as the ones you have just learned. One thing you might have noticed is that you haven't actually left the page while switching tabs; you've been on the same page the whole time. This is achieved through JavaScript. Go to the bonus section to learn how.

Bonus: JavaScript

Tab switching with JavaScript

I won't go through the JavaScript code like I went through the HTML and CSS. Instead, I'll tell you what you need to do to your HTML and CSS for this to work, and provide you with the JavaScript code. The JavaScript code has been commented, so it should be fairly easy to figure out what's going on if you've used JavaScript before. But first I'll list some information about using this script:

The JavaScript works by taking a list of identifiers of elements – that is, the values of the id attributes of the elements – on the page, and then scanning the page for all anchors (e.g., <a href="#element-id">anchor</a>) to those identifiers. When any of these anchors are clicked, the class of the element it references and all anchors referencing the element will be changed to active. The class of the other elements in the list and the anchors referencing them will be changed to inactive.

Here's the HTML for our previous example document (page1.html), modified to work with the JavaScript:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>My Homepage</title>
    <link href="tabs.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h1>My Homepage</h1>
<ol id="toc">
    <li><a href="#page-1"><span>Page 1</span></a></li>
    <li><a href="#page-2"><span>Page 2</span></a></li>
    <li><a href="#page-3"><span>Page 3</span></a></li>
</ol>
<div class="content" id="page-1">
    <h2>Page 1</h2>
    <p>Text...</p>
</div>
<div class="content" id="page-2">
    <h2>Page 2</h2>
    <p>Text...</p>
</div>
<div class="content" id="page-3">
    <h2>Page 3</h2>
    <p>Text...</p>
</div>
<script src="activatables.js" type="text/javascript"></script>
<script type="text/javascript">
activatables('page', ['page-1', 'page-2', 'page-3']);
</script>
</body>
</html>

View demonstration page

This is what has been done:

  1. The activatables.js file is included.
  2. The content of the three pages have been put into separate container elements with unique identifiers.
  3. The links have been changed to refer to the elements.
  4. The activatables function is called1 with page as the parameter name2 and a list of the identifiers of the pages.
  5. Since the activatables function uses the terms active and inactive rather than current (for the classes), the CSS has been changed to reflect this difference.

1 activatables must be called after all elements involved have been parsed. You can ensure this by either putting the <script> tag below the content, or by calling the function after the DOM has finished loading. How to detect when the DOM has finished loading varies depending on the browser, and I will not be going through how to do it here. Check out the jQuery library for a light-weight and simple solution.

2 The parameter name is used to generate the address (http://www.example.com/path/to/page.html#page=page-1.)

The code

The JavaScript code can be downloaded from the following link: activatables.js

If you're interested in using the code beyond the scope of this article, be sure to check out the following two examples of how to use the code:

You can download all the files as a zip-file: tabs-js.zip

Keeping in line with my low bandwidth policy, I recommend that you "minify" the code. This will make the code much smaller by removing whitespace and comments etc. For doing this, I recommend the YUI Compressor.

Feedback

Want more tutorials? Check out SimpleEditions, an open-source tutorial site being developed by me and some friends!

So, what did you think?

Please tell me what you think of my tutorial. Was it helpful? Did you miss something? Was I too vague? Did I write too much about anything in particular? Constructive feedback will make sure I write more and better tutorials! Feel free to ask any questions too.

The best way to reach me is to send me an e-mail. My e-mail address is andreas@blixt.org. You can also reach me on Twitter as @andreas_blixt.

Share it!

Hopefully you liked my tutorial. If you did, it'd be great if you shared it with your friends! The more the merrier, right?

To show your support for this article, you can share it with various services:

Bookmark and Share