A List Apart

Menu
Issue № 218

Prettier Accessible Forms

by Published in CSS, HTML, Accessibility · 118 Comments

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

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

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

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

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

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[removed];
    var labelWidth = document.defaultView. »
getComputedStyle( this, '' ).getPropertyValue( 'width' );
    var labelSpan = document.createElement( 'span' );
        labelSpan.style.display = 'block';
        labelSpan.style.width = labelWidth;
        labelSpan[removed] = labelContent;
    this.style.display = '-moz-inline-box';
    this[removed] = 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

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).

118 Reader Comments

Load Comments