A Better Implementation Of The Input Prompt Pattern

The Input Prompt pattern consists in prefilling a text field with a prompt as a way of supplying help information for controls whose purpose or format may not be immediately clear. In the browser, this pattern is most often implemented by dynamically modifying the value property of a text field element via the focus and blur event handlers attached to the text field, as shown in this example (live demo):

CSS:

.hint {
  color: #999;
}

Markup:

<input type="text" id="sbx">

JavaScript (based on YUI 3.0.0):

YUI().use('node', function (Y) {
    var sbx = Y.get('#sbx');
    Y.on('domready', function () {
        sbx.set('value', 'Search');
        sbx.addClass('hint');
        Y.on('focus', function () {
            if (this.get('value') === 'Search') {
                this.set('value', '');
                this.removeClass('hint');
            }
        }, sbx);
        Y.on('blur', function () {
            if (this.get('value') === '') {
                this.set('value', 'Search');
                this.addClass('hint');
            }
        }, sbx);
    });
});

Note: the code is intentionally implemented inside a domready event handler to work around issues related to form field caching.

The main problem with implementing this pattern using the value property is that the default text is used if the form is submitted while the input prompt is showing. Trying to work around this by testing the content of the text field when the form is submitted makes the default text impossible to use as a value. Another side effect of this implementation is that most developers will forget to attach a <label> element to the text field, leading to a confusing experience for screen reader users as they lack the necessary context to understand the purpose of the control.

A better implementation of this pattern consists in using a <label> element and positioning it on top of the text field it is attached to. Here is an example of this implementation (live demo):

CSS:

#container {
    position: relative;
}
#container label {
    position: absolute;
    top: 4px; *top: 6px; left: 3px;
    color: #999;
    cursor: text;
}
#container label.offscreen {
    left: -9999px;
}

Markup:

<div id="container">
    <label for="sbx" class="offscreen">Search</label>
    <input type="text" id="sbx">
</div>

JavaScript (based on YUI 3.0.0):

YUI().use('node', function (Y) {
    var sbx = Y.get('#sbx'),
        lbl = Y.get('#container label');
    Y.on('domready', function () {
        sbx.set('value', '');
        lbl.removeClass('offscreen');
        Y.on('mousedown', function () {
            setTimeout(function () {
                sbx.focus();
            }, 0);
        }, lbl);
        Y.on('focus', function () {
            lbl.addClass('offscreen');
        }, sbx);
        Y.on('blur', function () {
            if (sbx.get('value') === '') {
                sbx.set('value', '');
                lbl.removeClass('offscreen');
            }
        }, sbx);
    });
});

As always, I am looking forward to reading your comments and answering your questions in the comments section of this blog.

8 thoughts on “A Better Implementation Of The Input Prompt Pattern

  1. Pete B

    I was just writing one of these yesterday, and used an html 5 custom data attribute named ‘data-hint’.

    This has the advantage that you can define the hint as independent of the label text.

    I also tested and rejected whitespace only values, but that may be a little ott.

  2. Nicholas C. Zakas

    I’ve been trying to dissuade people from the first pattern you mention. That tends to be the easiest to implement, but also the least semantic. I really like the second pattern, I think that makes the most sense since the text visibly inside of the textbox really does act like a label. Thanks for sharing the better approach. :)

  3. Luke

    Nice. I put together something closer to the first type some time ago, but using the value modification to display example content rather than labels, leaving proper labels displayed.

    It turned out, though, that I ended up spending more time dealing with the field auto fill. It turns out that domready is too early for some browsers. It is likely working in your case because YUI 3′s async module loading spans the time before the window’s onload event.

    What I found was that some browsers fill the value before domready, others before window onload, and Opera (tested in 9.6, not in 10 yet) auto filled just *after* window onload. The end result was that to reliably deal with all A grade browsers’ value recall, the init code needed to be executed via a short setTimeout from a window.onload subscriber. It made me sad.

  4. Thomas Broyer

    The best implementation, of course, is to rely on the browser’s own implementation: [input placeholder="Search"] (well, in the case of a search field, you’d rather even use [input type=search placeholder="Search"])

    placeholder=”” is implemented in WebKit for a long time (which means Safari and Chrome); for others you’d then rely on JavaScript to create a [label] and use your CSS positioning trick (which I concur is the best solution, compared to changing the value=”” attribute’s value). To style the placeholder, you can use the ::-webkit-input-placeholder pseudo-element (input::-webkit-input-placeholder).

    …and to detect whether placeholder=”” is implemented.

    The only “issue” with placeholder=”” is that it isn’t meant to replace [label], which you seem do be doing here:

    Also, just wondering, couldn’t you position the [label] *below* the [input] and dynamically changing the [input]‘s background (from transparent to white) instead of moving the [label] around? the only drawback that I can see is that as soon as you style the background you no longer have the “platform” styling (thin blue border on Windows for instance) and you have to style the border too.

  5. Dominykas

    As Luke says, the real problem is the autofilled values, which make things look very very very ugly. Especially if one of the fields is a password field… So, you do need to init the default values with timeout.

    The label approach also has problems with positioning, since every browser has different margins/paddings for the input elements. But that can be solved with enough determination :)

    Another problem is that some browsers keep the focus upon page reload/back/forward - which means that you also need to double check the hasFocus upon init to avoid changing the value of the field when the user is already about to start typing.

    - focus on the input, F5 in IE. (The actual code, I think, has a bug for activeElement - can’t remember if I fixed it)

  6. Rik

    Nice implementation for present use.

    Hopefully, HTML5 will ease this behavior through the placeholder attribute (currently implemented in WebKit browsers).

Comments are closed.