Form field hints with CSS and JavaScript

My co-workers pointed out a nice effect on the Vox registration form. As you tab through each input field, some helper text appears in box out to the right. Try it out. Update (4/20/2007): It has been pointed out that Vox has changed their form, and it no longer uses this effect.

Vox Registration Form
Vox Registration Form

It’s a basic example of how helpful a little JavaScript and CSS can be in a form. Instead of the input hints always showing and potentionally cluttering a very simple form, only the hint for the currently focused input will show. This article will show a way to do this.

The HTML

We’ll start with a simple form. Vox’s form used a definition list to mark up the form, and I’ll be doing the same. I place the label in the <dt> tag, and the field in the <dd> tag, like so:

<dl>
  <dt>
    <label for="firstname">First Name:</label>
  </dt>
  <dd>
    <input
      name="firstname"
      id="firstname"
      type="text" />
  </dd>
  ...
  more <dt>'s and <dd>'s
  ...
</dl>

Immediately after each input field, add a span with a class of hint and create a hint for each input, like so:

<dl>
  <dt>
    <label for="firstname">First Name:</label>
  </dt>
  <dd>
    <input
      name="firstname"
      id="firstname"
      type="text" />
    <span class="hint">Use your real name!</span>
  </dd>
  ...
  more <dt>'s and <dd>'s
  ...
</dl>

View what we have so far – an unstyled form.

Some CSS

Let’s give this form some style. Assign our form-encompassing <dl> a fixed width and a position of relative. (This is important because our hints will be absolutely positioned relative to the containing <dl> rather than the <body>). Additionally, we’ll style the <dt> and <dd> so that the form takes some shape.

 
dl {
  position: relative;
  width: 350px;
}
dt {
  clear: both;
  float:left;
  width: 130px;
  padding: 4px 0 2px 0;
  text-align: left;
}
dd {
  float: left;
  width: 200px;
  margin: 0 0 8px 0;
  padding-left: 6px;
}

It’s coming along. It’s time to style the hints. Let’s add the following to the CSS.

.hint {
  position: absolute;
  right: -250px;
  width: 200px;
  margin-top: -4px;
  border: 1px solid #c93;
  padding: 10px 12px;
  background-color: #ffc;
}

See how it looks. All the hints are absolutely positioned, and their padding and containing content cause each of them to overlap a little. That’s ok, because we’ll only be showing one at a time. Add “display:none;” to the CSS.

.hint {
  display:none;
  position: absolute;
  right: -250px;
  width: 200px;
  margin-top: -4px;
  border: 1px solid #c93;
  padding: 10px 12px;
  background-color: #ffc;
}

Now that our form is styled and our hints are hidden, it’s time for JavaScript.

The JavaScript

I am going to prepare the input fields so that as focus is put on them, their corresponding hint appears. I do this preparation on page load, using “addLoadEvent”, because that’s how I roll.

function addLoadEvent(func) {
  var oldonload = window.onload;
  if (typeof window.onload != 'function') {
    window.onload = func;
  } else {
    window.onload = function() {
      oldonload();
      func();
    }
  }
}
 
function prepareInputsForHints() {
  var inputs = document.getElementsByTagName("input");
  for (var i=0; i<inputs.length; i++){
    inputs[i].onfocus = function () {
      this.parentNode.getElementsByTagName("span")[0].style.display = "inline";
    }
    inputs[i].onblur = function () {
      this.parentNode.getElementsByTagName("span")[0].style.display = "none";
    }
  }
}
addLoadEvent(prepareInputsForHints);

The function prepareInputsForHints looks for all <input> tags and cycles through them. When there is focus on that input field, we grab the first span tag in the <dd> in which the <input> resides, and we change it’s display to inline so that it’ll show up.

To keep things simple, I’ve assumed that there is only one <span> tag next to each <input> tag.

What about <select>‘s? We need to duplicate the logic we applied to the <input>‘s so that it will work with <select>’s, too.

function prepareInputsForHints() {
  var inputs = document.getElementsByTagName("input");
  for (var i=0; i<inputs.length; i++){
    inputs[i].onfocus = function () {
      this.parentNode.getElementsByTagName("span")[0].style.display = "inline";
    }
    inputs[i].onblur = function () {
      this.parentNode.getElementsByTagName("span")[0].style.display = "none";
    }
  }
  var selects = document.getElementsByTagName("select");
  for (var k=0; k<selects.length; k++){
    selects[k].onfocus = function () {
      this.parentNode.getElementsByTagName("span")[0].style.display = "inline";
    }
    selects[k].onblur = function () {
      this.parentNode.getElementsByTagName("span")[0].style.display = "none";
    }
  }
}
addLoadEvent(prepareInputsForHints);

Check out what we have. It’s functioning, for all inputs and selects, and it’s looking pretty good. There are still a couple of things we can do to help it, though.

Minor Tweaking

Right now the hint is just in a box. Notice how in Vox’s example, their hint box has a little arrow attached to it. Let’s add that pointer in there. We want an image that has the same border and fill as the hint box, with the exception of the left border, which we don’t want to exist. I’m going to use the same one Vox uses – a simple triangle.

pointer

This image will be added as a background image with the help of an additional span, which we need to insert within the hint in the HTML, and give a class of hint-pointer.

<span class="hint">Use your real name!
  <span class="hint-pointer">&nbsp;</span>
  </span>

Let’s style it so that it looks like it’s part of the hint box.

.hint .hint-pointer {
    position: absolute;
    left: -10px;
    top: 5px;
    width: 10px;
    height: 19px;
    background: url(pointer.gif) left top no-repeat;
}

That’s better.

Also, up to this point, we’ve assumed that every <input> will have a corresponding hint. What if we don’t need hints for some of those inputs? Who needs a hint for a field like “First Name”? We also don’t want to show hints with submit buttons (remember, they are <input>’s, too!)? As of now, if there is a missing hint, a JavaScript error is returned.

There is more than one way to account for this. Here’s one. Let’s add some logic to the JavaScript that checks to see if a span exists before trying to change the style of that span.

function prepareInputsForHints() {
  var inputs = document.getElementsByTagName("input");
  for (var i=0; i<inputs.length; i++){
    if (inputs[i].parentNode.getElementsByTagName("span")[0]) {
      inputs[i].onfocus = function () {
        this.parentNode.getElementsByTagName("span")[0].style.display = "inline";
      }
      inputs[i].onblur = function () {
        this.parentNode.getElementsByTagName("span")[0].style.display = "none";
      }
    }
  }
  var selects = document.getElementsByTagName("select");
  for (var k=0; k<selects.length; k++){
    if (selects[k].parentNode.getElementsByTagName("span")[0]) {
      selects[k].onfocus = function () {
        this.parentNode.getElementsByTagName("span")[0].style.display = "inline";
      }
      selects[k].onblur = function () {
        this.parentNode.getElementsByTagName("span")[0].style.display = "none";
      }
    }
  }
}
addLoadEvent(prepareInputsForHints);

I’d love to see other examples of similar effects. Let me know of any you like.

April 3, 2007: textarea example provided

April 3, 2007: Fixed IE6 bug.

It was pointed out that IE6 was showing some buggy behavior the first instance of showing the hints. The border wasn’t completely being drawn around the span. I was going to pass it off as “buggy IE6”, because I couldn’t find a fix, but thanks to David Carriger in the comments, I think we found something.

In the .hint CSS, we have been giving span.hint a background color using “background-color:#ffc”, but if we go ahead and give it a background image, too, IE6 will render span.hint properly.

.hint {
  display:none;
  position: absolute;
  right: -250px;
  width: 200px;
  margin-top: -4px;
  border: 1px solid #c93;
  padding: 10px 12px;
  background: #ffc url(pointer.gif) no-repeat -100px -100px;
}

Note that I gave it the same background image as the .hint-pointer span (less overhead that way), and positioned it with a negative margin so that it doesn’t actually show up. This is now fixed in the final example and downloadable zip.

See final example, which only shows hints for username and password (and fixes the IE6 bug!).

April 20, 2007): Vox.com, the site whose registration form this article is based on, has changed that form, and it no longer uses the effect.

May 14, 2007): If you like this post, you might also appreciate validation hints.


This post was ported from an old host and CMS, so many comments were lost. Below are the comments that I found were most helpful regarding this post that I salvaged. Some links or attributions may not be working correctly.


It could be easily done using :focus and the adjacent sibling selector. Something along the lines of:
span { display: none; }
input:focus + span { display: inline; }
of course this will only work in browsers that understand the pseudo selector ‘:focus’ and the adjacent sibling selector ‘+’ (tested and works in FF2)