19 Feb
2009

Semantically Delicious Forms

2 Comments Posted in Development

I’m continually revisiting my XHTML markup to remove all the excess crud and try to get it semantically correct while keeping the markup flexible and stylish.  I think I’m finally happy with the pattern I follow for form markup.  Back in the old day you’d just wrap a table around the whole thing and be done with it.  However using that method the markup doesn’t actually represent the data; a form isn’t a table of data.  The table tag was just an easy way to get the page to look right.  Now we have better ways.

Basic Form Layout

First lets start with the basic XHTML that will represent our form.  Thinking about the basic structure you’ve got a list of fields with labels where order of these inputs is important.  These are often grouped together into logical sets of inputs — one group on the page covering the account information, the next group covering the contact information, etc.

Pretty straight forward, right?  So, following those concepts, the XHTML would look like this for a form with just two inputs:

<form action="/signup" method="post">
<fieldset>
<legend>Basic Information</legend>
<ol>
<li><label for="name">Name</label><input type="text" id="name" name="name" size="30" /></li>
<li><label for="dob">Date of Birth</label><input type="text" id="dob" name="dob" size="10" /></li>
<li><label for="gender">Gender</label><select id="gender" name="gender">
    <option value=""></option>
    <option value="female">Female</option>
    <option value="male">Male</option>
    </select></li>
</ol>
</fieldset>
</form>

Most of that markup should be familiar except maybe the fieldset and legend tags since they are often left out when people create forms.  The fieldset tag groups a set of inputs together to show it is logically related and the legend is essentially a caption for that fieldset.  In the past developers would perhaps use header elements in between groups of data to separate them.  Doing it this way not only visually separates and groups the inputs but it also does so in the markup itself.

I’ll repeat the fieldset/legend/ol group as often as needed on a page for different sets of inputs that belong together and I want visually divided on a page (contact info vs basic info, etc).  Add an li for every input I need within a grouping.

Why ols and lis?  I’ve seen other sites out there that prefer to use the dd and dt tags to define the list of inputs.  I believe that the list tag more closely represents the data: there is no “definition” relationship between an input and it’s label.  The relationship is defined by the “for” attribute on the label; you shouldn’t need to define the relationship beyond that.

Next we need to apply some basic styling to the form.  Personally I prefer the labels aligned left with the inputs to the right.  In addition I prefer the inputs lined up neatly going down the page.  To accomplish this I started with the following:

fieldset { 
    margin: 0 0 20px 0; } 

fieldset legend { 
    font-weight: bold; 
    font-size: 16px; 
    padding: 0 0 10px 0; 
    color: #214062; } 

fieldset label { 
    width: 170px; 
    float: left; 
    margin-right:10px; 
    vertical-align: top; } 

fieldset ol { 
    list-style:none; 
    margin: 0;
    padding: 0;} 

fieldset ol li { 
    float:left; 
    width:100%; 
    padding-bottom:7px; 
    padding-left: 0; 
    margin-left: 0; } 

fieldset ol li input, 
fieldset ol li select, 
fieldset ol li textarea { 
    margin-bottom: 5px; } 

Just a few basic things going on here.  The fieldset and legend styling sets up how I personally like things to look — a 20px margin between fieldsets and in this case I have a dark blue legend at the top of each fieldset.  The ol and li styling ensures that the default nature, indented with numbers, is ignored for the list leaving me to style is as I please.  Then the label floating left with a margin ensures that all the inputs make a nice neat line going down the page to the right of the labels (170 pixels in, in this case).  If you prefer to have the labels above the input instead of to the left you could just remove the float.  Lastly the margin on the bottom of inputs ensures some spacing after the field.

One last part to tweak before the basic form is done.  The above code will work perfectly if you have some kind of CSS reset in place.  By default the fieldset element has a border and adds a padding around its conents.  If you don’t have a CSS reset in place (why don’t you?) you’d modify the fieldset element in the CSS like so:

fieldset { 
    margin: 0 0 20px 0; 
    border: 0;
    padding: 0;} 

That markup leaves you with:

What the basic structure looks like

Descriptions and Required Fields

So that’s the basic set-up, but forms are rarely that simple.  One of the first items I wanted to add was a description for a field; just a little bit of text below the field to help the user out.  Adding the text by itself after the input field results in an undesired look: the text will end up under the label instead of under the input.  To combat this we need to add a wrapper element around the input and description and then make sure the entire element is moved to the right the appropriate amount.  The changed XHTML and new CSS when adding a description to the Date of Birth field is:

<li><label for="dob">Date of Birth</label>
    <div class="inputWrapper"><input type="text" id="dob" name="dob" size="10" />
    <span class="note">YYYY-MM-DD</span></div></li>
form fieldset div.inputWrapper { 
    margin-left: 180px; } 

.note { 
    font-size: 0.9em; color: #666; }

The “note” class is just a convention I use it to specify text that should be a bit more subdued than normal text.  In the same vein I use an “error” class to denote error messages and required field markings.  We’ll use that to add the standard asterisks for noting the fields are required:

<li><label for="dob">Date of Birth <span class="error">*</span></label>
    <div class="inputWrapper"><input type="text" id="dob" name="dob" size="10" />
    <span class="note">YYYY-MM-DD</span></div></li>
.error{ 
    color: #d00; }

The IE Factor

IE treats legends a little differently than all the other browsers.  Most browsers, using the CSS and XHTML just written, will just put the legend above the group of inputs.  IE will do that but not without adding it’s own flair.  It will intent the legend a few pixels.  I prefer to have the legends and fieldsets to look as they do in all the other browsers:

To remedy this you need an IE fix in the header of your page:

<!--[if IE]>
<style type="text/css" media="screen">
legend {margin-left: -7px}
</style>
<![endif]-->

This will cause any IE browser to align the legend with the rest of the labels as opposed to indenting it a bit.

The Final Look With Markup

Cleanly marked up form

The form is now marked up and should be stylish in all browsers (even that pesky IE).  The complete code for this markup is as follows:

...
<!--[if IE]>
<style type="text/css" media="screen">
legend {margin-left: -7px}
</style>
<![endif]-->
...
<form action="/signup" method="post">
<fieldset>
<legend>Basic Information</legend>
<ol>
<li><label for="name">Name <span class="error">*</span>
    </label><input type="text" id="name" name="name" size="30" /></li>
<li><label for="dob">Date of Birth <span class="error">*</span></label>
    <div class="inputWrapper"><input type="text" id="dob" name="dob" size="10" />
    <span class="note">YYYY-MM-DD</span></div></li>
<li><label for="gender">Gender <span class="error">*</span></label>
    <select id="gender" name="gender">
    <option value=""></option>
    <option value="female">Female</option>
    <option value="male">Male</option>
    </select></li>
</ol>
</fieldset>
</form>

And the CSS:

fieldset { 
    margin: 0 0 20px 0; } 

fieldset legend { 
    font-weight: bold; 
    font-size: 16px; 
    padding: 0 0 10px 0; 
    color: #214062; } 

fieldset label { 
    width: 170px; 
    float: left; 
    margin-right:10px; 
    vertical-align: top; } 

fieldset ol { 
    list-style:none; 
    margin: 0;
    padding: 0;} 

fieldset ol li { 
    float:left; 
    width:100%; 
    padding-bottom:7px; 
    padding-left: 0; 
    margin-left: 0; } 

fieldset ol li input, 
fieldset ol li select, 
fieldset ol li textarea { 
    margin-bottom: 5px; } 

form fieldset div.inputWrapper { 
    margin-left: 180px; } 

.note { 
    font-size: 0.9em; color: #666; }

.error{ 
    color: #d00; }

Next Entry
Semantically Delicious Address Form »

Previous Entry
« SyntaxHighlighter 2.0

2 Comments

chelly said:

seriously? you are super geeky when it comes to coding. but super good at it. I have decided you should leave your current role and start your own consulting firm. NPM was taken, but what about reNedPM?

Parrots said:

Ha, I can't believe you remember the name of that!

Leave A Comment

(Won’t give it out, promise)
(Markdown enabled)