Autopopulating text input fields with JavaScript

For both accessibility and usability reasons, all input fields and other form controls except buttons should have an associated label that clearly states what the purpose of the control is, or what kind of input the user is expected to fill it with.

Sometimes the visual design places restrictions on those labels, in some cases to the extent that there is no room for a label. Perhaps one of the more common examples of that is for site-wide search forms, where the graphic designer often will not accept a visible label. It’s not an ideal situation, but it does happen, so we need to make the best of it. And since I’ve had to do this in a recent project I thought I’d describe the technique I ended up using.

Few people will argue against the need to explain to users what they are supposed to enter into text input fields. One common workaround when no label can be displayed is to put some placeholder text in the text field and let that act as the label.

This approach works reasonably well, but it burdens the user with having to clear the input before entering their own text, which can lead to frustration and mistakes. An approach that avoids that is using JavaScript to clear the input when it receives focus. Since that won’t work when JavaScript support is missing, JavaScript should be used to insert the placeholder text as well.

Not having a visible label doesn’t mean there can’t be a label in the markup though. And you really need that label, since otherwise some users will have no clue what the field is there for. Apple’s VoiceOver screen reader, for instance, announces “blank, edit text” when there is no label associated with a text input. However, if a label is there in the markup but visually hidden with CSS, VoiceOver will say “[Label text], edit text”. Much better, right?

As a bonus, the hidden label will also help people browsing without support for JavaScript or CSS. Since the placeholder text is inserted with JavaScript I figured it makes sense to let the script hide the label as well.

And of course I wanted to do all of this unobtrusively, without any kludges like the inline onfocus and onblur event handlers that are often suggested when similar methods are described.

The HTML

I’ll use a simple search form as an example, though this is applicable to any text input fields. Here is the example markup:

  1. <form action="/search/">
  2. <div>
  3. <label for="searchtext" class="structural">Enter search text</label>
  4. <input type="text" name="searchtext" id="searchtext" title="Enter search text" class="populate">
  5. <input type="submit" value="Search">
  6. </div>
  7. </form>

There’s nothing out of the ordinary here, but a number of the attributes I use here are important for this technique. First a bit about the label element’s class attribute (which is in this markup snippet for illustrative purposes only – in reality it is put there by the script).

The CSS

The label element has a class attribute with the value structural. This is a class name I tend to use for elements that should be hidden from people using a graphical browser with CSS support. The corresponding CSS is this:

  1. .structural {
  2. position:absolute;
  3. left:-9999px;
  4. }

This visually positions the label way outside the viewport, but it is still in the document flow and is not hidden from screen readers. As I mentioned above, this class will be added with the script and won’t be in the actual markup.

The JavaScript

With the label hidden to please the graphic designer, let’s look at the attributes for the input element. The title attribute contains the text that the script will put in the text field. You could argue that the text in the label element would be just as appropriate, and you may be right. In this case the text is the same as that of the input element’s title attribute, so using the label text would trim the markup. I decided to use the title attribute anyway to have the flexibility of not having to use exactly the same text as in the label.

The script looks like this (with the utility functions getElementsByClassName() and addEvent() removed for brevity):

  1. var autoPopulate = {
  2. sInputClass:'populate', // Class name for the input elements to autopopulate
  3. sHiddenClass:'structural', // Class name that gets assigned to hidden label elements
  4. bHideLabels:true, // If true, labels are hidden
  5. init:function() {
  6. // Check for DOM support
  7. if (!document.getElementById || !document.createTextNode) {return;}
  8. // Find all input elements with the given className
  9. var arrInputs = autoPopulate.getElementsByClassName(document, 'input', autoPopulate.sInputClass);
  10. var iInputs = arrInputs.length;
  11. var oInput;
  12. // Loop through the found input elements
  13. for (var i=0; i<iInputs; i++) {
  14. oInput = arrInputs[i];
  15. // Make sure it's a text input. If not, skip to the next input.
  16. if (oInput.type != 'text') { continue; }
  17. // Hide the input's label
  18. if (autoPopulate.bHideLabels) { autoPopulate.hideLabel(oInput.id); }
  19. // If value is empty and title is not, assign title to value
  20. if ((oInput.value == '') && (oInput.title != '')) { oInput.value = oInput.title; }
  21. // Add event handlers for focus and blur
  22. autoPopulate.addEvent(oInput, 'focus', function() {
  23. // If value and title are equal on focus, clear value
  24. if (this.value == this.title) {
  25. this.value = '';
  26. // Make input caret visible in IE
  27. this.select();
  28. }
  29. });
  30. autoPopulate.addEvent(oInput, 'blur', function() {
  31. // If the field is empty on blur, assign title to value
  32. if (!this.value.length) { this.value = this.title; }
  33. });
  34. }
  35. },
  36. hideLabel:function(sId) {
  37. // Find all label elements
  38. var arrLabels = document.getElementsByTagName('label');
  39. var iLabels = arrLabels.length;
  40. var oLabel;
  41. // Loop through the found label elements
  42. for (var i=0; i<iLabels; i++) {
  43. oLabel = arrLabels[i];
  44. // If the value of the label's for attribute equals the input element's id, hide the label
  45. if (oLabel.htmlFor == sId) {
  46. oLabel.className = oLabel.className + ' ' + autoPopulate.sHiddenClass;
  47. }
  48. }
  49. }
  50. };

The extensive comments I have written to explain what is going on make the script look a lot bigger than it really is. If you use a library like DOM Assistant or jQuery you can cut down on the length of this script a lot more. I like keeping demo scripts library agnostic though, so I’ll leave that as an exercise for the reader.

The full script, available in autopopulate.js, contains the getElementsByClassName() and addEvent() utility functions, so if you want to you can use it as is. If you’re already using other functions that do the same thing I’d recommend using those instead.

I’ve made a small demo form where you can see this in action. With JavaScript enabled, the label element is hidden and the value of the input element’s title attribute is copied to the value attribute. If you disable JavaScript and reload the page, the label is displayed above the text input, which is left empty.

You may notice that the label is displayed briefly before the script hides it. This can be circumvented by running the script when the DOM is ready instead of on window.load, but how to do that is beyond the scope of this demo.

I definitely prefer displaying form labels, but if it simply is not possible due to design or space constraints, I think this is a reasonable workaround.

Posted on October 17, 2007 in Accessibility, JavaScript, Usability

Comments

  1. October 17, 2007 by Chris

    How timely, I was just about to write some code to do exactly this.

    One issue I always have when hiding content with JavaScript is the slight delay between the HTML loading and the JS loading/executing which results in the text being visible briefly and then the entire page shifting as it is hidden. This becomes more of an issue when dealing with larger areas of content that are hidden.

    Just wondering if you had any tricks to work around this.

  2. Definitely a helpful snippet.

    @Chris: Roger does give a tip for working around the flicker. Read the last paragraph on the demo page.

    It’s definitely always a good idea unobtrusive JavaScript to have the mindset that JavaScript should be used to enhance the page, rather than provide base functionality. This snippet definitely does that. Thanks Roger!

  3. October 17, 2007 by Chris

    Oops, I clicked the link to the demo form and didn’t read anything after. I shall now go do some research.

  4. October 17, 2007 by Jim D

    Why…

    .structural {
        position:absolute;
        left:-9999px;
    }
    

    instead of…

    .structural {
        display: none;
    }
    

    Especially since this would avoid the…

    You may notice that the label is displayed briefly before the script hides it.

    Or do screen readers not read items where display is set to none in the CSS?

    I’ve been marking up my forms with <fieldset> and <legend> and hiding the legend via display:none, under the assumption that screen readers ignored CSS. Hrm….

  5. Nice technique Roger. I think I might actually prefer this method to the method discussed at A List Apart, because it’s always tough to get the labels looking just right over the input with that method. On the other hand, there’s already a jquery plugin for that method. It could be days before someone writes one for yours. :)

  6. You’re a genius. :) I’ll definitely make myself a JQuery version for some form rewriting which is on my todo-list.

  7. I have to agree with Jim D above. As I was reading this, I had the exact same question.

  8. October 17, 2007 by Roger Johansson (Author comment)

    Chris:

    One issue I always have when hiding content with JavaScript is the slight delay between the HTML loading and the JS loading/executing

    You can avoid that by running the script when the DOM is ready. Most libraries have functionality for that, or you can write your own. Google for DOM ready to get many different implementations.

    Jim D:

    Or do screen readers not read items where display is set to none in the CSS?

    That’s exactly the problem. Screen readers in general do not ignore CSS since they run on top of a browser.

    Scott:

    On the other hand, there’s already a jquery plugin for that method. It could be days before someone writes one for yours. :)

    :-)

  9. That’s a beautifully accessible method, but where I work, flashes of un-script-styled content are unacceptable. I’m not aware of the current state of screen readers—is media=”reader” honored?

  10. (In my experience, DOMContentLoaded still isn’t instantaneous for large pages.)

  11. Really strange — i did same things three days ago for my new site, but with label text inside inputs and some special onfocus/onblur manipulation (jQuery):

    http://pepelsbey.net/pro/label/

    …plus onsubmit action to prevent sending default values to server.

    What do you think?

  12. You could create the css styles for .structural using javascript and always apply the class=”structural” in the html (instead of applying it dynamically). That would avoid the flicker issue as long as your javascript comes before the element on the page.

  13. Just as an exercise, this is how you’d write this with jQuery:

    $(function(){
      $('.populate').each(function(){
        $(this).val( $(this).attr('title') ).focus(function(){
          $(this).val('');
        });
        $('label[@for='+$(this).attr('id')+']').addClass('structural');
      });
    });
    

    As an aside, jQuery will execute this when the DOM loads rather than the page, so you shouldn’t see the label at all.

    Also, I’ve not tested this, but it should work!

  14. Just a quick note to enforce Eric’s comment above. In certain situations DOM ready isn’t fast enough to prevent the flicker either. It won’t be necessary in all situations but where it is using JS to apply styling upfront is the most robust way to ensure no flicker occurs.

  15. October 17, 2007 by Mike Czepiel

    This is one of my favorite little tricks. To aid in making search fields in particular look more “search like” to please the designers I’ve also often flipped from input to the non-standard search field while doing the label-initial-text replacement where appropriate.

    Of course you can also go all out and style the field accordingly to emulate that same look in browsers that don’t support the search input type and even emulate some of the behavior of the search field: providing a clear button, clearing on esc.

    Anyway, nice way to enhance a form while maintaining accessibility, glad to see it written up so clearly.

  16. October 18, 2007 by Roger Johansson (Author comment)

    Braden:

    I’m not aware of the current state of screen readers—is media=”reader” honored?

    Not as far as I know.

    pepelsbey: I didn’t look at your implementation in detail, but it seems quite similar. I’d move the label elements before their inputs in the source though to make the form usable with JS off. You don’t need the tabindex attribute either - the inputs are already in logical order in the markup.

    seb:

    Just as an exercise, this is how you’d write this with jQuery

    That’s only a partial solution - you’d need to handle blur as well :-).

    Ed:

    In certain situations DOM ready isn’t fast enough to prevent the flicker either.

    I’ve been working on some pretty large and “heavy” stuff lately and have not come across that yet. Any idea of what specifically may cause that?

  17. I think this is a great idea! Most web developers tend to get content when “losing” the battle with the designer, and end up not going the whole nine yards.

    This is a great example how you can create good accessible code and then enhance it with JavaScript!

  18. Didn’t fully read through exactly what the script does… Here’s a fully working version in jQuery, also adding a class “inputLabel” which can be used to make the label text grey…

    $(function(){
      $('.populate').each(function(){
    
        var e = $(this);
        if (e.attr('title').length &! e.val().length) {
          e.val(e.attr('title'));
        }
        if (e.attr('title') == e.val()) e.addClass('inputLabel');
    
        e.focus(function(){
          var e = $(this);
          if (e.attr('title') == e.val()) {
            e.val('').removeClass('inputLabel');
          }
        }).blur(function(){
          var e = $(this);
          if (e.attr('title').length &! e.val().length) {
            e.val(e.attr('title')).addClass('inputLabel');
          }
        });
    
        $('label[@for='+$(this).attr('id')+']').addClass('structural');
    
      });
    });
    
  19. October 18, 2007 by Pete B

    The Javascript will only run once all linked script files are loaded into the browser. So if you have a 50k javascript library on the page, the will have to wait which may cause a delay.

    Placing scripts at the bottom of the page rather than the top is the natural way to achieve domready firing

  20. Brilliant idea - much better than not having a label and using a default input value.

    Personally, I’m not against JavaScript changing pages after the page has loaded (within reason, anyway). That’s the way progressive enhancement should work - the page is usable before the download has completed.

    I think it’s only noticeable on your demo because there’s little else on the page. It wouldn’t be so bad on a normal web page.

  21. Why use the title attribute when the label text already exists?

    Here’s my take on the hidden label technique - you can never have too many ways to skin a cat. :)

  22. October 18, 2007 by Brian LePore

    Matthew:As the author said in the article he toyed with the idea but went with the title attribute for flexibility.

    Pete: While there are advantages of placing JS at the bottom of the page, achieving DOMready is not one of them. You’re correct that JavaScript blocks the page display. That’s why it’s good for scripts that are going to change the look of the page to be in the head and call DOMready because then they’re code is loaded and will fire once the DOM is fully available. In this example placing the scripts at the bottom would mean that the code would show the label and then once it hits the script tag it will fire the event and cause the labels to be applied the class to hide them.

    About the hiding technique, while it is good there may be issues using it with older versions of Opera and Safari. If true, using off-top may be preferred, but it seems negligible at this point.

  23. I’ve been working on some pretty large and “heavy” stuff lately and have not come across that yet. Any idea of what specifically may cause that?

    Anything that prevents the entire page from being parsed all at once by the browser.

    The most obvious example is a javascript include in the middle of your body. The DOM won’t be ready until that js file has been downloaded, but the browser will have already parsed any html before that include. Here’s a quick example.

  24. I’d just like to note that the way you add the classname to the label causes problems in opera when (like in this case) the element doesn’t yet have a classname. The leading space confuses opera (not sure about newer versions) and will thus not hide the label.

  25. Thanks for the interesting demos, Eric. The Javascript style creation is really clever.

  26. nicely done. ages ago, i wrote a little script for form prepopulation myself, but it’s definitely much more basic than this.

  27. Cool, I just wrote a jQuery-version a few weeks ago for a freelance thing but that uses the input’s alt-attribute rather than its title-attribute.
    I actually skipped the label on that project because my hopes were that the alt-attribute would be read by a screen-reader. Anyone know if that’s true?
    If screen-readers read out the alt-attribute, could that be a substitution for a label in those cases were the designer doesn’t want them and it’s obvious what the form is for if you can see it?

  28. Although it isn’t helpful in this case, you want to ‘hide’ the label irrespective of javascript support, you can put any css styles that you want if there’s javascript support into a separate stylesheet and use a document.write in the head to link to it. This avoids waiting for the document to load before changing the look.

  29. October 21, 2007 by Roger Johansson (Author comment)

    Andreas:

    I actually skipped the label on that project because my hopes were that the alt-attribute would be read by a screen-reader. Anyone know if that’s true?

    The only form element that can take an alt attribute is <input type="image">. You can use the title attribute for all form elements though, and some (but not all) screen readers will read that if they can’t find a label. You really should always add a proper label. Hide it with CSS if you must, but put it in the source code.

  30. Ok, thanks for that. When I’ve used the alt-attribute in other types of inputs the validator has never complained. Is it really invalid or just not proper use?
    I’ll change my jQuery-snippet so it uses the title-attribute instead.
    Cheers.

  31. October 22, 2007 by Olle Lundberg

    Roger, you should think of swaping your addEvent method for the rock solid one, which will help you avoid memory leaks in IE.

    http://www.dustindiaz.com/rock-solid-addevent/

  32. October 22, 2007 by Roger Johansson (Author comment)

    Andreas: As I understand it, the alt attribute slips through validation for input elements because the validator doesn’t make a special case for type="image". For the same reason, the validator does not flag <input type="image"> as an error despite it having no alt attribute.

    Either way it’s meaningless since alt is meant as an alternative for an image or other graphical object. So for your purposes using the title attribute is better.

  33. Here is a slightly easier to read and cleaner version of this functionality written with help of Prototype. It also prevents from submitting the label text as an input value to the server. Enjoy.

  34. October 22, 2007 by Devan

    Nicely done script. I’m also working on the basics of jQuery. I tried substituting the javascript with Seb’s code, but nothing happened. Apparently additional code is needed? There is a jQuery watermark plugin, but I’d rather understand how it’s done. Thanks.

  35. I find it most entertaining how you get the predictable “this is how you can do it with xyz library” comments. It then becomes obvious that the point of Rogers article was completely missed. So far I counted three versions using jQuery, and one with Prototype. Roger is teaching “JavaScript.” Not a library, pure JavaScript.

    <humor>

    Oh, by the way, here’s how you would do this in my brand new library to come out…

    DED.makeLabelTextInputThing('#element');
    

    Beat that!

    </humor>

  36. Isn’t using both the label and title a little redundant?

    I’ve never used a screen reader but I would guess that hearing repetitive content like this (even with subtle dissimilarity) would be more of an annoyance than an assistance?

    That being said I’ll definitely be using this (with the aforementioned adjustment) on my sites going forward. Great balance of proper xhtml tagging, usability, and unobtrusiveness. Thanks!

  37. To expand on Seb’s Post (#13), if you want to have the text re-populate when the focus is changed…

    $(function(){
      $('.populate').each(function(){
        $(this).val( $(this).attr('title') ).focus(function(){
          $(this).val('');
        });
        $(this).val( $(this).attr('title') ).blur(function(){
          $(this).val( $(this).attr('title') );
        });
        $('label[@for='+$(this).attr('id')+']').addClass('structural');
      });
    });
  38. November 14, 2007 by Arik Jones

    Has anyone figured out how to clear the fields before submission if the default value wasn’t replaced? (Using the original code of course…)

Comments are disabled for this post (read why), but if you have spotted an error or have additional info that you think should be in this post, feel free to contact me.