Advertisement

Creating Friendlier, “Conversational” Web Forms

by

Web forms are constantly a hot topic when it comes to web design and user interaction. The reasons for this are vast, but one of the more obvious reasons is that forms are the most basic way for a user to input information into your application. In this article, we'll discuss a few techniques that allow your forms to respond to the user's input, while helping to obscure unnecessarily confusing or overwhelming elements.

Let's get started!


Forms Are Like Conversations

Imagine a form as a conversation you are having with your user. In a conversation, there is a sense of back and forth which occurs, where each party prompts the other party to respond. For instance, let's say your user is coming to either sign up or sign in. In both cases, you need their email, so conversationally, why not start with just that?

Give me your email, and I'll check and see if you have an account. If so, I'll ask you for your password, otherwise I'll let you give me a new password and then confirm that password with me.

So when the user arrives to the page with the form, they have a very clear prompt: "What's your email address?"

The Annoying Guy at the Party

We all know that guy. He's the guy who you avoid because he talks incessantly, and doesn't really seem to listen to what you have to say. Frankly, that guy is really overwhelming, and I'm not sure anyone enjoys that. Don't be that guy.

In your forms, learn a lesson from him. Instead of confronting your user with a massive number of inputs, consider reducing the user's input workload by creating reactive inputs.

Let's take a look at a simple example.


Login Form

The login process looks something like this: I enter my email address, then I enter my password, then I press enter. In this example, you'll learn how to show each of these fields only after the previous field is complete, using just HTML and CSS. We'll be building this, and then a modified version.

Let's start with a first shot at the markup.

<form method="post">
    <input type="email" name="email" required>
    <input type="password" placeholder="Password..." required>
    <input type="submit">
</form>

If that looks a little daunting, don't worry, I'll explain each piece. Let's start with the email input. We see a few attributes added to the tag beyond just the name. First of all, the input type is set to "email"; this is a relatively new type of input that gives us some special behavior in supporting browsers. For instance, on the iPhone, the "@" symbol will show up on the primary keyboard.

Another feature of input types is that HTML5 forms have browser-level validation abilities. This means that you don't have to write JavaScript to perform validation on basic form elements! Notice that we have a "required" attribute on both the email and the password elements. Once those two elements are filled in and their values are considered valid by the browser, you can even target them with the :valid pseudo selector.


Regex Land

This is awesome, but we could make it better. For instance, the browser accepts "a@b" as an acceptable email. The reason for this is because an email address could be set up to go to something like "something@localhost". Our password can be anything we enter, so a single character password like "a" would be considered valid. Let's add some regular expressions to fix these problems.

Note: if you aren't familiar with regex, that's okay! We cover a small amount here to show how you might approach using it for common tasks in HTML5 form validation, but you can stick to the browser implementations and still follow the tutorial. Just skip this section, and keep on going!

Another note: you could also check out the Regular Expressions for Dummies: Screencast Series by Jeffrey Way.

<form method="post">
    <input type="email" name="email" required pattern="[^ @]*@[^ @]*\.[a-zA-Z]{2,}">
    <input type="password" placeholder="Password..." required pattern=".{5,}">
    <input type="submit">
</form>

Let's walk through each part of these patterns. Regular expressions are a way to describe and compare the format of a string.

We'll start with the email pattern.

pattern="[^ @]*@[^ @]*\.[a-zA-Z]{2,}"
  • [^ @]* - match any number of characters that is not an @ sign or a space.
  • @ - a literal @ sign
  • \. - a literal .
  • [a-zA-Z] - any letter, both uppercase and lowercase
  • [a-zA-Z]{2,} - any combination of two or more letters

Putting all of this together, we can see that this expression says that an email is any set of characters except an @ sign, followed by an @ sign, followed by any set of characters except an @ sign, followed by a period, followed by at least two letters.

We can apply a much simpler regex if we want to only validate based on the length of the value:

pattern=".{5,}"

The . means "any character", and the {5,} says that there must be at least 5 of them.

With these patterns in place, the element will not be considered valid until a value satisfying the pattern is entered.

We'll round out the markup with some extra polish and a few more non-form elements.

<link href='http://fonts.googleapis.com/css?family=Varela+Round' rel='stylesheet' type='text/css'>
<div class="login">
  <h3>Log In</h3>
  <form action="." method="post">
    <input type="email" placeholder="Your Email" pattern="[^ @]*@[^ @]*\.[^ @]{2,}" required />
    <input type="password" placeholder="Password..." required pattern=".{5,}" />
    <input type="submit" />
  </form>
</div>

Some :valid CSS Magic

Now that we have some markup for our login form, let's see what we can do with CSS to respond to the user's input.

We want to show the next form element when the current one is valid. Let's start by hiding the form elements themselves, in an accessible way, so that screen readers will still see the full form, and so that autocomplete will be able to fill the values in. (Chris Coyier talks about why not to use display:none on these kinds of things here.) We'll use the visuallyhidden method described in Chris' post.

.visuallyhidden {
  position: absolute;
  overflow: hidden;
  clip: rect(0 0 0 0);
  height: 1px; width: 1px;
  margin: -1px; padding: 0; border: 0;
}

We'll start with some basic styles.

body {
  background-color: #245245;
  font-family: 'Varela Round', sans-serif;
}
h3 {
  color: #555;
  font-size: 2em;
  text-transform: uppercase;
  letter-spacing: .3em;
}
.login {
  width: 300px;
  margin: 50px auto;
  background: #f5f9fa;
  padding: 50px;
  border-radius: 8px;
  text-align: center;
}

input[type=password],
input[type=email]{
  width: 100%;
  border: 1px solid #ccc;
  padding: 8px;
  box-sizing: border-box;
}

input[type=password]:focus,
input[type=email]:focus {
  border: 1px solid #888;
  outline: none;
}
input[type=submit] {
  background-color: #F29E1E;
  width: 50%;
  border: none;
  padding: 8px;
  box-sizing: border-box;
  color: #fff;
  font-family: "Varela Round";
  font-size: 1em;
  text-transform: uppercase;
}
input[type=submit]:hover {
  background-color: #DB8F2A;
  cursor: pointer;
}
input {
  margin: 6px 0;
}
conversational-form-base-all

This gives us a simple centered login form. Let's take the visually hidden concept and apply it to the elements we want to hide.

input[type=password],
input[type=submit]{
  overflow: hidden;
  clip: rect(0 0 0 0);
  height: 1px; width: 1px;
  margin: -1px; padding: 0; border: 0;
  opacity: 0;
  transition: opacity 0.4s, height .4s, padding-top .4s, padding-bottom .4s;
}
conversational-form-base

We're going to be performing some animation, so we've extended the class a bit to also include opacity, removed the absolute positioning, and added the transition definition.

Note: we aren't including the vendor prefixes for the transition, but you should!

Now let's show them when they should be shown, using the :valid pseudo selector and the + sibling selector.

input[type=email]:valid + input[type=password]{
    opacity: 1;
    position: relative;
    height: 30px;
    width: 100%;
    clip: none;
    margin: 12px auto;
    border: 1px solid #ccc;
    padding: 8px;
}
input[type=password]:valid + input[type=submit]{
  opacity: 1;
    position: relative;
    height: 40px;
    width: 50%;
    clip: none;
    margin: 12px auto;
    padding: 8px;
}
conversational-form-animation-all

A Note About Best Practices

It's important to consider the user's goals and expectations in a web form. Sometimes, it is a sufficient solution to completely hide the inputs from the user. However, in some cases, this may have an adverse effect, causing the user to feel as though their isn't a clear roadmap for the process of filling in the form, and that it may take more time than they are prepared to give up.

A solution to this problem would be to use some kind of obscuration without completely hiding the inputs that help create the narrative feedback approach to forms that have a clear goal and relatively predictable expectations. Here's a modified version that uses scaling, webkit's blur filter, opacity, and pointer-events:none to "present" the inputs to the user as they are available. We also need to make sure the pointer-events are restored when the inputs are available, so that the user can click into them.

conversational-form-animation-modified
Take a look at the demo
body {
  background-color: #245245;
  font-family: 'Varela Round', sans-serif;
}
h3 {
  color: #555;
  font-size: 2em;
  text-transform: uppercase;
  letter-spacing: .3em;
}
.login {
  width: 300px;
  margin: 50px auto;
  background: #f5f9fa;
  padding: 50px;
  border-radius: 8px;
  text-align: center;
}

input[type=password],
input[type=email]{
  width: 100%;
  border: 1px solid #ccc;
  padding: 8px;
  box-sizing: border-box;
}

input[type=password]:focus,
input[type=email]:focus {
  border: 1px solid #888;
  outline: none;
}

input[type=submit] {
  background-color: #F29E1E;
  width: 50%;
  border: none;
  padding: 8px;
  box-sizing: border-box;
  color: #fff;
  font-family: "Varela Round";
  font-size: 1em;
  text-transform: uppercase;
}
input[type=submit]:hover {
  background-color: #DB8F2A;
  cursor: pointer;
}
input {
  margin: 6px 0;
}

input[type=password],
input[type=submit]{
  -webkit-filter: blur(1px);
  transform: scale(0.9);
  opacity: .4;
  transition: all .4s;
  pointer-events: none;
}

input[type=password]:valid + input[type=submit],
input[type=email]:valid + input[type=password] {
  pointer-events: auto;
  -webkit-filter: none;
  transform: scale(1);
  opacity: 1;
}

One quick bug to point out: when a user presses tab, they will enter the next form element. We prevent clicking into the next form element with pointer-events:none, but there isn't a "focusable" attribute in CSS. Instead, we would need to control this behavior with a touch of JavaScript (jQuery flavored).

var inputs = $("input");
$(document).on("keypress", "input", function(){
    inputs.each(function(i,el){
        var $el = $(el);
        if ($el.is(":valid")){
            $el.next("input")[0].disabled=false;
        } else {
            $el.next("input")[0].disabled=true;
        }
    });
});

This JavaScript watches our form inputs and disables/enables inputs based on their previous sibling's ":valid" state. This will disallow us from focusing the element. The pointer-events: none still functions to keep our submit input from receiving the hover state.


Other Use Cases

Imagine a form that has multiple "tracks", as in our "log in or sign up" example. A user may be selecting multiple options that would necessitate a new selection of form elements, such as selecting a state for shipping or a time range for a dinner reservation. In these cases, simple validation may not be enough, and instead we would use a JavaScript-powered solution.

Let's take, for instance, the "log in" versus "sign up" example. To solve this, we must ask the server if there is a user that matches the provided email login.

<form action="">
    <input type="email">
    <input name="login_password" type="password">
    <input name="signup_password" type="password">
    <input name="signup_password_confirm" type="password">
</form>
var timeout;
$(document).on("keyup", "input[type=email]" function(){
    clearTimeout(timeout);
    var input = $(this);
    timeout = setTimeout(function(){
        if (input.is(":valid")){
            var email = input.val();
            $.getJSON("/check_for_user", {email : email}, function(data){
                if (data.user_exists){
                    input.parents("form").attr("action","/login")
                    input.addClass("user_exists");
                    $("input[type=password]").filter("[name*='login']").each(function(i,el){
                        el.required = true;
                    })
                } else {
                    input.parents("form").attr("action","/sign_up")
                    input.addClass("user_not_exists");
                    $("input[type=password]").filter("[name*='signup']").each(function(i,el){
                        el.required = true;
                    })
                }
            });
        }
    })
});

In the above JavaScript, we send the email address to the server at the "/check_for_user" url. The server would return a simple JSON response, similar to the following.

callback({
    user_exists : true
});

Based on this value, we add a class to the input, and change the endpoint for the form. We also set the relevant passwords to be required. We could then use CSS in a variety of ways to define what happens next. For instance, we could use a similar approach from earlier to show or hide inputs.

If you are signing up for a new account, we might need to show the signup password field (rather than the login password field). To do so, we would use the ~ selector, which is the general selector for siblings, and allows us to "jump" over the irrelevant sibling elements.

input[name="signup_password"] {
    /* visually hidden styles here */
}
input[type=email].user_not_exists:valid ~ input[name="signup_password"] {
    /* "visible" styles go here */
}
input[type=email].user_exists:valid ~ input[name="login_password"] {
    /* "visible" styles go here */
}

Fallbacks

When we're pushing new techniques in front-end development, they aren't going to work all the time, in all browsers. Happily, it's very easy to deal with a good fallback in this situation. We're covered in most major browsers, but this selection unfortunately doesn't include iOS 7 or Android. For non-supporting browsers, we'll simply fall back to default form behavior.

To do this, we could use Modernizr to detect whether or not the browser supports validation of form elements. It's best practice to use Modernizr for this type of feature detection, but we can also write a quick function for now. Modernizr's "Non-Core Detects" include an option for "forms-validation", so you will need to build a custom download. Once you've set this up, supporting browsers will be given the class of form validation on the html element. You can target this formvalidation class to enhance the form for supporting browsers.

.formvalidation input[type=password], .formvalidation input[type=submit] {
  /* Hide or obscure your inputs and disable pointer-events here */
}

Since the :valid and :invalid pseudo elements will only work in supporting browsers, the CSS that hides the inputs is the only CSS we need to use progressive enhancement with. The inputs will act with their default function in non-supporting browsers.


That's It!

You now have some cool ways to take your forms to a new level of interactive conversation modelling with your users. What other powerful things have you imagined using the :valid and :invalid pseudo classes? Talk about it in the comments!

Advertisement