While they may not be that pretty, forms are one of the most important parts of a web site. I have already written an article on CSS Forms, that was over a year ago, and I have developed much better techniques.
There are other great resources for information about form design, and I will take this information into account when I am showing the flexibility of my method of marking up and styling forms.
The Markup
After previously using a definition list to mark up my forms, I have switched to using an ordered list now. I switched for two main reasons:
- The list item of the ordered list provides a containing clearing element that the definition list does not provide.
- Semantically, it just makes more sense. A form is really just a list of form fields to fill out. Forms are typically meant to be filled out in order, hence the ordered list instead of un-ordered list (I think this could be debated, but in the long run, either one is more semantic than anything else).
I have decided to create just a simple un-styled form to use as an example. Here is what the markup of the page is:
<ol class="forms">
<li><label for="name">Name</label><input type="text"name="name" id="name" /></li>
<li><label for="email">Email</label><input type="text" name="email" id="email" /></li>
<li><label for="city">City</label><input type="text" name="city" id="city" /></li>
<li><label for="state">State</label><input type="text" name="state" id="state" /></li>
<li><label for="zip">Zip</label><input type="text" name="zip" id="zip" /></li>
<li class="grouping"><label>Sign Up for the Following</label>
<ul>
<li><input type="checkbox" name="info1" id="info1" /><label for="info1">Information 1</label></li>
<li><input type="checkbox" name="info2" id="info2" /><label for="info2">Information 2</label></li>
<li><input type="checkbox" name="info3" id="info3" /><label for="info3">Information 3</label></li>
</ul>
</li>
<li><label for="message">Message</label><textarea name="message" id="message"></textarea></li>
<li class="buttons"><button type="submit" name="submit" id="submit">Submit</button></li>
</ol>
Even without any style applied, the form is still completely usable.
Styling the Forms
There are really 3 main styles of laying out forms:
- Labels above the form field
- Labels to the side of the form field, jagged right
- Labels to the side of the form field, jagged left
Again, like I said, I am not going to go through the pros and cons of each method, I am going to show how easy it is to switch between the methods using my markup.
Labels Above the Form Field
This is by far the easiest method. Here is the CSS used to style my example form:
ol.forms { float: left; list-style: none; width: 100%; }
ol.forms li {
clear: both;
float: left;
margin: 0 0 10px;
width: 100%;
}
ol.forms label { cursor: pointer; display: block; font-weight: bold; }
ol.forms input, ol.forms textarea {
font: inherit;
padding: 2px;
width: 300px;
}
ol.forms textarea { height: 250px; }
ol.forms li.grouping { margin-bottom: 0; }
ol.forms li.grouping ul { list-style: none; }
ol.forms li.grouping ul label {
display: inline;
font-weight: normal;
margin: 0 0 0 10px;
}
ol.forms li.grouping ul input { width: auto; }
Really nothing too complicated. In my opinion, I don’t like stacking the label over the form field; I think it just makes the form too long.
Labels to the Side of the Form Field, Jagged Right
I like this layout of forms the best. I think it presents the form in a clear way so that you can just scan right through the form fields. With just a few changes to the CSS, you can float the labels to the left of the form fields. The changes are marked in bold:
ol.forms { float: left; list-style: none; width: 100%; }
ol.forms li {
clear: both;
float: left;
margin: 0 0 10px;
width: 100%;
}
ol.forms label {
cursor: pointer;
display: block;
<strong>float: left;</strong>
font-weight: bold;
<strong>margin: 0 10px 0 0;
width: 90px;</strong>
}
ol.forms input, ol.forms textarea {
font: inherit;
padding: 2px;
width: 300px;
}
ol.forms textarea { height: 250px; }
<strong>ol.forms li.grouping label { margin: 0; width: auto; }</strong>
ol.forms li.grouping { margin-bottom: 0; }
ol.forms li.grouping ul { list-style: none; <strong>margin-left: 100px;</strong> }
ol.forms li.grouping ul label {
display: inline;
<strong>float: none;</strong>
font-weight: normal;
margin: 0 0 0 10px;
<strong>width: auto;</strong>
}
ol.forms li.grouping ul input { width: auto; }
<strong>ol.forms li.buttons { float: none; margin-left: 100px; width: auto; }</strong>
Take a look at the example with the fields on the left. Depending upon your label length, you may need to increase the width of the labels so that it does not span more than one line, but that is an easy enough change.
Labels to the Side of the Form Field, Jagged Left
Now with two changes to the CSS, you can easily switch to having the labels jagged left, which puts them closer to the form input. The changes needed are highlighted in bold:
ol.forms label {
cursor: pointer;
display: block;
float: left;
font-weight: bold;
margin: 0 10px 0 0;
<strong>text-align: right;</strong>
width: 90px;
}
ol.forms li.grouping label { margin: 0; <strong>text-align: left;</strong> width: auto; }
Here is the example of the form with the labels jagged left.
Taking it a Step Further with More CSS and a Little jQuery
Since I like the form layout with labels to the side and jagged right, I am going to use this one to customize the display a little, and then add in some jQuery functionality.
Improving the Display
When looking at the form, it may be confusing to have all form fields be the same width. It is helpful to the user to modify the width to suit the data that will be entered.
For example, there is no reason that the zip code, city, and state fields should be so long, so let’s add a class to each one to shorten it to the length of the data that will probably be entered. Here is the modified markup:
<li>
<label for="city">City</label>
<input type="text" name="city" id="city" class="medium" />
</li>
<li>
<label for="state">State</label>
<input type="text" name="state" id="state" class="small" />
</li>
<li>
<label for="zip">Zip</label>
<input type="text" name="zip" id="zip" class="extraSmall" />
</li>
Then we just have to add those 3 classes to the CSS:
ol.forms .extraSmall { width: 50px; }
ol.forms .small { width: 100px; }
ol.forms .medium { width: 150px; }
Here is what the form looks like with that little bit of extra style. I also added in some help text, a notation for required fields, and the associated CSS; so take a look at the code to see that.
Adding a Little jQuery
Obviously you have read my article about AJAX forms with jQuery, which discusses how to add AJAX functionality to a form in a degradable way. So the following code assumes that you are going to be doing server side validation of the form as well.
Since there are required fields in this form, I add a class of required to the form and to each parent list item of required input fields.
First, we want our JavaScript to execute when the form with a class of required is submitted:
$(document).ready(function() {
$('form.required').submit(function() {
…
});
});
If there are any error messages still displaying from before, we want to hide them. Also, let’s disable the submit button so just in case, the form cannot be submitted twice. I am also creating a variable that I will be using later to determine if there is an error or not:
$(document).ready(function() {
$('form.required').submit(function() {
<strong>$('form.required span.error').remove();
$('form.required li.buttons button').attr('disabled','disabled');
var hasError = false;</strong>
});
});
Now, we want to check each form field that has a parent list item with a class of required:
$(document).ready(function() {
$('form.required').submit(function() {
$('form.required span.error').remove();
$('form.required li.buttons button').attr('disabled','disabled');
var hasError = false;
<strong>jQuery.each($('form.required ol.forms li.required'),function() {
…
});</strong>
});
});
There are a lot of other jQuery plugins that validate forms, but most of the time, it will just alert the user that the field is required. Instead of doing that, I am going to grab the text from the label element and display that in the error message:
$(document).ready(function() {
$('form.required').submit(function() {
$('form.required span.error').remove();
$('form.required li.buttons button').attr('disabled','disabled');
var hasError = false;
jQuery.each($('form.required ol.forms li.required'),function() {
<strong>var labelText = $(this).children('label').text();
labelText = labelText.replace(' *','');</strong>
});
});
});
Now, we have two different structures of form fields for each list item: a single form field and a grouping of checkboxes or radio buttons. We have to do things a little bit different for each one:
$(document).ready(function() {
$('form.required').submit(function() {
$('form.required span.error').remove();
$('form.required li.buttons button').attr('disabled','disabled');
var hasError = false;
jQuery.each($('form.required ol.forms li.required'),function() {
var labelText = $(this).children('label').text();
labelText = labelText.replace(' *','');
<strong>if($(this).hasClass('grouping')) {
…
} else {
…
}</strong>
});
});
});
First, let’s focus on the case when there is a grouping. We want to loop through each input and see if it is checked. If it is, we just increment a counter. Then, if the counter equals zero, meaning we have no fields checked, we display an error message:
$(document).ready(function() {
$('form.required').submit(function() {
$('form.required span.error').remove();
$('form.required li.buttons button').attr('disabled','disabled');
var hasError = false;
jQuery.each($('form.required ol.forms li.required'),function() {
var labelText = $(this).children('label').text();
labelText = labelText.replace(' *','');
if($(this).hasClass('grouping')) {
<strong>var numSelected = 0;
jQuery.each($(this).find('input'),function() {
if($(this).attr('checked') == true) {
numSelected++;
}
});
if(numSelected == 0) {
$(this).append('<span class="error">You must select from '+labelText+'.</span>');
hasError = true;
}</strong>
} else {
…
}
});
});
});
That seems easy enough, and it’s even easier for the single input fields. We just check to see if the value is empty, and if it is, display the error message:
$(document).ready(function() {
$('form.required').submit(function() {
$('form.required span.error').remove();
$('form.required li.buttons button').attr('disabled','disabled');
var hasError = false;
jQuery.each($('form.required ol.forms li.required'),function() {
var labelText = $(this).children('label').text();
labelText = labelText.replace(' *','');
if($(this).hasClass('grouping')) {
var numSelected = 0;
jQuery.each($(this).find('input'),function() {
if($(this).attr('checked') == true) {
numSelected++;
}
});
if(numSelected == 0) {
$(this).append('<span class="error">You must select from '+labelText+'.</span>');
hasError = true;
}
} else {
<strong>if(jQuery.trim($(this).children('input, textarea').val()) == '') {
$(this).append('<span class="error">Please enter your '+labelText+'.</span>');
hasError = true;
}</strong>
}
});
});
});
To finish up, we just check to see if the hasError variable is true. Since this is just a demo form, I just add an alert if the form would submit successfully:
$(document).ready(function() {
$('form.required').submit(function() {
$('form.required span.error').remove();
$('form.required li.buttons button').attr('disabled','disabled');
var hasError = false;
jQuery.each($('form.required ol.forms li.required'),function() {
var labelText = $(this).children('label').text();
labelText = labelText.replace(' *','');
if($(this).hasClass('grouping')) {
var numSelected = 0;
jQuery.each($(this).find('input'),function() {
if($(this).attr('checked') == true) {
numSelected++;
}
});
if(numSelected == 0) {
$(this).append('<span class="error">You must select from '+labelText+'.</span>');
hasError = true;
}
} else {
if(jQuery.trim($(this).children('input, textarea').val()) == '') {
$(this).append('<span class="error">Please enter your '+labelText+'.</span>');
hasError = true;
}
}
});
<strong>if(hasError) {
$('form.required li.buttons button').removeAttr('disabled');
return false;
} else {
alert('The form would submit now');
$('form.required li.buttons button').removeAttr('disabled'); //Remove this line if using for real
return false; //Change this to true when using it for real
}</strong>
});
});
Go ahead and take a look at the demo form to see it in action.
Conclusion
With a well structured form, you can easily customize it to your liking with just a little bit of CSS. Then, by using some custom jQuery code, you can easily customize how you would like to have the form validation appear.
With a little more work, you could easily add in validation for things like form elements expecting an email address, zip codes, phone numbers, etc. I will have to save that for another post since this one is quite long.
What do you all think? How do you structure your forms? Which layout of forms do you prefer and why?
Categories
Recent Articles
February 2012
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 |
9 Comments
fabian
08.03.2008Labels should be aligned right for easy reading.
Tobi
12.09.2008nice one, but ou should have included a check for a valid email adress..
Willabee
03.06.2009Demo does not validate strict because you need to wrap your JavaScript in a CDATA section.
It would be nice to see how you style radio inputs. Do you treat them in the same way as your demo’d checkboxes?
I do progressive enhancement but you’ve sold me on jQuery.
Good to grab the label names as part of your error content and the CSS width assignment to inputs. What’s your thoughts on adding maxlength (That agrees with database data lengths) and tabindex (To correspond with your ordered list).
A great project for you (I think) would be to have a server process that can generate an object in JSON format that picks up all the validation rules for a table from your database.
The server-side validation would convert JSON into an object and do its validation.
At the client, jQuery could grab the same JSON string as an object for client-side validation.
Demonstrate the form with no progressions, just SEO friendly, accessible and does correct server-side sanitation and validation.
Then add the CSS to make it pretty.
Then add the jQuery to enhance the form, and validate it.
A great example of PE (progressive enhancement) and DRY (don’t repeat yourself) implimentation.
I find there are lots of good client-side behaviours that never show you what takes place on the server and how server processes have to be changed to call another process (web service), that can be shared with XHR requests.
Sorry for the long comment but I like your work as you are doing all the right things in my mind and your better placed than myself to sell the idea to the community.
Well done on your tutorials and articles.
Trevor
03.06.2009@Willabee-
Yes, if this demo was something that I would use in production, I would pull the JS into an external file.
Yes, I would treat radio buttons the same way as checkboxes, but the point of the demo is that it flexible and easy to be changed.
I definitely think maxlengths are useful if you need to restrict length, but you should still check on the server side as well. I absolutely hate tabindex though. It always ends up messing up the tab order when you start adding new fields, or adding other forms to a page. I try and avoid it.
That project sounds interesting, but I don’t think I would have the time to devote to that.
balaganek
04.09.2009Hey,
can you explain me why did you make ol.forms float: left? I don’t understand that. I’m a rather CSS newbie but wouldn’t it be better to avoid floats and make it for example overflow: hidden?
Trevor
04.09.2009@balaganek-
It’s not completely necessary, but since the labels are floated, I floated the list items. Then to contain the floated list items, I floated the ordered list.
balaganek
04.10.2009Thanks for the response. I thought that you don’t have to float container in order to have floated elements inside it. I was using “overflow: hidden” in order to have everything working correctly. I found working with such floated containers much harder. But as i said previously, i’m not a CSS expert, but for now i haven’t found any disadantages of using my approach.
Trevor
04.10.2009@balaganek-
Floating a container is one way to contain the floats., and using overflow: hidden is another.
The problem with using overflow: hidden in this situation is, what happens if you want to use absolute positioning to show an error message next to the form field, and it breaks out of the container. If you were using overflow: hidden, you can’t push it outside of the container.
syndrael
02.10.2010Great tutorial.. easy and detailed..
Best regards from Paris,France under the snow..
——-
Too late, comments are closed!
Don’t worry, you can email me or contact me on Twitter.