Monday, January 7, 2008

As far back as I can remember, Web 2.0 has been synonymous with Table-less layout. And table-less layout has been limited by one major, major problem:
There is no way to set the width of two elements that are placed side by side.

The div is a 'block level' element. This means that it can receive a width and height. But it also means it cannot share a line inside its parent with any other element.
Ditto for the H1 - H6, Pre, P, and all other block level elements.

The span is an 'inline element'. This means that you can place as many spans as you need side by side on the same line. However, you cannot set a width on an inline-element and expect that width to be kept cross-browser.
Ditto here for the a, em, strong, and other inline elements.

The img and button elements exist they way we need - their dimensions can be set, but they do not need their own line. However, there is no easy way to put content inside these elements.

The CSS specs do have a solution: a style called inline-block. But inline block in IE is only kept by the span and a, and even thre it is buggy. In Firefox it is not kept at all.
(Note: inline-block works great in FF3, which is expected out in mid February. It is also up to par in IE8, expected out in mid 2009. Don't hold your breadth.)

There does exist one "easy" solution - floats. A floated div can have other divs alongside it in the same row, and they can have their widths and height set. But, they have plenty of their own baggage. If you are not familiar with floats I suggest you go learn them (at w3schools). Trust me that they are not the panacea you need to move into a table free environment.

Floats aside, there are three techniques that exist around the web (If someone knows another, please let me know).

1) Amending the button. [credit: Mozilla]
As said above, there are two elements that are inline-block by default: the img and the button. There exists no way to put anything into the img tag other other than an image or alternate text, so there is no real way to use it.
The button element does accept inner html, so as long as you clean up all the default css it can be used quite simply. Below is the neccessary code:css type="text/css"

button.fix{border:none; background:inherit; font:inherit; color:inherit; vertical-align: bottom;}

example usage (it seems blogger eats all the carrots):

button class="fix" style="WIDTH: 150px"
User
/button
input name="user"/
button class="fix" style="WIDTH: 150px"
Password
/button
input name="pass" type="password" /

This is a great technique for when you just have a little bit of text that needs a certain width, such as in the form above.
However it is semantically a sin, so for the same effort you might as well use tables.
Especially if you will be using it to house a large section of your html.
Also, the text inside the button is not selectable (after all, it is a button) so for anything more than a little text it is usually useless.

2) Filling out the span
3) Wrapping the div:
By far the most popular method for dealing with this problem is by wrapping the block element with an inline element (ie. put the div into a span). To paraphrase the w3c: The inside div is formatted as a block box, and the outer element is formatted as an inline replaced element.

Since this doesn't work out of the box, there are a few hacks:

a) The fieldset:
Set a fieldset's display to inline and remove the default css border and margin and wrap it around your div (or around a span, etc. that you set to display:block). Width and height properties should remain on the div.

Example:
fieldset.fix{display:inline; margin:0; border:0}

fieldset class="fix"
div style="width:200px; background-color:red"
Woot!
/div
/fieldset
fieldset class="fix"
div style="width:200px; background-color:red"
Woot the second!
/div
/fieldset

b) The table
Exactly the same as the fieldset, but using a table instead (If you are using this, you should consider using a *gasp* table):

table.fix{display:inline; margin:0; padding:0}
table class="fix" tr td
div style="width:200px; background-color:red"
Woot!
/div
/td /tr /table
table class="fix" tr td
div style="width:200px; background-color:red"
Woot the second!
/div
/td /tr /table

These have the advantage of not throwing any error by the w3c validator, as they are both originally block elements, and may contain a div.

c) the span
IE recognises display-block on a span, so the real hack is needed for FF. While the following solution works, and may someday be considered better code, it currently does not validate.

Set a span's display to both -moz-inline-box and inline-block and wrap it around your div (or around a span, etc. that you set to display:block). Width and height properties should remain on the div. Note that you can use -moz-inline-stack or -moz-groupbox instead of -moz-inline-box, I could not find a difference.

Note that theoretically, you should be able to use any inline block as the wrapping span, as long as it is set to display:inline or is naturally inline. However, Caveat Emptor! Quirky things happen in strict mode! For example, you can use a strong, but not an em. And when the entire span/div is inside pres, it acts different than when inside Hs.

span.fix{display:-moz-inline-box; display:inline-block}

span class="fix"
div style="width:200px; background-color:red"
Woot!
/div
/span
span class="fix"
div style="width:200px; background-color:red"
Woot the second!
/div
/span

There are several drawbacks to all of these techniques.
The first is that you have a block inside an inline element which should not be valid (even if the parser does not catch it).

The second is that there is no way to set a percentage based width. (IE actually renders the width of the inner div the way we would like it, even when using a percentage based width. This is not technically correct, as any percentage of the parent - which has no set width - should be 0, but it would've been useful if the FF team would've granted us this infraction.)

The third is that the browsers treat these div as being truly inline. Which means that when you put the two divs on separate lines in your code, you get a space between the inline-blocked divs. Just as when you put text on two lines in your code, they appear in the browser with a space between them.
This is hard to describe, but when you try to implement the code it shows up right away. We are used to opening and closing divs each on their own lines and having them show up seamlessly in the browser.
It is also avoidable; as long as the fieldsets/spans are closed and reopened on the same lines, it doesn't matter whether or not the divs inside them are on separate lines. But don't take it lightly or it will bite you.

d) the Div
Wrapping the div with a div solves all of the problems mentioned above, but requires a bit more markup. Kudos to Suzy at WebmasterWorld for first pointing it out (I have since seen it elsewhere.)

The extra markup here is several fold:
i. two(!) outside wrapping divs.
ii. on one line {display:-moz-inline-stack; display:inline-block}
iv. on a separate line, but only for IE {display:inline}
v. the width must be on every one of the divs, or if percentage, the inner widths need 100%.
vi. Even with all this, it doesn't work. but I working on getting it, I must be missing something obvious.

No comments: