Saturday, November 16, 2013

Placeholders as labels

This post is content previously published on www.cathmhaol.com

The highly-esteemed Jeremy Gillick wrote an interesting piece about using placeholders as field labels1. Of course, not being one to leave something good alone, and being the code and speed nut that I am, I had to tinker with the idea to see if I could do as well, or almost as well, with less code. Remember, less code means less complexity and a generally faster page/site.

So...let's take a look at a typical (US) billing address form. (If you want to figure out the deets with the code, you can jump directly there.)


This example is pretty simple, as is the markup behind it (shown below).

<form>
  <fieldset class="vcard">
    <legend>Billing Address</legend>
    <div class="fn">
      <input id="fn" placeholder="Name" type="text" />
      <label for="fn">Name</label>
    </div>
    <div class="adr">
      <div class="street-address">
        <input id="street-address" placeholder="Address" type="text" />
        <label for="street-address">Address</label>
      </div>
      <div class="extended-address">
        <input id="extended-address" placeholder="Suite/Apt" type="text" />
        <label for="extended-address">Suite/Apt</label>
      </div>
      <div class="locality">
        <input id="locality" placeholder="City" pattern="[A-Za-z\s\-]{1,}" type="text" />
        <label for="locality">City</label>
      </div>
      <div class="region">
        <input id="region" placeholder="State" pattern="[A-Z]{2}" title="Two letter state code" maxlength="2" type="text" />
        <label for="region">State</label>
      </div>
      <div class="postal-code">
        <input id="postal-code" pattern="[0-9]{5}(\-[0-9]{4})?" placeholder="ZIP Code" title="5 digit ZIP Code or ZIP+4" type="text" />
        <label for="postal-code">ZIP Code</label>
      </div>
    </div>
  </fieldset>
...
</form>

I've departed from Jeremy's markup a little - I'm using a text input for the state (region) and I'm using the pattern and title attributes from HTML5 for simple inline validation. Using this approach, you can easily localize the form by changing the pattern and title, and of course if you want you could easily switch the region input for a select.

After I have the markup, I write the CSS to make the form look more like the address would look on a parcel (in the US). This is just a matter of floating various pieces and giving them a reasonable width and margin. Since this is really only applicable to tablet and desktop, I'm going to wrap this positioning in a media query like so...

@media (min-width: 480px) {
  fieldset { border:1px solid black; }
  form { margin:auto; padding:1em; width:480px; }
  legend { position:static; }
  .vcard .fn { width:100%; }
  .vcard .adr .street-address { float:left; margin:0 1% 0 0; width:49%; }
  .vcard .adr .extended-address { float:left; margin:0 0 0 1%; width:49%; }
  .vcard .adr .locality { clear:left; float:left; margin:0 1% 0 0; width:69%; }
  .vcard .adr .region { float:left; margin:0 1% 0 0; text-align:center; width:9%; }
  .vcard .adr .postal-code { float:left; margin:0 0 0 1%; text-align:center; width:19%; }
}

I also do little things like aligning text to the center in the region and postal-code inputs and transforming the text to uppercase and giving a 3px top and bottom margin to the div's that wrap each field.

As you'll notice from Jeremy's post, the image he shows of the mobile device has full-width text entry. Because of the lack of real estate on mobile, that's pretty much a must-have. To do this, I set the display to block and the width to 100% for both the input and label tags. Since they're wrapped in div's we can just adjust the size of the div (like we did in the media query) and everything will work, easy-peasy.

Next, I want to hide the labels, but I don't want to affect accessibility, so I'm not going to use visibility or height - hiding by those properties typically takes the content out of screen readers - I'm going to use the clip property (and the position property, of course). So, I clip all the labels to a 1px rectangle - and now the labels are gone.

At this point I have an accessible form, but as Jeremy points out, one that also has a higher cognitive load when someone starts typing in the field. Here is where Jeremy and I really separate - Jeremy uses jQuery and I solve this problem a little differently. Here's my solution, which goes in the stylesheet.

input:focus + label { position:static; }

That's it. The clip property doesn't work with a static position, and a position static puts the label exactly where it's supposed to be - in fact where it's been all along.

Of course, there are no fancy transitions as there are with Jeremy's approach, but the code performs well and we're not adding extra weight in the form of JavaScript, so there is a benefit to offset the lack of animation.

One caveat if you've gotten this far: you'll have to adjust the layout for IE to get it to appear the way it should because of the way it handles float, margin, and width. Of course this post isn't really about a skeuomorphic parcel label, so you may opt to skip that part.





Notes and references. Links in the notes and references list open in a new window
  1. Gillick, Jeremy. Good UX for Placeholders as Field Labels. 2013-12-04




Complete code
<style type="text/css">
  * { font-family:Verdana; }
  fieldset { border:none; border-radius:5px; }
  div { margin:3px 0; }
  input, label { display:block; width:100%; }
  label { clip:rect(1px 1px 1px 1px); clip:rect(1px, 1px, 1px, 1px); position:absolute; }
  legend { clip:rect(1px 1px 1px 1px); clip:rect(1px, 1px, 1px, 1px); position:absolute; }
  input:focus { background-color:#ddd; }
  input:focus + label { position:static; }
  .region input { text-align:center; text-transform:uppercase; }
  .postal-code input { text-align:center; text-transform:uppercase; }
  @media (min-width: 480px) {
    fieldset { border:1px solid black; }
    form { margin:auto; padding:1em; width:480px; }
    legend { position:static; }
    .vcard .fn { width:100%; }
    .vcard .adr .street-address { float:left; margin:0 1% 0 0; width:49%; }
    .vcard .adr .extended-address { float:left; margin:0 0 0 1%; width:49%; }
    .vcard .adr .locality { clear:left; float:left; margin:0 1% 0 0; width:69%; }
    .vcard .adr .region { float:left; margin:0 1% 0 0; text-align:center; width:9%; }
    .vcard .adr .postal-code { float:left; margin:0 0 0 1%; text-align:center; width:19%; }
  }
</style>
<form>
  <fieldset class="vcard">
    <legend>Billing Address</legend>
    <div class="fn">
      <input id="fn" placeholder="Name" type="text" />
      <label for="fn">Name</label>
    </div>
    <div class="adr">
      <div class="street-address">
        <input id="street-address" placeholder="Address" type="text" />
        <label for="street-address">Address</label>
      </div>
      <div class="extended-address">
        <input id="extended-address" placeholder="Suite/Apt" type="text" />
        <label for="extended-address">Suite/Apt</label>
      </div>
      <div class="locality">
        <input id="locality" placeholder="City" pattern="[A-Za-z\s\-]{1,}" type="text" />
        <label for="locality">City</label>
      </div>
      <div class="region">
        <input id="region" placeholder="State" pattern="[A-Z]{2}" title="Two letter state code" maxlength="2" type="text" />
        <label for="region">State</label>
      </div>
      <div class="postal-code">
        <input id="postal-code" pattern="[0-9]{5}(\-[0-9]{4})?" placeholder="ZIP Code" title="5 digit ZIP Code or ZIP+4" type="text" />
        <label for="postal-code">ZIP Code</label>
      </div>
    </div>
  </fieldset>
</form>

No comments:

Post a Comment