Designing Modern Web Forms with HTML 5 and CSS3

Recently I noticed that many web developers are still using HTML tables to layout their forms. Mainly it is because people stick with what they know, and have never taken the time to learn a better way. Once you learn to layout forms with standards compliant CSS it is actually quite easy!

View Demo Download Code

We will be using HTML5 and CSS3 to achieve great style and functionality without causing problems in less capable browsers. To be clear, this code will not look exactly the same in every browser. We are designing for the most advanced browsers, then making sure it still degrades gracefully.

Starting with the right markup.

Here we are using HTML 5 to code the form. Take a look:

[html]
<div id="registration">
<h2>Create an Account</h2>

<form id="RegisterUserForm" action="" method="post">
<fieldset>
<p>
<label for="name">Name</label>
<input id="name" name="name" type="text" class="text" value="" />
</p>

<p>
<label for="tel">Phone Number</label>
<input id="tel" name="tel" type="tel" class="text" value="" />
</p>

<p>
<label for="email">Email</label>
<input id="email" name="email" type="email" class="text" value="" />
</p>

<p>
<label for="password">Password</label>
<input id="password" name="password" class="text" type="password" />
</p>

<p><input id="acceptTerms" name="acceptTerms" type="checkbox" />
<label for="acceptTerms">
I agree to the <a href="">Terms and Conditions</a> and <a href="">Privacy Policy</a>
</label>
</p>

<p>
<button id="registerNew" type="submit">Register</button>
</p>
</fieldset>

</form>
</div>

[/html]

Note the input types. Instead of only using the usual “name” and “password” I also added “tel” and “email”. Most browsers don’t render anything different here, but it makes a big difference for the user experience on Safari mobile (iPhone and iPad). The key difference is a rearranged keyboard to focus on the type of input. Email adds the @ sign as well as a . button. Also the auto-correct changes so it doesn’t try to split domain names into multiple words.

It looks like a minor difference but it is very frustrating to enter an email address into a text input on the iPhone. Your users will thank you for paying attention to the details.

Older browsers that don’t understand HTML 5 forms will just fall back to input type=”text”. Which is just fine for our purposes. Also each field has class of “text” so that I can style them with the CSS input.text selector. Even though they aren’t all text fields, I do want them to look the same. You could use CSS3 selectors instead, but getting that to work in IE is beyond the scope of this article.

Adding basic styling.

Now to style our form. We are going to start with a very simple reset. Please use something more appropriate to your project.

[css]

/* Add whatever you need to your CSS reset */
html, body, h1, form, fieldset, input {
margin: 0;
padding: 0;
border: none;
}

body { font-family: Helvetica, Arial, sans-serif; font-size: 12px; }

[/css]

Now to style the form container.

[css]

#registration {
color: #fff;
background: #2d2d2d;
background: -webkit-gradient(
linear,
left bottom,
left top,
color-stop(0, rgb(60,60,60)),
color-stop(0.74, rgb(43,43,43)),
color-stop(1, rgb(60,60,60))
);
background: -moz-linear-gradient(
center bottom,
rgb(60,60,60) 0%,
rgb(43,43,43) 74%,
rgb(60,60,60) 100%
);
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
border-radius: 10px;
margin: 10px;
width: 430px;
}

#registration a {
color: #8c910b;
text-shadow: 0px -1px 0px #000;
}

#registration fieldset {
padding: 20px;
}

[/css]

Here we are using CSS3 gradients to set the background of the container. We are using three different background declarations to accommodate different browsers. First we set a background color of #2d2d2d that all browsers will understand, then we overwrite it for -webkit and -moz to use background gradients instead. Since Internet Explorer doesn’t understand gradients it will ignore them and use the solid color specified first.

You can use this website to generate your own CSS gradients.

Then for the rounded corners we are adding -webkit-border-radius, then -moz-border-radius for the browsers that support it, then adding standard border-radius for when the official spec is adopted in the future (hopefully by IE9).

For the links we are adding a default color, then more importantly adding text-shadow (not supported in IE). The syntax for the text-shadow is this:

[css]

text-shadow: x  y  blur  color;

[/css]

[css]

text-shadow: 0px -1px 0px #000;

[/css]

So we are using that to add a black vertical shadow that gives the text an indented look.

Styling the Input Fields

[css]

input.text {
-webkit-border-radius: 15px;
-moz-border-radius: 15px;
border-radius: 15px;
border:solid 1px #444;
font-size: 14px;
width: 90%;
padding: 7px 8px 7px 8px;
background: #ddd;
background: -moz-linear-gradient(
center bottom,
rgb(225,225,225) 0%,
rgb(215,215,215) 54%,
rgb(173,173,173) 100%
);
background: -webkit-gradient(
linear,
left bottom,
left top,
color-stop(0, rgb(225,225,225)),
color-stop(0.54, rgb(215,215,215)),
color-stop(1, rgb(173,173,173))
);
color:#333;
text-shadow:0px 1px 0px #FFF;
-moz-box-shadow: 0px 1px 0px #777;
-webkit-box-shadow: 0px 1px 0px #777;
box-shadow: 0px 1px 0px #777;
}

[/css]

Here we are adding background gradients again, remember to specify the default for older browsers. The rounded corners also give the fields a nice pill shape.

The new part is that we used a box-shadow to give it a recessed look. The syntax is the same for box-shadow as it is for text-shadow. So box-shadow: 0px 1px 0px #777; is a light colored shadow, with no blur, that is down 1px. This combined with a dark stroke gives it the look below.

Adding Icons.

Next we will add icons to each field so that it is more easily identified at a glance. With CSS3 we are able to use multiple backgrounds to have both a gradient and an image. First create a sprite image of all your icons combined into one file. This will decrease HTTP requests, simplify your markup, and improve page load. An example of the icons I used are on the right.

You will want to replace the earlier code for the input field backgrounds with this new code.

[css]

background: #ddd url(‘img/inputSprite.png’) no-repeat 4px 6px;
background: url(‘img/inputSprite.png’) no-repeat 4px 6px, -moz-linear-gradient(
center bottom,
rgb(225,225,225) 0%,
rgb(215,215,215) 54%,
rgb(173,173,173) 100%
);
background:  url(‘img/inputSprite.png’) no-repeat 4px 6px, -webkit-gradient(
linear,
left bottom,
left top,
color-stop(0, rgb(225,225,225)),
color-stop(0.54, rgb(215,215,215)),
color-stop(1, rgb(173,173,173))
);

[/css]

We are able to add multiple backgrounds by separating each one with a comma. To accommodate the new icons we will also need to change the right padding to 30px.

[css]
padding: 7px 8px 7px 30px;
[/css]

Then specify the background position for each field individually so that it will display the correct icon. The exact values will depend on the icons that you use. Note that the order of the icons in the sprite does not have to match the order of the input fields.

[css]

input#email {
background-position: 4px 5px;
background-position: 4px 5px, 0px 0px;
}
input#password {
background-position: 4px -20px;
background-position: 4px -20px, 0px 0px;
}
input#name {
background-position: 4px -46px;
background-position: 4px -46px, 0px 0px;
}
input#tel {
background-position: 4px -76px;
background-position: 4px -76px, 0px 0px;
}

[/css]

Here the first background-position is for the browsers that don’t support multiple background images, the second is for the gradient position on the browsers that do support it.

Styling the Header & Submit Button

[css]

#registration h2 {
color: #fff;
text-shadow: 0px -1px 0px #000;
text-align: center;
padding: 18px;
margin: 0px;
font-weight: normal;
font-size: 24px;
font-family: Lucida Grande, Helvetica, Arial, sans-serif;
border-bottom: solid #181818 1px;
-moz-box-shadow: 0px 1px 0px #3a3a3a;
-webkit-box-shadow: 0px 1px 0px #3a3a3a;
box-shadow: 0px 1px 0px #3a3a3a;
}

[/css]

Using a bottom border and a box shadow we are able to create an indented separating line without any additional markup.

For the submit button we will use an sprite that has 3 states for :link, :hover, and :active.

You can then use different background positions to shift the image up for each state. This keeps your HTTP requests to a minimum and also prevents a flicker while the browser loads an image for the :hover state.

[css]

#registerNew {
width: 203px;
height: 40px;
border: none;
text-indent: -9999px;
background: url(‘img/createAccountButton.png’) no-repeat;
cursor: pointer;
float: right;
}
#registerNew:hover { background-position: 0px -41px; }
#registerNew:active { background-position: 0px -82px; }

[/css]

Moving Labels Inline with jQuery.

To further style the forms I want to move the label inside the field itself. This technique is largely based on the work from Trevor Davis at Viget Labs.

[html]

<script type="text/javascript">

$(document).ready(function() {
/*
* In-Field Label jQuery Plugin
* http://fuelyourcoding.com/scripts/infield.html
*
* Copyright (c) 2009 Doug Neiner
* Dual licensed under the MIT and GPL licenses.
* Uses the same license as jQuery, see:
* http://docs.jquery.com/License
*
* @version 0.1
*/
(function($) { $.InFieldLabels = function(label, field, options) { var base = this; base.$label = $(label); base.$field = $(field); base.$label.data("InFieldLabels", base); base.showing = true; base.init = function() { base.options = $.extend({}, $.InFieldLabels.defaultOptions, options); base.$label.css(‘position’, ‘absolute’); var fieldPosition = base.$field.position(); base.$label.css({ ‘left': fieldPosition.left, ‘top': fieldPosition.top }).addClass(base.options.labelClass); if (base.$field.val() != "") { base.$label.hide(); base.showing = false; }; base.$field.focus(function() { base.fadeOnFocus(); }).blur(function() { base.checkForEmpty(true); }).bind(‘keydown.infieldlabel’, function(e) { base.hideOnChange(e); }).change(function(e) { base.checkForEmpty(); }).bind(‘onPropertyChange’, function() { base.checkForEmpty(); }); }; base.fadeOnFocus = function() { if (base.showing) { base.setOpacity(base.options.fadeOpacity); }; }; base.setOpacity = function(opacity) { base.$label.stop().animate({ opacity: opacity }, base.options.fadeDuration); base.showing = (opacity > 0.0); }; base.checkForEmpty = function(blur) { if (base.$field.val() == "") { base.prepForShow(); base.setOpacity(blur ? 1.0 : base.options.fadeOpacity); } else { base.setOpacity(0.0); }; }; base.prepForShow = function(e) { if (!base.showing) { base.$label.css({ opacity: 0.0 }).show(); base.$field.bind(‘keydown.infieldlabel’, function(e) { base.hideOnChange(e); }); }; }; base.hideOnChange = function(e) { if ((e.keyCode == 16) || (e.keyCode == 9)) return; if (base.showing) { base.$label.hide(); base.showing = false; }; base.$field.unbind(‘keydown.infieldlabel’); }; base.init(); }; $.InFieldLabels.defaultOptions = { fadeOpacity: 0.5, fadeDuration: 300, labelClass: ‘infield’ }; $.fn.inFieldLabels = function(options) { return this.each(function() { var for_attr = $(this).attr(‘for’); if (!for_attr) return; var $field = $("input#" + for_attr + "[type=’text’]," + "input#" + for_attr + "[type=’password’]," + "input#" + for_attr + "[type=’tel’]," + "input#" + for_attr + "[type=’email’]," + "textarea#" + for_attr); if ($field.length == 0) return; (new $.InFieldLabels(this, $field[0], options)); }); }; })(jQuery);

$("#RegisterUserForm label").inFieldLabels();
});

</script>

[/html]

The line “$(“#RegisterUserForm label”).inFieldLabels();” is what activates the script for those particular labels. Make sure to change the ID if yours is different. I modified the script to add support for the input types “tel” and “email”. If you choose to use others you will need to write those in (near the end of the script).

We are also adding a class of .infield to the labels that need to be restyled. This way if JavaScript is disabled the form will degrade gracefully. Here is the necessary CSS:

[css]

fieldset label.infield /* .infield label added by JS */ {
color: #333;
text-shadow: 0px 1px 0px #fff;
position: absolute;
text-align: left;
top: 3px !important;
left: 35px !important;
line-height: 29px;
}
[/css]

Sometimes the browser auto-complete can interfere with the inline form fields (especially on login boxes). If this is an issue for your site then you can add autocomplete=”off” to the input fields.

Note: We could have used the HTML5 Placeholder attribute instead, but this method works better in older browsers and also I like how it looks better.

How it looks in Internet Explorer

Because we designed first for modern browsers, certain less capable browsers will not be able to display the form in its best possible look. Here is what IE users will see:

Not as pretty, but everything still functions perfectly.

That’s all!

Please ask any questions and give feedback in the comments.

View Demo Download Code

7 thoughts on “Designing Modern Web Forms with HTML 5 and CSS3”

  1. Hi, I have used the script and found that it does not work. In the ‘action’ field of the form I put in “mailto:myemail@address.com” but nothing has happened.

    Any ideas?

    Christopher

    1. This tutorial only covers the markup, design and infield labels. If you want to actually post the form then you need server-side code, which is beyond the scope of this tutorial. You will want to look into doing that with PHP or whatever other language you are working in.

  2. Pretty form, but serves no purpose without functionality. Whilst you’ve said it’s outside the scope of this tutorial, you could have easily included a functional snippet that users could copy paste into the form to make it actually do something. Otherwise we’re left with a pretty CSS/HTML5 gimmick with no purpose.

Leave a Reply to Michael Story Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>