Prettier Accessible Forms
Issue № 218

Prettier Accessible Forms

A note from the editors: While brilliant for its time, this article no longer reflects modern best practices.

It can be time consuming to make web forms both pretty and accessible. In particular, laying out forms where the form label and input are horizontally adjacent, as in the image below, can be a real problem. We used to use tables, which worked well in this scenario—but forms don’t constitute tabular data, so it’s a semantic faux pas.

Article Continues Below

I’ve tried to create a form-styling solution that is both accessible and portable (in the sense that I can move the code from one project to the next). Floats have often provided a solution to my problem, but given the complexity of some layouts and the numerous float bugs associated with Internet Explorer, it’s not always easy to reuse a float solution. I wanted to create something that anyone could easily reuse on any project: a style sheet that, when applied to a correctly marked up HTML form, would produce the basis of the required layout. So here it is—my attempt at portable, accessible forms.

Example Forms
Example Forms

Marking up the form#section2

The most important part of a form is the HTML we use to build it. Fortunately, HTML gives us a nice assortment of tags to build our forms in an accessible way. These are fieldset, legend, and label. If you are unfamiliar with these tags, here’s a brief overview:

fieldset and legend#section3

The fieldset element allows us to group form controls into logical, related “chunks.” legend then allows us to add a caption to that fieldset, which helps users understand the context of the form controls contained within that fieldset. In some screen readers, the legend is associated with each form control within a fieldset and is read out after each tab of the keyboard, so that a particular control can always be referenced back to its legend.

label#section4

The label element is used to associate information with a specific form control, while also enforcing a code-level association between form control information and the control element itself.

Let’s look at a simple fieldset example (line wraps marked » -Ed.):

<fieldset>
  <legend>Delivery Details</legend>
  <ol>
  <li>
      <label for="name">Name<em>*</em></label>
      <input id="name" />
    </li>
  <li>
      <label for="address1">Address<em>*</em></label>
      <input id="address1" />
    </li>
  <li>
      <label for="address2">Address 2</label>
      <input id="address2" />
    </li>
  <li>
      <label for="town-city">Town/City</label>
      <input id="town-city" />
    </li>
  <li>
      <label for="county">County<em>*</em></label>
      <input id="county" />
    </li>
  <li>
      <label for="postcode">Postcode<em>*</em></label>
      <input id="postcode" />
    </li>
  <li>
      <fieldset>
        <legend>Is this address also your invoice »
address?<em>*</em></legend>
        <label><input type="radio" »
name="invoice-address" /> Yes</label>
        <label><input type="radio" »
name="invoice-address" /> No</label>
      </fieldset>
    </li>
  </ol>
</fieldset>

The HTML is fairly simple, but you will notice a few things in the structure. I’m using an ordered list (ol) inside the main fieldset. I’m doing this for two reasons:

  1. I can use each list item (li) as a container for each row in the form, which is handy for styling.
  2. It is, in my opinion, semantically appropriate (i.e. a list of form controls in some kind of logical order).

Additionally, the ol provides additional information for some screen readers that announce the number of list-items when they first encounter the list.

Fields that have two or more control options are nested inside an additional fieldset. This logically groups the control options, as discussed above, and the legend acts as a caption for each option within (in our case, radio inputs). Each option is then wrapped inside its own label tag. This is the most accessible approach, and really aids users of assistive technologies.

Styling the form#section5

The styling of the form is the fun part. My aim was to produce a main forms style sheet that can be imported to give a form the basic structural styling we need. Here it is.

form.cmxform fieldset {
  margin-bottom: 10px;
}
form.cmxform legend {
  padding: 0 2px;
  font-weight: bold;
}
form.cmxform label {
  display: inline-block;
  line-height: 1.8;
  vertical-align: top;
}
form.cmxform fieldset ol {
  margin: 0;
  padding: 0;
}
form.cmxform fieldset li {
  list-style: none;
  padding: 5px;
  margin: 0;
}
form.cmxform fieldset fieldset {
  border: none;
  margin: 3px 0 0;
}
form.cmxform fieldset fieldset legend {
  padding: 0 0 5px;
  font-weight: normal;
}
form.cmxform fieldset fieldset label {
  display: block;
  width: auto;
}
form.cmxform em {
  font-weight: bold;
  font-style: normal;
  color: #f00;
}
form.cmxform label {
  width: 120px; /* Width of labels */
}
form.cmxform fieldset fieldset label {
  margin-left: 123px; /* Width plus 3 (html space) */
}

As you can see, the styles are pretty basic; those with a keen eye will notice the display property set to “inline-block” on the labels. If you are unfamiliar with “inline-block,” here’s an explanation from the W3C site:

This value causes an element to generate a block box, which itself is flowed as a single inline box, similar to a replaced element. The inside of an inline-block is formatted as a block box, and the element itself is formatted as an inline replaced element.

This is the magic, and the good news is that it works in Internet Explorer for both Windows and Mac. If you are tempted to use this value in other scenarios, I must point out that in Internet Explorer for Windows, it only works on elements that have a default display of “inline,” which a label does. The bad news, however, is that Mozilla-based browsers (Firefox, Netscape, etc.) do not directly support this property, but we can fix it, and I’ll come to that shortly.

So, these are the core styles required to give the form its most basic appearance.

I wanted to create a style sheet that contains basic form styles that act as part of a larger library of reusable style rules. In theory, they would not need to be modified by an author, but could simply be included in any site to set the baseline rules. (This core set of form styles is the first part of my vision for creating a library of style sheets that handle the common CSS conundrums.) The best part is that authors who want to modify something like the width of the label elements, can easily overwrite the default further down in the cascade or with a more specific selector. And to truly customize each form, you can add your own styles in a separate style sheet like I have done in this “pretty” form example.

Bugs#section6

As I mentioned above, users of Mozilla-based browsers won’t see what all the fuss is about. Unfortunately, Mozilla does not currently support the “inline-block” display type, which is a bit of a problem. However, Mozilla has a browser-specific display value, “-moz-inline-box”, which acts, for the most part, like “inline-block.” The problem with this value is that if you have a really long label, the text disappears under the adjacent form control. Text does not wrap inside an element displayed as “-moz-inline-box.” But if we place the label text inside an element displayed as “block” inside the “-moz-inline-box” element and give it a width, it behaves as it should.

Adding all that by hand into the document is going to mess up our nice, lean, mean HTML —plus we may decide to change the form layout later, and who wants the extra markup in there? Luckily, there’s an easy way to fix this using JavaScript and the DOM (line wraps marked » -Ed.):

if( document.addEventListener ) »
document.addEventListener( 'DOMContentLoaded', cmxform, false);function cmxform(){
  // Hide forms
  $( 'form.cmxform' ).hide().end();  // Processing
  $( 'form.cmxform' ).find( 'li/label' ).not( '.nocmx' ) »
.each( function( i ){
    var labelContent = this.innerHTML;
    var labelWidth = document.defaultView. »
getComputedStyle( this, '' ).getPropertyValue( 'width' );
    var labelSpan = document.createElement( 'span' );
        labelSpan.style.display = 'block';
        labelSpan.style.width = labelWidth;
        labelSpan.innerHTML = labelContent;
    this.style.display = '-moz-inline-box';
    this.innerHTML = null;
    this.appendChild( labelSpan );
  } ).end();  // Show forms
  $( 'form.cmxform' ).show().end();
}

The JavaScript uses the wonderful JQuery library (which also, obviously, needs to be included) to simplify the processing. I will briefly explain how this works.

Firstly, I hide any form that uses the “cmxform” class.

// Hide forms
$( 'form.cmxform' ).hide().end();

We do this because otherwise the user might see the form “fixing” itself as it renders in the page, which can look a bit strange.

Secondly, using the JQuery methods and a combination of CSS selectors and XPath, I collect all labels that are a direct descendant of an li, that do not have a class of “nocmx”.

$( ‘form.cmxform’ ).find( ‘li/label’ ).not( ‘.nocmx’ ).each( function( i ){ ...

The reason I use the “nocmx” filter is so that users can add that class to labels that require a different styling. This can be very useful, as I have discovered from using this method for a while. Then, for each collected label, a span is created inside to fix the “inline-block” problem.

$( 'form.cmxform' ).find( 'li/label' ).not( '.nocmx' ).each( function( i ){ … }

Finally, all the originally hidden forms are made visible again, and everything looks sweet.

// Show forms
$( 'form.cmxform' ).show().end();

Note: I’m using the Mozilla-specific document.addEventListener() method to launch the script. This really is ideal, as it only runs for Mozilla (which is all we need) and it launches as soon as the DOM has been loaded.

Although JavaScript might not appear the most elegant solution, it is completely unobtrusive and, when JavaScript is disabled, the form degrades gracefully.

There’s also a small amount of tidying up to do in Internet Explorer though. For Windows, we need to tweak the positioning of the legends so they line up neatly. This can be achieved by giving the legend a negative left/right margin as shown below:

form.cmxform legend {
  padding: 0 2px;
  font-weight: bold;
  _margin: 0 -7px; /* IE Win */
}

For IE5/Mac, we need to tweak the display of the legend, to fix an odd display bug, by adding the following:

/**//*/
form.cmxform legend {
  display: inline-block;
}
/* IE Mac legend fix */

This rule allows us to supply a rule specifically to Internet Explorer for Mac. If you are uncomfortable in using browser specific rules in this way, feel free to remove them.

You could add these fixes to browser-specific style sheets, but I wanted this technique to be as portable as possible, so all the styles are in one place.

Finishing up#section7

We’re done. All you need to do is include the relevant “cmxform” files and then add the class of “cmxform” to any form you need to. (If you are wondering where the name “cmxform” came from, it’s there because I developed this technique while working for Cimex Media in London.) Enjoy!

Note: This form technique has been tested with Safari 2.0.3, Firefox 1.5, Opera 8.5, Internet Explorer 7b2 , Internet Explorer 6, Internet Explorer 5 (Windows), Internet Explorer 5.2 (Macintosh), Netscape 7.2 (Macintosh), and Netscape 8.1 (Windows).

About the Author

Nick Rigby

Nick Rigby is a freelance web standards and accessibility developer for his company, Puretic. In between work he enjoys football, music, being creative, and writing the occasional article for nickrigby.com.

118 Reader Comments

  1. Hello. Opera 9.01 is inserting ‘Null’ in front of the label values for me, with the intended label then dropping down a line. Anyone got a fix for that?

  2. I know I’m awfully late to the party, but I was wondering about the statement in the article that, “forms don’t constitute tabular data.”

    I’ve heard other people say this, and I really don’t get why. Especially given the concept of label/data pairs. In fact, I would suspect that when most forms are submitted to a database the data is submitted in a near identical format (e.g. The first column being an identifying label, and the next column being the data (although, I still think it would be semantic, even if the order was reversed… just kind of whacky in L to R reading languages)).

    I’m wondering if I’m missing something.

  3. I dont have anything to add to the discussion. I just wanna say that the idea of using lists for structuring is very…ummm…unique.

    I won’t argue about semantics or anything. coz as long as it works, it’s totally fine by me 😀

  4. Here is a poor attempt at re-implementing this using Prototype.js since JQuery clashes. This requires that the form has an id=”cmxform”, I’m sure people out there that actually know what they’re doing could improve upon this.

    
    if( document.addEventListener ) document.addEventListener( 'DOMContentLoaded', cmxform, false );
    
    function cmxform(){
      // Hide forms
      $( 'cmxform' ).hide();
      
      // Processing
      var someNodeList = $('cmxform').getElementsByTagName('label');
      var nodes = $A(someNodeList);
      nodes.each( function( i ){
    
        var labelContent = i.innerHTML;
        var labelWidth = document.defaultView.getComputedStyle( i, '' ).getPropertyValue( 'width' );
        var labelSpan = document.createElement( 'span' );
            labelSpan.style.display = 'block';
            labelSpan.style.width = labelWidth;
            labelSpan.innerHTML = labelContent;
        i.style.display = '-moz-inline-box';
        i.innerHTML = null;
        i.appendChild( labelSpan );
    
      } ); //.end();
      
      // Show forms
      $( 'cmxform' ).show();
    }
    
  5. Someone above was asking about a tableless two column css layout.

    Working for a non-profit which serves the disabled, this article was my original inspiration for developing the above.

    I eventually found the use of fieldsets preferrable to unordered lists because of the extra junk that comes with them in screen-readers. Besides, they make natural large containers, I wish I had discovered them years ago.

    This article is right-on in promoting the use of labels, which I now use religiously. I simply happen to like the div/span approach.

  6. Someone above was asking about a
    “tableless two column css layout”:http://www.yeago.net/work/2006/09/two-column-css-layout.html

    Working for a non-profit which serves the disabled, this article was my original inspiration for developing the above.

    I eventually found the use of fieldsets preferrable to unordered lists because of the extra junk that comes with them in screen-readers. Besides, they make natural large containers, I wish I had discovered them years ago.

    This article is right-on in promoting the use of labels, which I now use religiously. I simply happen to like the div/span approach.

    ps: severe apologies for the double-post. forgot to Textile.

  7. Let me first say that this article has become a great resource for me and has changed the way I think about and create forms. But I’ve found a problem that I’m surprised that no one has mentioned here. I’m developing on a Mac, and everything looks perfect on Safari! But in the rest of the browsers I’ve seen, there are inconsitencies. I’ve done a little modification to the CSS for my own purposes, naturally, and set the label width to 33%, as I’m using them in some varying CSS column layouts and this causes the labels to collapse so that the inputs are right along the right side of them. This happens everywhere bu Mac Safari and MSIE. The only other inconsistency is that MSIE seems to give a left margin to fieldsets. I set up my Submit button like so:


  8. So that it would line up with my checkboxes and other inputs, but it shift a good inch to the right. Any ideas on how to fix this situation?

  9. Hi,

    Both on IE 6 and Firefox on Win2k, when a legend has more than 1 line, it’s not broke but continues on a single line.

    It’s at the exact position of “Is this address also your invoice address ?” but longer.

    Even if I force the width property of the legend, it’s not working, evenwith ” !important”.

    If you have any idea “¦

    Cheers.

  10. Firstly, I think this article does provide a nice insight and a perfectly valid method for form layouts. I think whilst the use of Javascript to fix style hacks is far from ideal, it is a solution which should work for the vast majority of people and thus is not a big usability issue. The downside is simply the untidiness of having extra “˜hack fix’ code.

    I would, personally, disagree with your assertion that it is wrong to represent form data in a tabular form. I believe that it is perfectly acceptable to represent any data however you like providing the semantics are easy to understand, mathematically solid, and do not contravene existing standards unless there is a specific reason for doing so. In the case of forms, I would say that it’s as easy to understand the semantics of a table form layout vs. a list based layout. Because there aren’t any fixed standards in place for form semantics I can’t see why using tables can be considered a poorer solution to using lists.

    For most situations you can classify a form as a set of related fields with undefined size, and classify a field as a typical (key, value) pair. To give an example, taking the first three, fields of your form (Name, Address, City) using set notation you could mathematically represent a typical record of data as ((Name, “˜Homer Simpson’), (Address, “˜742 Evergreen Terrace’), (City, “˜Springfield’)). As the set contains pair based items only, you can consider a 2-column table where the first column represents the head of the pairs and the second represents the tail of the pairs. Clearly, it is a direct mapping between two representations and the related XHTML code would be pretty self explanatory.

    I think there is a certain hesitation to use tables in the designer world for fear of being ridiculed for being out-of-touch. I think in this case a table solution would be perfectly understandable and increase the clarity of the page’s stylesheet.

  11. The alternative javascript (in comment #106) for use with Prototype.js only works with one form on the page and with it’s ID being ‘cmxform’, not the class. This alternative works pretty much the same as the original script, only using prototype.js instead of jquery.

    function cmxform(){

    // Hide forms
    $$(‘form.cmxform’).each(Element.hide);

    // Processing
    $$(‘form.cmxform li label’).each( function( label ){
    if (label.classNames() != ‘nocmx’) {
    var labelContent = label.innerHTML;
    var labelWidth = document.defaultView.getComputedStyle( label, ” ).getPropertyValue( ‘width’ );
    var labelSpan = document.createElement( ‘span’ );
    labelSpan.style.display = ‘block’;
    labelSpan.style.width = labelWidth;
    labelSpan.innerHTML = labelContent;
    label.style.display = ‘-moz-inline-box’;
    label.innerHTML = ” ;
    label.appendChild( labelSpan );
    }
    } ) ;

    // Show forms
    $$(‘form.cmxform’).each(Element.show);

    }

    if( document.addEventListener ) document.addEventListener( ‘DOMContentLoaded’, cmxform, false );

  12. Why are submit buttons never included in fieldsets?

    Since submit buttons can have meaning themselves, it would seem appropriate to me. I’ve never understood this.

  13. A different solution to tableless forms

    Nice article Nick, one of the best examples I’ve seen.

    I’ve been looking for a good css alternative to table based forms for a while now. I found this article along with a few other articles, all with different implementations. However, the majority of them used div, span, lists, p, br etc, which personally I don’t think is semantically correct. Javascript/css combinations like Nick’s seem to be a favourite too, which I’m not a fan of, sorry.

    Most of the examples I’ve seen don’t validate to XHTML 1.0 Strict, and fall apart in different browsers. They also didn’t cater for radio buttons, check boxes etc, so I’m glad to see Nick’s article includes these.

    ANYWAYS, as nothing else I have seen suited me I’ve been working on my own tableless form which covers all the points above and seems pretty bulletproof:

    Tested and working in Windows: IE 5, IE 5.5, IE 6, IE 7, FF 1, FF 2, Netscape 6*, Netscape 7*, Netscape 8, Opera 5**, Opera 6**, Opera 7, Opera 8, Opera 9

    Having read the previous posts, this is a different solution that might suit some of you better. Thought I’d share it and see what you think:

    A different solution to tableless forms

Got something to say?

We have turned off comments, but you can see what folks had to say before we did so.

More from ALA

I am a creative.

A List Apart founder and web design OG Zeldman ponders the moments of inspiration, the hours of plodding, and the ultimate mystery at the heart of a creative career.
Career