How to create perfect form markup and style it with CSS
When I introduced aardvark.legs CSS framework two days ago, I mentioned that one of its greatest strengths is the way it treats forms. Those forms are based on a coding style, that, to me, proves to be the most successful when dealing with HTML forms.
This post will explain my choices when marking up the forms, and the CSS styling involved in making them cross-browser compatible. While I would love for you to useaardvark.legs in your next project, this tutorial is completely standalone and is not dependent on the framework.
The perfect HTML form mark up
That’s a strong claim; however, over the past few years I don’t think I’ve ever had to deviate from this mark up. I’m sure there will be cases that require some tweaking — especially when dealing with a complicated design — but in most cases, this should be more than enough.
First, let’s have a look at the full HTML code we’ll be dealing with. That way, it will be much easier to see where I’m going with this post. (Thanks to Dan Webb for the nice, little Code Highlighter.)
<form action="" method="post">
<fieldset>
<legend>Vertical Form</legend>
<div class="field">
<label for="field1">Text Input</label>
<input type="text" class="text" id="field1" name="field1" />
</div>
<div class="field">
<label for="field2">Password Input</label>
<input type="password" class="password" id="field2" name="field2" />
</div>
<div class="field">
<label for="field3">Select</label>
<select id="field3">
<option>Option 1</option>
<option>Option 2</option>
</select>
</div>
<fieldset>
<legend>Radio set</legend>
<ul class="fields">
<li>
<label><input type="radio" class="radio" name="field4" /> A radio button 1</label>
</li>
<li>
<label><input type="radio" class="radio" name="field4" /> A radio button 2</label></li>
<li>
<label><input type="radio" class="radio" name="field4" /> A radio button 3</label>
</li>
</ul>
</fieldset>
<div class="field">
<label for="field6">File Input</label>
<input type="file" class="file" id="field6" name="field6" />
</div>
<div class="field">
<label for="field7">Textarea</label>
<textarea cols="60" rows="6" id="field7" name="field7"></textarea>
</div>
<div class="buttons">
<input type="submit" class="button" value="Submit" />
<input type="reset" class="button" value="Reset" />
</div>
</fieldset>
</form>
That’s a little long, but it covers almost all of the form elements we want to be able to style. With that out of the way, let’s go over the various decisions made when marking up this code sample.
Type is not enough; set class too
Styling input elements can often be a hassle — they’re all very different (text, checkbox, radio, file, submit, reset) and yet there’s only one tag for them. Sure, you could use attribute selectors, but unfortunately IE doesn’t understand those. This is where classes come to the rescue. Simply set the input’s class to the same value as the type, and then use it to target that input. Sure, it’s silly, but it works.
Group elements together logically
This is one of those tips that pays off more and more over time. Everyone probably uses fieldset to group multiple form elements, but I know that not that many people are in the habit of grouping their labels with the matching form element.
When grouping elements with their respective labels, it pays to be consistent. That’s why every label and matching form element are nested in a div with a class “field” (you could also use a p element instead of div). There are several advantages to this grouping:
- Semantics. In a way, I’m specifying that these two elements belong together.
- Clarity. I can easily see which label belongs to which element.
- Ease of use.
- I have one more element to use when styling the form.
- I can set further classes on this div, such as “required”, to signify that this field is required.
I would say that of all those reasons, my favorite is the last. It gives me the ability to apply extra styling to both a label of a required form element and to the element itself.
Earlier, I mentioned that everyone already uses fieldset elements for grouping related fields. What I don’t see that often is nesting of fieldset elements within each other. Why would you do that? To semantically group related radio or checkbox inputs, and to be able to give that group a title, or rather a legend. While we’re at it, make sure that you put each label and input in its own list item within an unordered or ordered list. After all, it is a list of checkboxes / radio buttons that you’re working with.
Finally, I think that grouping all buttons in a form makes perfect sense. I usually do it with a div that has a class “buttons”, so that I can easily select it in my CSS and do many other great things with it.
Are legends worth it?
To be honest, it depends. If you’re not in need of too much styling and can stomach the little hacks required to get legend working properly, then go with it. If not, just use an appropriate heading element instead. For the sake of completeness, I’ll be using the legend element in this post.
Styling forms in a cross-browser compatible way
Now that we have a great form to work with and we know what’s happening with all the mark up, let’s discuss how we can achieve basic cross-browser compatible styles. The browsers I’ll be testing on include: IE6, IE7, Firefox 3, Safari 3, Opera 9.6, and Camino 1.6. This set-up should cover all the bugs that we could possibly encounter.
I’ll approach this part in the same way as the previous one, showing the full CSS first and then explaining the various declarations. In general, I would recommend that you use Eric Meyer’s reset; however, since I want this tutorial to be completely standalone, I’ll be providing some basic styles and resets along with it.
There are two common styles of CSS forms. The “vertical” layout has labels above input fields, while the “horizontal” layout has labels to the left of the input fields. Once you know how to build the basic vertical layout, it’s very easy to switch to the horizontal one. I’ll make sure to explain both.
Here’s the CSS we’ll be dealing with:
/* First, some basic resets, body styles, etc. */
html, body, div, span, fieldset, form, label, legend
{border: 0; margin: 0; outline: 0; padding: 0; background: transparent; vertical-align: baseline;}
body {background: #fff; color: #000; font: .75em/1.5em Helvetica, Arial, "Liberation Sans", "Bitstream Vera Sans", sans-serif;}
/* Here's our form stuff */
fieldset {clear: both;}
legend {padding: 0 0 1.286em; font-size: 1.167em; font-weight: 700;}
fieldset fieldset legend {padding: 0 0 1.5em; font-size: 1em;}
* html legend {margin-left: -7px;} /* ie6 */
*+html legend {margin-left: -7px;} /* ie7 */
form .field, form .buttons {clear: both; margin: 0 0 1.5em;}
form .field label {display: block;}
form ul.fields {margin: 0 0 1.5em; padding: 0;}
form ul.fields li {list-style-type: none; margin: 0;}
form ul.inline li, form ul.inline label {display: inline;}
form ul.inline li {padding: 0 .75em 0 0;}
input.radio, input.checkbox {vertical-align: top;}
label, button, input.submit, input.image {cursor: pointer;}
* html input.radio, * html input.checkbox {vertical-align: middle;} /* ie6 */
*+html input.radio, *+html input.checkbox {vertical-align: middle;} /* ie7 */
textarea {overflow: auto;}
input.text, input.password, textarea, select {margin: 0; font: 1em/1.3 Helvetica, Arial, "Liberation Sans", "Bitstream Vera Sans", sans-serif; vertical-align: baseline;}
input.text, input.password, textarea {border: 1px solid #444; padding: 2px;}
/* horizontal layout */
form.horizontal .field {padding-left: 150px;}
form.horizontal .field label {display: inline; float: left; width: 140px; margin-left: -150px;}
Wow. That’s a lot… let’s take it piece by piece and figure out what’s going on there. If you want, you can have a peek at the example file this will produce: vertical layout or horizontal layout.
I’ll begin by covering a couple of generalities and then get into the technical details. First, I would like to say that I’m not attempting to create an attractive form, but to create styling that is cross-browser compatible and could serve as a basis for further prettification. Second, creating reliable cross-browser styles for forms is a difficult task to accomplish without the use of hacks, therefore I’ll be using two — one for IE6 and one for IE7. Both are very similar: the IE hack consists of the * html selector and the IE7 hack consists of the *+html selector. These selectors shouldn’t really match any elements, but since they do in IE, we will take advantage of that. The other option would be to use conditional comments; however, for the sake of brevity, I won’t.
Getting the basics right
Earlier, I mentioned that we can use the div elements that wrap our fields to add some extra styling to our forms. That’s exactly what we’ll do first. We’re going to set nice bottom margins to space out our form and set the fields to clear anything that came before them. This is a precaution for what is about to come later.
We’ll also make sure to set the labels to be displayed as block elements — that way each will be on its own line, rather than next to its form field.
Another important thing to take care of early on is setting margins on all form elements to 0 and setting them to use the correct font-size and font-family. This is very important, as otherwise the browser will attempt to use its default font settings. In my experience, it’s best to set fonts only on text, password, textarea, and select elements, rather than all of them. I would strongly advise against setting custom fonts on buttons (unless your design modifies the buttons heavily), as that will cause Safari’s default buttons to look worse than with the default font (Lucida Grande). Also, consider setting custom borders and adding about 2px of padding inside text, password, and textarea elements. That will give the text a little room to breathe inside the form elements, and make them look nicer.
When setting width on form elements, keep in mind that in some browsers the padding and border are part of the width, rather than extra as in most. This happens regardless of the box model set, unfortunately.
Don’t even try styling file inputs. Have fun with the label and what not, but styling file inputs is a lost cause. They just won’t listen.
Dressing up legends with style
Despite its semantic value, the legend element is unruly when styled with CSS. The browsers that have the most difficulties with legend are IE6 and IE7, so we’ll have to use some hacks to get around those. We’ll add -7px of left margin to get around the irremovable default padding on the left. Another odd thing about the legend element is its issues with margins, so you’re better off using paddings instead. This means that some styling will be, unfortunately, off limits.
Checkboxes and radio buttons
Checkboxes and radio buttons are a peculiar case. I recommend setting vertical-align: top; on both. All peachy. Well, not so much… IE6 and IE7, once again, misbehave. To the rescue come hacks: set both to have vertical-align: middle;. Now you’re set.
By default, the checkboxes and radio buttons in our example each have a new line, but we can put them all on the same line very easily. Just add class “inline” to the unordered list that holds them and set display: inline; to the list items and the labels.
Horizontal layout, where labels are on the left of form fields
This is the most common layout for forms and is incredibly easy to accomplish with all the prep work done earlier. Just set left padding of the containing div to the desired width of the label. Now, set the label to float and make it display inline to deal with IE6 bugs. Follow it up with negative left margin equal to the desired width of the label, and set the width of the label to be the desired width - 10px. This will ensure that your label text never touches the form field on the right. This float is the reason we’ve set our fieldsets and divs to clear floats, earlier in this post.
… and we’re done
That sure took a while, but I think we covered it all. If you’re interested in the examples, you can see them here: vertical layout or horizontal layout.
This styling works across a wide range of browsers and serves as perfect groundwork for creating good-looking forms. In the near future, I’ll be following up this post with another one, but this time dealing with more specific issues such as image buttons, required fields, and other interesting things.
I hope you found this useful, and that it will save you some time when coding forms. While we’re at it, please consider using aardvark.legs CSS framework which has all this nice stuff built into it.
Update: I have tested this with IE8 and updated the checkbox / radio definitions to match better.
Comments
This is exactly what I do with my forms!
On an aside, though, if we’re doing it totally semantically, isn’t it better to use a
ptag rather thandiv, representing logical paras within the form?#1 • Indranil wrote this on January 24, 2009 at 2:52 am
This was brought up on reddit and I disagree, simply because it’s not a real paragraph. Thus I feel like HTML becomes harder to understand when we start using paragraphs for things that aren’t really paragraphs. So, while it is shown as an example in HTML 5 spec, I feel like it is counter-intuitive and could be confusing to new coders — ultimately making HTML harder to learn.
However, if people prefer paragraphs, I’m perfectly happy with that. Either one is great, IMO, and it is a one line change, nothing major. I decided to change a part of the text to reflect that, since it came up several times now.
#2 • Anatoli Papirovski wrote this on January 24, 2009 at 8:23 am
Very useful post for me Anatoli, thanks, I’ve been so out of touch with CSS stuff for a while now, it’s awesome just to learn about the * html and *+html hacks to deal with IE!!
And now I see why the P tag twitter posting stuff too!
#3 • Cameron Booth wrote this on January 24, 2009 at 9:01 am
Hi Anatoli,
Congratulations on your good write-up.
To the semantic advocats: Frontend coding is always a tradeoff between maintainability and semantics. Personally I’d much rather have one more div in my code than having to change the code basics everytime I’m dealing with new contents. Template-driven css should target reusability and the developer’s needs first. So I’m totally d’accord with Anatoli’s approach.
One minor concern though. Setting labels to display:block will make the clickable area bigger than the label text, which is good on the one hand (bigger click target), but can be confusing to novice users as they’re basically able to click on white canvas to select form fields. Got an opionion on that?
Cheers,
Milan
#4 • Milan wrote this on January 25, 2009 at 5:33 am
You could set width on the labels to match the inputs. With that said, I’ve never had problems with users getting confused and I’ve done a lot of usability testing in my life.
As for semantics, I am a huge fan of semantically-correct code and I don’t see what’s wrong with
div. It’s a semantically neutral element which can possibly take on meaning through it’s class name or ID, which is what happens in Microformats. Perfectly fine usage of divs in this case.As for the other person commenting here under name “J. Albert Bowden II”. Your comments are forever lost and will be deleted whenever you post. Why? Because I have no interest in discussing this post with someone who can’t read. (I explain file inputs, conditional comments, etc. in the article above, bro.)
#5 • Anatoli Papirovski wrote this on January 25, 2009 at 7:57 am
well.. in the horizontal version why do you have padding left to div with a class “field” & again negative margin to labels.. what is the purpose?
#6 • suraj wrote this on January 25, 2009 at 9:46 am
& it colud have been great if we think of Error messages or Alerts like tool tip, required for validations..that we can just add one more div after any input tag though…
i got good names for forms after reading this post..Verticle & horizontal..I will integrate this in my CSS framework…thanks
#7 • suraj wrote this on January 25, 2009 at 9:49 am
The left padding sets off the input or textarea or select, then the label is pulled left and makes it display nicely. This method avoids cross-browser problems, etc. that could happen in some edge-cases. It’s actually pretty common.
#8 • Anatoli Papirovski wrote this on January 25, 2009 at 9:54 am
I very much like your approach and clear explanation :-)
I did notice one (possible) copy/paste issue: the legend on the “horizontal layout” says “Vertical Form”. Thanks!
#9 • Troy wrote this on January 25, 2009 at 1:53 pm
Good work Anatoli,
One small problem I did notice is that if the label on a field is very long, spanning multiple lines then then bottom margin of the field div is ignored. This means the text in the label appears right above the label below without any margin applied.
A work around is to apply the same bottom margin to both the label and the input element instead of the bottom of the field div.
#10 • James Moss wrote this on January 26, 2009 at 1:26 am
James,
you could also just set
overflow: hidden;on thediv.fieldif you expect that to happen. I usually do that, but didn’t feel it was important enough to mention in this tutorial. I wanted to reduce the CSS to the bare minimum.#11 • Anatoli Papirovski wrote this on January 26, 2009 at 7:04 am
Great post. Thanks!
#12 • Timothy wrote this on January 26, 2009 at 10:12 am
Love it. Great stuff.
One thing I like to do is include a break tag after the labels. They can be set to display:inline but for those who do not have CSS enabled the form will be more readable.
#13 • Dustin Boston wrote this on January 26, 2009 at 10:33 am
Don’t mind, it’s just a minor issue I run into myself from time to time.
Regarding div-elements, I tought it was clear, that I don’t have any problems with your approach. In fact I’m using a quite similiar technique, where label/input are grouped by wrapping divs. I add the input type class to the wrapper though.
<div class="input text m">
<label for="firstname">Vorname</label>
<input id="firstname" type="text" />
</div>
#14 • Milan wrote this on January 26, 2009 at 4:28 pm
This is good stuff. I use a pretty similar method except that I wrap the input or inputs in a div (in horizontal forms) so that all the inputs will line up vertically regardless of how many you have in a grouping.
With regards to the discussion of semantics, at least for now, I still think that in any practical sense divs, class names, etc. are not semantic. I think we’re all just so anxious for the web to move forward that we can sometimes convince ourselves that having more meaningful class names is somehow going to enhance UX.
#15 • Adam wrote this on January 26, 2009 at 11:37 pm
Enjoyed the post so much I had to show how to do it in ASP.NET on my own blog. Thanks for the post!
#16 • Eric Sowell wrote this on January 28, 2009 at 8:56 am
Hi,
I know that there are several ways to define “perfect markup” and what beautiful css is. I always try to use as little IE-Hacks as possible. When a style looks perfect in firefox and both IEs without using any hacks, it very likely looks perfect in every other browser on earth. Your css uses IE-Hacks every second line, so I don’t think it’s “perfect”.
I know it’s really pain in the ass to define styles that work without hacks. But if you find a good way to style forms without that crap, please let me know. That - indeed - would be a perfect markup.
#17 • dennis wrote this on February 3, 2009 at 3:08 am
There’s two hacks in total, I would say that’s not too bad. If you have a better way, please share. Furthermore, the term “markup” refers to HTML not CSS.
#18 • Anatoli Papirovski wrote this on February 3, 2009 at 8:43 am
Thanks for this. So much of the available discussion on what the best way to markup forms is is from at least 4 years ago, and often earlier—and very often is a discussion on whether to use tables or definition lists. Learning how to construct forms just in the past few weeks, both of those methods have felt rather archaic.
So while I seem to have finally weaned myself off of using divs as a solution to everything, your argument is clear and logical and, while people have been arguing that you perhaps use too many divs, your method feels semantically cleaner to me than those well worn alternatives.
Cheers.
#19 • Joseph K wrote this on February 11, 2009 at 9:21 am
While this markup for a form is much better than some tutorials I’ve seen in the past. It’s really not the cleanest markup you could possibly have. You can get the same results you get now without the markup
Ditch those
- then you can claim the perfect markup.#20 • Justin wrote this on February 20, 2009 at 7:35 am
**Ditch those extra divs then you can claim the perfect markup*
#21 • Justin wrote this on February 20, 2009 at 7:35 am
Thanks for your comment, Justin. However, I will have to vehemently disagree. The reason for those divs is simple… they are there to provide 1) clarity, 2) hook for JavaScript when I’m trying to set up validation, etc. (is it better to set “error” class just on div or on label and input and possibly description?), and 3) if I ever need to add a background for each row of form fields, I don’t have to change my mark-up just to fit that one project.
This mark up is flexible and works across many different projects. The goal was to create something that can be used again and again without much modification, while keeping it semantic.
There is nothing “unclean” about using divs, as they are semantically neutral element. However, they can and do often take on meaning through their class as I’ve mentioned before (look at microformats). In this case, imagine that I was using a
fieldelement rather than a div element, such afieldelement doesn’t exist in the spec but would be used to group label with the input and possibly with the description. Since it doesn’t exist, I go for the next best thing which is adivwith class “field”.Also, if you turn off CSS and look at an example with no divs or paragraph tags to separate form fields you’ll see a pretty ugly picture. While this is not necessarily a great test, I prefer to let people using text browsers or old mobile devices see something a little more organized.
#22 • Anatoli Papirovski wrote this on February 20, 2009 at 8:12 am
nice work Anatoli
#23 • Krasi wrote this on February 20, 2009 at 8:54 am
Holy hacks, Batman!
#24 • Timothy wrote this on February 20, 2009 at 9:31 am
OK, if you mean CSS ONLY, but there is a way of styling file inputs with just a few lines of JS:
http://www.shauninman.com/archive/2007/09/10/styling_file_inputs_with_css_and_the_dom
There is even a lighter version as a jQuery plugin.
#25 • sinisasasic wrote this on February 21, 2009 at 2:29 am
Nice work, and really well explained! Although for semantics sake I would use an ordered list rather than divs to separate the form elements. Yes you will have to style the list, but isn’t the form just a basic list of elements in the end?
#26 • Jay wrote this on February 21, 2009 at 3:59 am
Sorry, the comments are closed at this time.