In a recent study of web design patterns, Dr. Melody Ivory found that accessibility is the most underutilized aspect of good web page design (Ivory 2005). In fact websites have become more complex and less accessible over time (Hackett 2003). Less than 20% of the Fortune 100 have websites that are fully accessible (Loiacono 2004). Accessible forms are one way to combat this disturbing trend. With CSS layout, you can create two-column forms without the use of tables to save space and time. This article shows how to create a simple two-column contact form using CSS to style structural elements that is both fast and accessible. A survey of CSS-based forms revealed many variations on a theme (see Vandersluis 2004). Most use block-level floats and margins to position form elements on the page. However, in my testing I found IE5.x Mac to have rendering problems with many of these forms. After numerous iterations, I arrived at a solution that worked for IE5.x Mac as well as Safari 1.07 Mac, Firefox 1.07 Win/Mac, IE 6 Win, Camino, and Opera. Our goal here is to create a simple accessible contact form without the use of tables (see Figure 1). First let's look at the old way of laying out a form. Tables use columns and rows to align labels and input fields (see Figure 2). Note how much of the XHTML code is used for layout. Note also that this is an minimalist example with little or no accessibility features. The resulting table looks something like this rendered in a browser. Unfortunately, laying out a form with tables can make it difficult to access for folks with disabilities. Table-based layout also lowers your content to markup ratio, which reduces potential SEO rankings and speed. Fortunately there is a better way, CSS layout. The key to any CSS conversion project is to strip down your XHTML to bare structural elements, and then use CSS to contextually target as much style and layout as possible using few embedded classes or IDs. Forms are no different. What the table-based form above is missing are semantic elements the W3C provides in the (X)HTML specifications, namely FIELDSET and LABEL. Although they can be funkily interpreted for background colors, FIELDSET and LABEL give logical order to FORMs. FIELDSET groups like-minded form input fields and LABEL FOR links field labels with input fields through the ID attribute to make them more accessible and eminently stylable. Let's take the above table-based form code and strip it down to the bare essentials. This barebones contact form contains the same fields, but they are now surrounded by semantic markup, not layout. The first FIELDSET groups the personal data together, while the second groups the comment field into one group. This style-free form looks like this (see Figure 2). The key to laying out a two-column form is to float the label (and input in some cases) element thus: Note that CSS purists may object to the fixed width, attempts at relative widths using ems or percentages were met with browser rendering problems. The above code creates a two column layout using the label and input elements of the above form. Note that for multiple elements placed after one floated label the second and subsequent elements may break left. Since we use only one input element per label, we're on safe ground here. Later on we'll show how to indent instructive text to the second "column" with a margin-left command. Next we set a width for our form and style the fieldsets. Note how we use the form element to set a min/max width. Unfortunately fieldsets are not rendered identically by different browsers. Backgrounds can overlap, padding doesn't behave, and they tend to take up the entire width of the screen on Internet Explorer. Auto, ems, and percentages were tried, but the form misbehaved stretching this way and that. A pixel value larger than the largest anticipated font size did the trick, avoiding wrapping. However we chose to set the width using the form element, which bypassed the problem. Next we bump up the legend font size and underline the first letter of our labels to signify our access keys. Note that Firefox 1.07 Win and Explorer 5.2 Mac do not appear to support the first-letter pseudo-class on legend elements. Opera also breaks the legend after the first letter underlined (despite attempts to fix this with white-space:nowrap;, see Figure 3). Due to these legendary problems (thanks to readers below who pointed the Opera problem out) we switched to underlining the first letter in our labels ( We need to clear our floats somehow, and clearing on the structural elements didn't work in all browsers tested, so we used a BR element, which also helps our form gracefully degrade for older browsers. To add instructions to the right column you can set a left margin equal to the accumulated indent of the input fields. This is simpler than using a dummy label element. Speaking of which, we found that the alignment of our submit and reset buttons was more consistent using a dummy label element than a left margin. You can use the above technique to indent these elements but we found the positioning was inconsistent between browsers. We were trying to avoid CSS hacks and browser sniffing so the "kludge" label was the most expedient method (this could be renamed as "submit" for clarity). One gotcha to watch out for is in the notoriously finicky IE5 Mac. If you specify display: inline for the input and textarea elements they break left in IE5x Mac (see Figure 4). Make sure you test your creation in Safari as well. We ran into disappearing or obscured buttons when floating both labels and input elements (see Figure 5). If you do choose to float your inputs, we surmise it is the floats applied to our input buttons, removing floats and displaying inline solved the problem (see code below). We chose to avoid the problem entirely by floating only our labels. We tried adding padding between the flush right labels and flush left input elements using margins and padding on the right side of our labels. This didn't work as well as we would have hoped, giving inconsistent positioning on our textarea element. Adding margins to the left of our input and textarea form elements worked more consistently in IE6 Win. The final product can be seen below and in a separate CSS form complete with JavaScript to focus the cursor in the first input element. Here is the CSS and form code. More complex forms can be done with CSS, with similar techniques used for 3 and 4-column layouts. You can float notes and additional columns within this framework. With careful use of CSS and lots of browser testing you can create two or three-column forms using CSS layout. Be sure to test your creation in as many browsers as possible (you can use Browsercam as well). We found that designs that worked well in some browsers would break in others (especially IE 5.x Mac). By marking up your forms with structural elements and targeting those elements with CSS contextual selectors you can create a fast-loading form that is SEO friendly and accessible.
Figure 1: CSS Forms Layout Example (image)
Table-Based Contact Form
<form action="http://www.example.com/submit.cgi" method="post" class="ft">
<table>
<colgroup><col align="right"></col><col align="left"></col></colgroup>
<tr><th colspan="2"><h3>Personal Information</h3></th></tr>
<tr><th>First name:</th>
<td><input type="text" name="firstname" value="" title="first name"><td></tr>
<tr><th>Last name:</th>
<td><input type="text" name="lastname" title="last name"></td></tr>
<tr><th>Email:</th>
<td><input type="text" name="email" title="email"><td></tr>
<tr><th colspan="2"><h3>Comments</h3></th></tr>
<tr><th>Your comments:</th>
<td><textarea name="comments" rows="3" cols="20" title="comments"></textarea></td></tr>
<tr><td></td><td><input type="submit" value="Send"> <input type="reset" id="reset"></td></tr>
</table>
</form>
Building a CSS-based Form
<form action="http://www.example.com/submit.cgi" method="post" name="f">
<fieldset>
<legend>Personal Information</legend>>
<label for="firstname" accesskey="f">First name: </label>
<input type="text" id="firstname" name="firstname" value="" tabindex="1">
<label for="lastname" accesskey="l">Last name: </label>
<input type="text" id="lastname" name="lastname" tabindex="2">
<label for="email" accesskey="e">Email: </label>
<input type="text" id="email" name="email" tabindex="3">
</fieldset>
<fieldset>
<legend>Comments</legend>
<label for="comments" accesskey="c">Your comments: </label>
<textarea id="comments" rows="3" cols="20" name="comments"></textarea>
<input type="submit" value="Submit" tabindex="4"> <input type="Reset" tabindex="5">
</fieldset>
</form>
Figure 2: Unstyled CSS Form Layout Example (screenshot)
Add CSS Float, mix well
form label {
display: block; /* block float the labels to left column, set a width */
float: left;
width: 150px;
padding: 0;
margin: 5px 0 0; /* set top margin same as form input - textarea etc. elements */
text-align: right;
}
Set Form Width
form { /* set width in form, not fieldset (still takes up more room w/ fieldset width */
font:100% verdana,arial,sans-serif;
margin: 0;
padding: 0;
min-width: 500px;
max-width: 600px;
width: 560px;
}
form fieldset {
/ * clear: both; note that this clear causes inputs to break to left in ie5.x mac, commented out */
border-color: #000;
border-width: 1px;
border-style: solid;
padding: 10px; /* padding in fieldset support spotty in IE */
margin: 0;
}
Bump up Legend Size and Underline First Letter
Figure 3: Opera Breaks First-Letter Underlined Legends
form fieldset label:first-letter;
) which works on all browsers tested and requires accesskey attributes in each input field.form fieldset legend {
font-size:1.1em; /* bump up legend font size, not too large or it'll overwrite border on left */
/* be careful with padding, it'll shift the nice offset on top of border */
}
form fieldset label:first-letter { /* use first-letter pseudo-class to underline accesskey, note that */
text-decoration:underline; /* Firefox 1.07 WIN and Explorer 5.2 Mac don't support first-letter */
/* pseudo-class on legend elements, but do support it on label elements */
/* we instead underline first letter on each label element and accesskey */
/* each input. doing only legends would lessen cognitive load */
}
Break Clear of the Ordinary
form br {
clear:left;
}
Miscellaneous Tweaks and Workarounds
Indent Instructions to Second Column
form small {
display: block;
margin: 0 0 5px 160px; /* instructions/comments left margin set to align w/ right column inputs */
padding: 1px 3px;
font-size: 88%;
}
Dummy Label Element
<label for="kludge"></label>
<input type="submit" value="Send" id="submit">...
CSS Gotchas
form input, form textarea {
display: inline; /* inline display must NOT be set or will hide submit buttons in IE 5x mac */
width:auto; /* set width of form elements to auto-size, otherwise watch for wrap on resize */
margin:5px 0 0 10px; /* set margin on left of form elements rather than right of
label aligns textarea better in IE */
}
Figure 4: IE5x Mac Breaks Left with Display Inline
Floating Safari Hides Input Buttons
Figure 5: Safari Mac Can Hide or Obscure Buttons
form input#submit, form input#reset {
float: none;
display: inline;
margin:0;
padding:0;
}
Padding the Cellmates
form input, form textarea {
...
margin:5px 0 0 10px; /* set margin on left of form elements rather than right of
label aligns textarea better in IE */
}
<style type="text/css">
<!--
form { /* set width in form, not fieldset (still takes up more room w/ fieldset width */
font:100% verdana,arial,sans-serif;
margin: 0;
padding: 0;
min-width: 500px;
max-width: 600px;
width: 560px;
}
form fieldset {
/ * clear: both; note that this clear causes inputs to break to left in ie5.x mac, commented out */
border-color: #000;
border-width: 1px;
border-style: solid;
padding: 10px; /* padding in fieldset support spotty in IE */
margin: 0;
}
form fieldset legend {
font-size:1.1em; /* bump up legend font size, not too large or it'll overwrite border on left */
/* be careful with padding, it'll shift the nice offset on top of border */
}
form label {
display: block; /* block float the labels to left column, set a width */
float: left;
width: 150px;
padding: 0;
margin: 5px 0 0; /* set top margin same as form input - textarea etc. elements */
text-align: right;
}
form fieldset label:first-letter { /* use first-letter pseudo-class to underline accesskey, note that */
text-decoration:underline; /* Firefox 1.07 WIN and Explorer 5.2 Mac don't support first-letter */
/* pseudo-class on legend elements, but do support it on label elements */
/* we instead underline first letter on each label element and accesskey */
/* each input. doing only legends would lessens cognitive load */
/* opera breaks after first letter underlined legends but not labels */
}
form input, form textarea {
/* display: inline; inline display must not be set or will hide submit buttons in IE 5x mac */
width:auto; /* set width of form elements to auto-size, otherwise watch for wrap on resize */
margin:5px 0 0 10px; /* set margin on left of form elements rather than right of
label aligns textarea better in IE */
}
form input#reset {
margin-left:0px; /* set margin-left back to zero on reset button (set above) */
}
textarea { overflow: auto; }
form small {
display: block;
margin: 0 0 5px 160px; /* instructions/comments left margin set to align w/ right column inputs */
padding: 1px 3px;
font-size: 88%;
}
form .required{font-weight:bold;} /* uses class instead of div, more efficient */
form br {
clear:left; /* setting clear on inputs didn't work consistently, so brs added for degrade */
}
-->
</style>
<script>
<!--
function sf(){document.f.firstname.focus();}
-->
</script>
</head>
<body onLoad=sf()>
<form action="#" method="post" name="f">
<p><b>Bold</b> fields are required. <u>U</u>nderlined letters are accesskeys.</p>
<fieldset>
<legend>Personal Information</legend>
<label for="firstname" accesskey="f">First name: </label>
<input type="text" id="firstname" name="firstname" tabindex="1" value="" title="first name"><br>
<label for="lastname" accesskey="l">Last name: </label>
<input type="text" id="lastname" name="lastname" tabindex="2" title="last name"><br>
<label for="email" class="required" accesskey="e">Email: </label>
<input type="text" id="email" name="email" tabindex="3" title="email"><br>
<small>Your email address is safe with us, until we're acquired.</small>
</fieldset>
<fieldset>
<legend>Comments</legend>
<label for="comments" accesskey="c">Comments: </label>
<textarea name="comments" rows="3" cols="23" id="comments" tabindex="4" title="comments"></textarea><br>
<label for="kludge"></label>
<input type="submit" value="Send" id="submit" tabindex="5"> <INPUT type="reset" id="reset" tabindex="6">
</fieldset>
</form>
Multicolumn Forms
Conclusion
Further Reading
By website optimization on 30 Mar 2006 AM