Site in an Hour

a big purple eye

Making Simple Work of Complex CSS Layouts

by - February ‘05

What are we trying to create?

The Design

Here's what our designer has given us to work with: A simple, clean design with three columns, horizontal navigation and a variable-width header. For the purposes of this demonstration, we will assume our designer has sliced any graphics into the necessarry pieces.

design mock up

Design Notes - Basic Elements

Rough Code Map

design mock up

Mark-up Plan — Containers

While it is always preferable to directly style semantic (x)html, using containers is often necessarry due to varying content and a willingness to avoid adding box-model hacks for IE<6

Which sections will require containers (divs)?

Further Code Considerations

Bring on the Code!

Now that we've planned out how we are going to make the design a reality, it's time to get get your hands dirty.

The first thing that needs to be added to our basic XHTML shell is our (empty) CSS file.

How will we import our CSS? That decision is guided by our intended browser support. Since we don't want to send any CSS to Netscape 4 or IE mac, we will use the following @import method...

CSS Negotiation


<style type="text/css" media="screen,projection">
    /* backslash hack hides from IEmac \*/
	    @import url(default.css);
    /* end hack */
</style>

What does that do?

By setting the media attribute to screen,projection (note comma seperated list — do not add a space), we are locking out NN4 and also telling Opera to use this style sheet in projection mode (full-screen mode).

Because we don't want to feed IE/mac a style sheet which it cant handle, we include the ‘backslash hack’ to send that browser on its merry way.

Now we are serving our CSS to all intended browsers, while legacy browsers are left with unstyled mark-up.

Those who have read my article on this subject would know that I prefer to keep all IE specific hacks in an external file and reference that file via conditional comments. For this project, however, we will be retaining all IE hacks (valid hacks!) in the main CSS file to keep down the number of server requests per visitor.

Setting the Limits

I mentioned previously that we will use a wrapper div to hold our design together, but this div also fills a much more important role &mdash it will control the scaling of the design.

% or em? BOTH!

In an ideal world, our design would expand and contract to fit within the available screen real-estate while still maintaining a legible line-length regardless of font-size or resolution.

He's gone mad with power!

Not at all (well, maybe a little); this utopian goal is achievable — even in IE5!

Part 1: Holding it Together; Letting it Flow


div#outer {
	width:94%;
    min-width:40em;
    max-width:70em;
}

By combining a percentage width with em min. and max. widths, we can be assured of the following:

Part 2: Setting the Standard

There are three properties I set at the beginning of every style sheet; padding, margins and a base font-size.

My preferred method is to globally reset all margins and padding to zero and assign them to elements as needed. I prefer this over instances of margin:0;padding:0; all through my CSS and I have repeatedly found it to cut the development time required for CSS layouts.

For font sizing, I use the method of setting a base size for the body element and em for all other elements, starting at 1em for the content text. For more information regarding the issues relating to even font-scaling, take a look at this.


* {
    margin:0;
    padding:0;
}
body {font-size:90%;}

The reason the font-size property is not declared within the global ruleset is that it has undesirable affects in relation to nested elements — the deeper you go, the smaller they get!

Setting the Standard - Links

There is one part of styling links that many fail to remeber — hovering with a mouse is not the only method of focusing on a link. Many users, myself included, use the tab key to skip through links on a page. To accomodate these users, we add a visual cue to the :focus pseudo-class.

Once again, IE/PC comes to the party and leaves a horrid mess; in this instance the problem is that IE does not render the :focus pseudo-class, but it does treat :active as :focus

Here's our basic link styles, with the :focus and :active pseudo-classes added:


a {
	color:#4C53E0;
}
a:focus, a:hover, a:active {
	color:#EB8518;
}

If you have trouble remembering the order for these declarations; this lymeric is a great help:
Lord Vader's Former Handle, Aniken

Part 3: The Banner

Time to get serious — our designer has presented us with a design that contains a fluid-width banner with vertically centered text. Since we can't use the v-align attribute in XHTML, we'll use line-height to achieve the same effect.

XHTML


    <div id="header">
        <h1>GeneriCo.</h1>
    </div>

CSS


#header {
	background:#EBEBE9 url(img/banner-bg.jpg) repeat-x left bottom;
}
#header h1 {
    font:bold 3em/2.5 "Lucida Bright", Georgia, Times, serif;
    background: transparent url(img/banner.jpg) no-repeat right bottom; 
}

By using line-height to achieve even vertical padding and attaching our non-repeating image (the buildings) to the bottom right corner of the h1, we have reliable vertical spacing and image placement regardless of font-size. Get used to that term, I love anything that works regardless of font-size.

Part 4: Navigation

The design for the menu is reasonably simple, so I won't go into excessive detail.

Here is the basic CSS:


ul#nav {
	list-style:none;
	text-align:center;
	background:#fff;
}
#nav li {
    width:25%;
    float:left;
    display:block;
    text-align:center;
    background:#EAF0E6 url(img/mnu-btm.gif) repeat-x left bottom;
}
#nav a {
	display:block;
	font:bold 1em/1.8 'Lucida Grande', Arial, tahoma, verdana, sans-serif;
	text-decoration:none;
	background:transparent url(img/mnu-top.gif) repeat-x left top;
}

Each of the four menu items is assigned a width of 25% and floated, causing them to line up horizontally. To create the button effect in from the intial design, two small gradient images are tiled along the x-axis of the link and its parent li. Once again, line-height is employed for the purpose of vertical alignment.

Part 5: Columns

As we discussed earlier, to place our content in the optimal position within the source (ie: as early as possible) we will need to add an extra div (hereby known as #sub), which wil be floated to the left. The first div within #sub will be floated right and will then sit in the center of the comlete layout.

Our design dictates the following widths for the columns:

layout map

Columns Continued

Simple, right? Not quite — because of our newly aquired #sub wrapper, we have to calculate the widths of #left and #center according to their parent element.

layout map

While it may seem confusing at first, it's actually pretty straight forward. Our #sub wrap takes up 75% percent of our content area. Within, #center takes up 66% of it's parent (#sub), which translates to 50% of our content area. #left fills the remaining 33% of #sub and #right is assigned a width of 25% due to the fact it is not nested within #sub, so it's width is calculated accroding to our entire content area.

To avoid the need for box-mdel hacks (which become nightmare-ish when dealing with this sort of layout), I have reduced the width of each column slightly, using the remaining gaps between them as the column ‘gutters’

Part 6: Content

Now that we've got three well-proportioned columns, it's time to fill them up with content. The main point I want to make regarding content is in relation to units of measure for padding and margins.

The best way to achieve a consistant design regardless of resolution or font size is by using percentage values for all horizontal spacing and em for all vertical spacing.

Here is a simple example:


p { margin:1em 5%; }

Enhancing Legibility

By default, web text is rather messy. The leading (line-spacing) is tiny and the words are crammed together in an unappealing manor. Here's how we fix that:


#sub, #right {
	font: 1em/1.5 'Lucida Grande', arial, verdana, sans-serif;
	word-spacing: 0.1em;

By defining our word-spacing in em, we are assured of consistency regardless of font size.

Part 7: Left Column

left column

The left column contains an image which our designer assures us draws the eye towards the 'Catalogue' link in the menu.
We think he's had one too many lattes, but we'll keep him happy anyway.

This image is assigned as the background for #left and is anchored to the top right corner.

To overcome the problem of 'running out of image' as the column expands vertically, we asked our designer to make up a version of the image which fade to white at the left edge. While the gradient can often be less than ideal visually, I find this approach to be good balance between having 'image to spare' and saving every bit of bandwidth. A vertical example of this technique can be found in the banner I made for brothercake.com

Part 8: Right Column - Form Madness

right column

Forms provide a wealth of styling hooks without extraneous mark-up, so that isn't a concern. What is a concern is the width of the input elements. Once again, our simple rule of setting all horizontal measures using % saves the day.

To make maximum use of the space available, we will assign a width of 98% to the text inputs, with the extra 2% allowing for padding within the form.

To differentiate between various input types, we have to add a class attribute (.txt in this case) — this will not be necessary once all common browsers support the full CSS2 selector set.

The ideal method of differentiating between input types would be to use attribute selectors:


input[type=text] { /* text input styles */ }
input[type=submit] { /* submit input styles */ }

Part 9: The Footer

The footer is a simple affair, with a horizontal menu and a short copyright notice.

Contrary to the main navigation, the footer nav. uses inline lis to minimise the horizontal space required. Because we don't add presentaional mark-up, the 'pipe seperator' is emulated using a CSS border.

Here is the CSS for the footer menu


#footer ul {
	list-style:none;
	margin-top:0.7em;
}
#footer li {
	display:inline;
	border-right: 1px solid #C8DCC2;
	padding:.3em 2%;
}

Although this breaks our strict grid layout, it also reduces the probability of the copyright notice causing the menu to line break.

Part 10: Adding a Behaviour Layer

Semantic XHTML is thing of beauty, perhaps; but a dash of unobtrusive javascript is a great way to tip the usability scales in your favour.

Text inputs often contain examples of intended input or further instructions relating to the field. This used to be required due to early browsers having issues with empty input elements.

A common addition to pre-filled input elements is this.select() attached to the onfocus event — this causes all text within the field to be selected upon focus of the element, allowing the user to start typing immediately without first deleting the existing content.

If javascript is disabled, the user will need to first delete the existing content, so why don't we use javascript to add it in the first place?

Behaviour Layer — Continued

This script will do exactly what we're after — add the filler text and also attach the onfocus behaviour.


function prepInput( id , text ) {
	//check that getElementById is supported and element exists
	if (!document.getElementById(id)) { return false; }	
		var elem = document.getElementById(id);
		elem.setAttribute('value', text);
		elem.onfocus = function() {
			this.select();
		}
}

To call the function, we add this script block to the head of the page:


<script type="text/javascript">
// <![CDATA[
    window.onload = function() {
        prepInput('searchQ', 'Enter search term');
        prepInput('subEmail', 'you@youraddress.com');
    }
// ]]>
</script> 

But we can take this one step further...

More Behaviour Layer

Another nice usablilty feature of CSS is the ability to add styling chages for currently focused form elements using the :focus pseudo-class. IE, of course, can't cope with such advanced CSS (bahaha!), but we can make it obey our commands by addinga few lines to the existing script


function prepInput( id,text ) {
	if (!document.getElementById(id)) { return false; }	
		var elem = document.getElementById(id);
		elem.setAttribute('value', text);
    // store class name
		var origClass = elem.className;
        elem.onfocus = function() {
    // append 'focus' to class, emulates :focus pseudo-class for IE 
        this.className += " focus";
        this.select();
    }
// return class name to origClass after element loses focus
    elem.onblur = function() {
    this.className = origClass;	
    }
}

Then in our CSS, we have this:


input:focus, input.focus { border-style:inset; }

Done!

Check it out: GeneriCo.

Anything Else?

So it's fluid, it's elastic, it's simple and it works. Did we miss anything, or have we met all our goals?

We've written clean, accessible code, we've made a flexible layout that will accomodate any combination of resolution and font size, so I guess we get the WAI-AAA stamp of approval...

Wrong!

There's one crucial element that our designer overlooked, one which we then also overlooked — contrast.

There isn't sufficient contrast between the content header anxc the white background, or between the menu text and its background.

Accessible code does not equal accessible design.

For more details on specific sections of the CSS, XHTML and Javascript, please refer to the source code. It is heavily commented and includes URLs of further reading.