Minimal Login Form with Drupal

Digital Alternatives
Home

One technique that is seen on A List Apart for creating a minimal login form design was used as a basis for what is done at Digital Alternatives. Unfortunately, given the current level of implementation of CSS3 in even the most modern browsers, and the non-support in others, this is impossible to accomplish consistently without some level of scripting.

So, taking what we've found in the article above, we can adapt this to work with Drupal. By default, the login form sits in a block called #user-login-form, so we can put together some simple CSS rules to use layering to hover a gray label over the login and password fields:


#user-login-form .form-item { 
  position: relative; height: 2em; width: 100%;
}

#user-login-form .form-item label,
#user-login-form .form-item input {
  font-size: 12pt;
  font-family: Courier, monospace;
  line-height: 1em;
}

#user-login-form .form-item label {
  position: absolute;
  color: #888; 
  top: 0;
  height: 1em;
  margin: 0; padding: 4px 4px;
  z-index: 3;
}

#user-login-form .form-item input {
  position: absolute;
  width: 90%; height: 1em;
  margin: 0; padding: 2px;
  top: 0; left: 0;
  border: 2px inset #999;
  z-index: 4;
}

/* Only do fancy frippery if this class gets attached: which should
 * happen only if scripting is enabled */
#user-login-form .form-item label.overlabel { z-index: 5; }

/* Hide the submit buttons and "sign up" links. Enter key
 * should do it. */
#user-login-form .form-submit,
#user-login-form .item-list { display: none; }

Now, ideally we should be able to have this function entirely within CSS, for example by writing the following rules to select the input which has a focus or has a value set:


/* Pull the input up when it's focused or has a value */
#user-login-form input:focus,
#user-login-form input[value] { z-index: 6; }

Pity things don't work out this way. With only line two, using the :focus psuedo-class, the labels will at least be occluded during input, but once the input loses focus, the label shows back up and garbles it. In a perfect world, the selector on line three would only select when the value attribute contains a non-blank string, but the CSS spec only says that it be present. Which it will always be for input elements.

Sad. Enter JavaScript:


/** smart labels from alistapart
 * http://www.alistapart.com/articles/makingcompactformsmoreaccessible
 */
function initOverLabels (form) {
  if (!document.getElementById || !form) return;      

  var labels, id, field;

  // Set focus and blur handlers to hide and show 
  // labels with 'overlabel' class names.
  labels = form.getElementsByTagName('label');
  for (var i = 0; i < labels.length; i++) {
      // Skip labels that do not have a named association
      // with another field.
      id = labels[i].htmlFor || labels[i].getAttribute('for');

      if (!id || !(field = document.getElementById(id))) {
        continue;
      } 

      // Change the applied class to hover the label 
      // over the form field.
      labels[i].className = 'overlabel';

      // Hide any fields having an initial value.
      if (field.value !== '') {
        hideLabel(field.getAttribute('id'), true);
      }

      // Set handlers to show and hide labels.
      field.onfocus = function () {
        hideLabel(this.getAttribute('id'), true);
      };
      field.onblur = function () {
        if (this.value === '') {
          hideLabel(this.getAttribute('id'), false);
        }
      };

      // Handle clicks to label elements (for Safari).
      labels[i].onclick = function () {
        var id, field;
        id = this.getAttribute('for');
        if (id && (field = document.getElementById(id))) {
          field.focus();
        }
      };

    }
  }

function hideLabel (field_id, hide) {
    var field_for;
    var labels = document.getElementsByTagName('label');
    for (var i = 0; i < labels.length; i++) {
	field_for = labels[i].htmlFor || labels[i].getAttribute('for');
	if (field_for == field_id) {
	    labels[i].style.zIndex = (hide) ? '3' : '5';
            return true;
	}
    }
}

// Use a better initialization method, please!
window.onload = function() {
  var form = document.getElementById('user-login-form');
  setTimeout(function() { initOverLabels(form); }, 50);
};

What I do differently from the ALA version is use zIndex instead of text-indent to show or hide the input label. This allows the code to maintain semantic correctness while keeping text where it should be. Moving the text far off to the side using text-indent could end up popping the text in someone's way on super wide screens. Not likely, but possible. I also adjusted initOverLabels to accept an optional element which to limit the scope selected elements. Lastly, applies the "overlabel" class only once to the labels who deserve it.