Form Widgets with jQuery UI

Hello!  In this tutorial we’re going to (finally!) finish the HTML5 contact form we’ve been developing.  Just a quick recap – in part one we went over the HTML markup and discussed the new types of input element;  in part two we prettied the form up a little using CSS3; in part three we wrote a JavaScript function to detect whether the user’s browser supports any of the new UI widgets; and in part four we used the jQuery Validate plugin to enable client side form validation.  In this tutorial, we’re going to use jQuery UI to enable form widgets if they aren’t supported natively.

Check out where we’re at thus far: Demo
You can view the finished product here

Before we start, I’m going to make a quick change to the form – I’m going to change the input type of the ‘project-budget’ field from ‘number’ to ‘range’, and remove validation for this input.  Now we have an extra UI widget to work with.

For this tutorial, we don’t need all of jQuery UI – we only need the core and a couple of the widgets.  So do we serve a custom jQuery UI file, which will be considerably smaller in size, or load the whole of jQuery UI from the Google CDN?  From a performance perspective, both options have pros and cons.  On the one hand, you have the smaller file size of a custom build, but on the other you have content being downloaded concurrently from a CDN, with the possibility that it’s already cached in the user’s browser. If the custom build was very small I would go for that option, but for the purposes of this tutorial, the simplicity of loading jQuery UI from Google’s CDN is just easier for me.  You should always consider each case on it’s own merit to get the best performance possible; but if you decide to use files on an external CDN, make sure that you always put a fallback in place in case the CDN is unavailable.

We need to include a reference to the jQuery UI base theme in the head of our page.

<link rel="stylesheet" href="screen.css">
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.9/themes/base/jquery-ui.css">

We already have some script files referenced in our html –

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.0/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.9/jquery-ui.min.js"></script>
<script src="http://ajax.microsoft.com/ajax/jquery.validate/1.7/jquery.validate.min.js"></script>
<script src="global.js"></script>

Lets change the version number on the jQuery core to 1.5, and import jQuery UI. Paste the following lines of JavaScript code into the bottom of the form HTML, just before the last body tag.

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.0/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.9/jquery-ui.min.js"></script>
<script src="http://ajax.microsoft.com/ajax/jquery.validate/1.7/jquery.validate.min.js"></script>
<script src="global.js"></script>

Why do we put them at the bottom of the page?  We put them here so that they don’t block the loading and rendering of page content.  If you place a script in the head of a page, especially if it’s a large one, the browser retrieves and parses this script before flowing the content into the page.  So your user will be faced with a big fat blank page until the script has been fetched and parsed, which could be a considerable amount of time depending on how large your script is.  Not very user friendly!  Notice also that we’re linking to a specific version number, not the shorthand ‘latest’.  If you link to the ‘latest’ version, you don’t get the benefits of caching mentioned above, and run the (small) risk of backwards compatibility issues with your code when the next version of jQuery is released.

Below is the (refactored) support detection script that we wrote in part three.

(function() {
    var inputs = ['url', 'email', 'datetime', 'date', 'month', 'week', 'time', 'datetime-local', 'number', 'color', 'range'],
        input = document.createElement('input')
        len = inputs.length,
        uiSupport = {},
        i = 0;

    for (; i < len; i++) {
        input.setAttribute('type', inputs[i]);

        if (input.type === 'text') {
            uiSupport[inputs[i]] = false;
        } else {
            input.value = 'testing';
            (input.value === 'testing') ? uiSupport[inputs[i]] = false : uiSupport[inputs[i]] = true;
        }
    }
})();

After this function has executed (i.e. immediately), the uiSupport object will contain the name of each input type, and a boolean value (true or false) describing whether it is supported in the browser.  Next we’re going to create an object to contain all of our widget methods.  We’re going to use the slider and the datepicker from jQuery UI.

...
        i = 0,
        widgets = {
            date : function(elem) {
                elem.datepicker({
                    beforeShow : function(input, inst) {
                        inst.dpDiv.css({
                            fontSize : '14px',
                            marginLeft : 215,
                            marginTop : -22
                        });
                    }
                });
            },
            range : function(elem) {
                elem
                    .after('<div></div><span class="slider-val">1500</span>')
                    .next()
                    .slider({
                        value : 1500,
                        min : 500,
                        max : 4000,
                        step : 500,
                        slide: function(event, ui) {
                            $(this).next().text(ui.value);
                        }
                    })
                    .end()
                    .remove();
            },
            altrange : function(elem) {
                elem
                    .addClass('ui-slider')
                    .after('<span class="slider-val">1500</span>')
                    .change(function() {
                        $(this).next().text($(this).val());
                    });
            }
        };

Then after the for loop, we’re going to use for…in to iterate over the uiSupport object to check support and call the relevant method:

    for (var prop in uiSupport) {
        if (prop === 'range') {
            widgets[(uiSupport[prop] ? 'alt' : '') + prop]($('input[type=' + prop + ']', 'form'));
        }
        if (prop === 'date' && !uiSupport[prop]) {
            widgets[prop]($('input[type=' + prop + ']', 'form'));
        }
    }

The last thing we need to do is to disable the ‘contact time’ field, unless the ‘phone’ radio button has been clicked.  Our complete function looks like this:

$.contactForm = (function() {

    var inputs = ['url', 'email', 'datetime', 'date', 'month', 'week', 'time', 'datetime-local', 'number', 'color', 'range'],
        input = document.createElement('input')
        len = inputs.length,
        uiSupport = {},
        i = 0,
        widgets = {
            date : function(elem) {
                elem.datepicker({
                    beforeShow : function(input, inst) {
                        inst.dpDiv.css({
                            fontSize : '14px',
                            marginLeft : 215,
                            marginTop : -22
                        });
                    }
                });
            },
            range : function(elem) {
                elem
                    .after('<div></div><span class="slider-val">1500</span>')
                    .next()
                    .slider({
                        value : 1500,
                        min : 500,
                        max : 4000,
                        step : 500,
                        slide: function(event, ui) {
                            $(this).next().text(ui.value);
                        }
                    })
                    .end()
                    .remove();
            },
            altrange : function(elem) {
                elem
                    .addClass('ui-slider')
                    .after('<span class="slider-val">1500</span>')
                    .change(function() {
                        $(this).next().text($(this).val());
                    });
            }
        };

    for (; i < len; i++) {
        input.setAttribute('type', inputs[i]);

        if (input.type === 'text') {
            uiSupport[inputs[i]] = false;
        } else {
            input.value = 'testing';
            (input.value === 'testing') ? uiSupport[inputs[i]] = false : uiSupport[inputs[i]] = true;
        }
    }

    for (var prop in uiSupport) {
        if (prop === 'range') {
            widgets[(uiSupport[prop] ? 'alt' : '') + prop]($('input[type=' + prop + ']', 'form'));
        }
        if (prop === 'date' && !uiSupport[prop]) {
            widgets[prop]($('input[type=' + prop + ']', 'form'));
        }
    }

    $('input[type=time]').attr('disabled','disabled');
    $('input[type=radio]').click(function() {
        if ($(this).val() === 'Phone') {
            $('input[type=time]').removeAttr('disabled');
        } else {
            $('input[type=time]').attr('disabled','disabled');
        }
    });

})();

If we have a look at our form now, we can see that in Firefox, where none of the UI widgets are currently supported, we have the jQuery UI slider and datepicker:

In Chrome, where the range slider is supported but the datepicker isn’t, we see this:

And in Opera, where both the range slider and datepicker are supported natively, we see this:
You can view the final product at the link below.
Demo

I hope you’ve enjoyed this series of tutorials.  Please leave a comment below if you have any questions or suggestions.

jQuery Form Validation

In this tutorial, we’ll continue developing the HTML5 form that we’ve been working on.  We’re going to import jQuery and the jQuery Validate plugin into our page, so that we can validate any data entered into the form.  In the next part, we’ll work on propping up any HTML5 UI improvements that aren’t supported by the user’s browser using jQuery plugins.

At the bottom of our html contact form, just before the closing body tag, import jQuery from the Google CDN into the page.  Unfortunately, the Google CDN doesn’t currently host the jQuery validate plugin, but Microsoft do, so we’ll import it from their CDN.  After this, import a new script – I’ve called mine ‘global.js’ – into the page.  Don’t forget to create the new empty script file.

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="http://ajax.microsoft.com/ajax/jquery.validate/1.7/jquery.validate.min.js"></script>
<script src="global.js"></script>

A lot of jQuery code samples you see start a script with the document.ready function.  There isn’t any need for us to do that here, as we’ve put our script tags at the bottom of the page – the document has already loaded before we’ve told the browser to fetch any scripts.

We’re going to check the data entered against the following patterns:

Fieldset 1

  • Name: letters, hyphens and spaces only, required field
  • Company: no validation
  • Email: email format (a@b.c), required field
  • Telephone: numbers only, min 11 chars, max 11 chars, required field
  • Website: url format

Fieldset 2

  • Type of Project: no validation (one item is selected by default), dynamically generate a textarea to collect details
  • Estimated Completion Date: no validation (we’ll use a jQuery datepicker plugin here later)
  • Estimated Budget: required field, numbers only
  • Additional Details: no validation

Fieldset 3

  • Please contact me by… : no validation required, as an option is selected by default
  • Best time to contact: disabled unless ‘contact by phone’ is selected

If all the tests pass, the form will be submitted; otherwise, the user will get instant feedback regarding any errors.  In global.js, write the following code:

$('form').validate();

If you specify which fields to validate in the html itself (usually by adding class=”required” to the input field you want to validate), this would initialise the plugin with the default options.  However, we want some custom validation and custom error messages, so we’re going to have to delve a little more deeply into the plugin’s interface.  For reference, all the methods associated with the plugin can be found here.

Options are specified by passing them into the validate() function with an object literal, as follows:

$('form').validate({
    rules : {}
});

This is where we will define the validation rules for each of our form inputs.  Each form input is referred to by it’s ‘name’ attribute.

$('form').validate({
    rules : {
        name : {},
        email : {},
        telephone : {},
        website : {},
        'project-budget' : {},
        'contact-time' : {}
    }
});

Notice that we’ve had to wrap any keys containing a hyphen in quotes, as hyphens are not permitted in property names.  Also, be sure to omit the comma for the last item of an object literal, as this will throw an error in IE.  Lets go through the form and add a ‘required’ rule for all the appropriate fields.

$('form').validate({
    rules : {
        name : {
            required : true
        },
        company : {},
        email :  {
            required : true
        },
        telephone :  {
            required : true
        },
        website : {},
        'project-budget' :  {
            required : true
        },
        'contact-time' : {}
    }
});

If we were to leave a field empty now, we’d see the default error message, telling us that the field is required.

Lets fill out the rest of the options.

$('form').validate({
    rules : {
        name : {
            required : true,
            letters : true
        },
        email :  {
            required : true,
            email : true
        },
        telephone :  {
            required : true,
            digits : true,
            minlength : 11,
            maxlength : 11
        },
        website : {
            url : true
        },
        'project-budget' :  {
            required : true,
            digits : true
        },
        'contact-time' : {
            required : function(element) {
                return $('.contact-method input:last').is(':checked');
            }
        }
    }
});

The above code is pretty self-explanatory, but for a couple of points.  Firstly, ‘required’ is not set to true or false on the ‘contact-time’ field, as we want the field to be a required field only if the ‘phone’ option is selected.  We need to pass the ‘required’ method a function containing an expression to be evaluated; this value is then set to be the return value of the function.  If the ‘phone’ option is selected, the expression will evaluate to true, so the field will be required, otherwise, the expression will be false, and the field will not be required.

Secondly, the ‘letters’ method is not a method native to the plugin, so we have to create a custom method using $.validator.addMethod.  We want to create a method that will check the data to match a pattern of letters, spaces and hyphens only.  We do this by matching the data against a regular expression.  Follow the code below as we build up a regular expression for matching lettters:

  1. [a-z] – lowercase letters only
  2. [a-zA-Z] – lowercase and capitals
  3. [- a-zA-Z] – lowercase, capitals, hyphen, space
  4. ^[- a-zA-Z]$ – ^marks the beginning of a pattern, $ marks the end
  5. ^[- a-zA-Z]+$ – the plus(+) sign after the brackets but before the end of the pattern denotes that we want to find more than 1 character
  6. /^[- a-zA-Z]+$/ – we wrap the enter regular expression in the JavaScript regex literal (//)

Lastly, we use the match method to check if the field data is a match:

6.   value.match(/^[- a-zA-Z]+$/);

The final code for the custom letters method is as follows:

$.validator.addMethod('letters', function(value) {
    return value.match(/^[- a-zA-Z]+$/);
});

Next we need to specify the error messages that we want to be displayed for each test, as below.

$('form').validate({
    rules : {
        name : {
            required : true,
            letters : true
        },
        email :  {
            required : true,
            email : true
        },
        telephone :  {
            required : true,
            digits : true,
            minlength : 11,
            maxlength : 11
        },
        website : {
            url : true
        },
        'project-budget' :  {
            required : true,
            digits : true
        },
        'contact-time' : {
            required : function(element) {
                return $('.contact-method input:last').is(':checked');
            }
        }
    },
    messages : {
        name : {
            required : 'The name field cannot be blank',
            letters : 'Please enter letters only'
        },
        email :  {
            required : 'The email field cannot be blank',
            email : 'Please enter a valid email address'
        },
        telephone :  {
            required : 'The telephone field cannot be blank',
            digits : 'Please enter numbers only',
            minlength : 'Please enter a UK phone number (11 digits)',
            maxlength : 'Please enter a UK phone number (11 digits)'
        },
        website : {
            url : 'Please enter a valid url'
        },
        'project-budget' :  {
            required : 'The project budget field cannot be blank',
            digits : 'Please enter numbers only'
        },
        'contact-time' : {
            required : 'The contact time field cannot be blank'
        }
    }
});

That’s all the validation we’re going to do in this part of the tutorial, in the next section we’ll improve the UI with widgets in browsers that don’t support the native HTML5 form improvements yet. Before we finish, we’ll just tidy up those error messages.

From the screenshot above, we can see that the errors are displaying in the wrong place – they should be underneath the error field, not to the bottom left where the field labels are.  To remedy this, we simply need to override the default css for a label element.  Fortunately, errors generated by the plugin have the class “error”, so we can simply target this class:

label.error { text-align: left; margin-left: 200px; color: #CC0000; padding: 0; height: auto; width: 208px; font-size: 11px }
input.error { border: 2px solid #CC0000 }

View the Demo

HTML5 Forms Support Detection with JavaScript

In this tutorial, we’ll continue developing our HTML5 form.  In the last tutorial, we styled the form with some of the new CSS3 properties; this time we’re going to detect support for the new HTML5 input types.

We need to detect whether the browser supports the new HTML5 input types natively – if not, we can use this knowledge to fall back to JavaScript where appropriate.  It is important to note that there currently isn’t a lot of browser support for these input types: Opera 10.6 has the most complete support, Safari 5 and Chrome 5 have partial support, Firefox 3.6.8 and IE8 (as usual) are dead in the water.  If you’re dabbling with HTML5 at all, you’ll know that support is patchy at best, and you should treat tutorials like this one as instructional rather than definitive.  So whilst this information is current now, the specification is still undergoing revision (and will be for a while yet!).

Support Detection

If a browser doesn’t support a particular attribute value, it will degrade to the default value text.  In theory, this is how we test for browser support – we create a new input element in JavaScript (in memory only, don’t append it to the DOM), assign to it the attribute value we want to detect support for, and then query the element’s type attribute.  If the attribute retains the value that you assigned to it, the browser supports it.  Otherwise, if it returns the default value, the browser doesn’t support it. Using this method, we can loop through all the new input types and log the results to console to see what we get back:

var inputs = ['search', 'tel', 'url', 'email', 'datetime', 'date', 'month', 'week', 'time', 'datetime-local', 'number', 'color', 'range'],
    len = inputs.length;

for (var i = 0; i < len; i++) {
    var input = document.createElement('input');
    input.setAttribute('type', inputs[i]);
    console.log(inputs[i] + ' -> ' + input.type);
}

Notice that I began the above statement with the qualifier “in theory.”  Whilst this method should technically work, some browsers are prone to giving false positives, i.e. asserting that they support a particular input type, when in reality, they don’t.  Take a look at the output produced for the basic test above:

Only Firefox, IE8 and Opera are telling the truth! If you visually check in Safari and Chrome whether the UI improvements or validation functionality is present (using Mike Taylor’s HTML5 Input form, for example), it’s pretty evident that Safari and Chrome are not being entirely honest with us.  So how do we test for support if two of the major browsers are telling porkies?  Well, you have to go a step further and test for both UI improvements and validation functionality manually.

However, support for data validation is currently pretty flakey – that’s interpreting it generously.  It appears that some browsers validate the data properly, whilst others don’t check the data but pass it as valid anyway (see this discussion for details).  This probably won’t be resolved for a long time, so we’re going to validate data with JavaScript – experiments are one thing, but the state of implementation is so fragmented at this point that it’s not actually of any use to us.  So in the next part of this tutorial, validation will be handled by JavaScript exclusively.  What about the UI improvements?  If the UI improvements are supported, we’ll use those, otherwise we’ll provide the functionality ourselves.  Sounds like a plan!

We need to test for these UI improvements to see if they’re present in the browser.  UI improvements can be best described as things that we’d usually have to implement a widget for in Calendar in OperaJavaScript –  like a date picker or calendar – except now they’re implemented by the browser. We test for them by passing a text value to our mock-input, and then checking what gets returned – any new UI functionality shouldn’t be able to hold a text value.  If a string is returned, there’s no UI improvement; if anything else is returned (e.g. a numerical value in the case of range, or an empty value in most other cases) then a UI improvement is present.  One caveat: there are no detectable improvements or validation where the telephone and search inputs are concerned as yet, so we have to eliminate these from the first part of the test.

var inputs = ['search', 'tel', 'url', 'email', 'datetime', 'date', 'month', 'week', 'time', 'datetime-local', 'number', 'color', 'range'],
len = inputs.length,
uiSupport = [];

for (var i = 0; i < len; i++) {
    var input = document.createElement('input');
    input.setAttribute('type', inputs[i]);
    var notText = input.type !== 'text';

    if (notText && input.type !== 'search' && input.type !== 'tel') {
        input.value = 'testing';
        if (input.value !== 'testing') {
            console.log(input.type);
        }
    }
}

In the FF and IE consoles, we don’t get any data returned, as expected.  Safari and Chrome return a non-string value for range only.  Opera returns a non-string value for datetime, date, month, week, time, datetime-local, number and range.  The last thing we have to do is store this information in an array so that we can refer to it later, then wrap the whole code block in a function so that the variables have some semblance of a namespace.

(function() {
    var inputs = ['search', 'tel', 'url', 'email', 'datetime', 'date', 'month', 'week', 'time', 'datetime-local', 'number', 'color', 'range'],
    len = inputs.length,
    uiSupport = [];

    for (var i = 0; i < len; i++) {
        var input = document.createElement('input');
        input.setAttribute('type', inputs[i]);
        var notText = input.type !== 'text';

        if (notText && input.type !== 'search' && input.type !== 'tel') {
            input.value = 'testing';
            if (input.value !== 'testing') {
                uiSupport.push(input.type);
                // console.log(uiSupport);
            }
        }
    }
})();

uiSupport will store the names of the input type attributes that are supported, so we can use them when we come to create our fallbacks in the next tutorial.

Quick Tip: If you need to detect support for some of the other input attributes – autofocus, for example, or any of the other new HTML5 elements, it’s definately worth checking out Modernizr.  Modernizr has the added benefit of having IE support baked in, so there’s no need to include an additional HTML5 shim to get the new (header, section, etc.) tags working.

Form Styling with CSS3

Today we’re going to continue developing our HTML5 contact form, using CSS.

Extract the inline CSS from the head of the form and paste it into a new document.  Name this new stylesheet style.css and make a reference to it in the head of the form HTML.  At the top of style.css, I’ve imported Eric Meyer’s stylesheet reset (named “reset.css”) and removed the star (*) selector that temporarily reset the margin and padding.  (NB: never use the start selector in production!)  Your stylesheet should now look like this:

@charset "utf-8";
@import "reset.css";

form { width: 460px; padding: 20px; color: #333333; font-family: Verdana, Geneva, sans-serif; font-size: 12px; margin: 0 auto }
fieldset { border: 0; padding: 10px; margin-bottom: 10px; background: #F6F6F6 }
legend { padding: 5px 10px }
label, span { float: left; clear: left; display: block; width: 180px; padding-right: 20px; text-align: right }
input, textarea, select { float: left; width: 200px }
.submit { width: 100px; float: right; margin-right: 33px }
select { width: 206px }
p { overflow: hidden; margin-bottom: 10px }
.contact-method label { clear: none; width: auto }
.contact-method input { float: none; width: 20px }

We’re just going to make a quick couple of changes before we start.

Firstly, add ‘height: 26px; line-height: 26px’ to the ‘label, span’ selector to vertically centre the text within the input labels.

Secondly, there’s a bit of a bug in IE concerning legends and fieldsets – IE bleeds the background colour of the fieldset behind the legend.  To recify this, we’re going to add a little bit more margin to the bottom of the fieldset, and position the legend absolutely within it.

Unfortunately, positioning the legends absolutely within a fieldset that has padding triggers a bug in Firefox.  This bug causes Firefox to calculate the legend’s position incorrectly, so to fix this, we’re going to remove the padding from the top of the fieldset, and add that padding to the first paragraph in the fieldset instead.  Add “padding-top: 0” to the fieldset attribute, and “p:first-of-type { padding-top: 25px }” underneath the fieldset style.  I rarely see :first-of-type used out in the wild, as :first-child can often be used in a similar context.  However, :first-of-type selects the element that is the first sibling of that type within it’s parent, rather than just selecting the first child.  Very useful!

Now that we’ve fixed that, we’ve broken IE again by removing the padding. Add the hack “padding-top: 25px9” to the fieldset selector and we should be good to go.

With these changes, your css should look like this:

form { width: 460px; padding: 20px; color: #333333; font-family: Verdana, Geneva, sans-serif; font-size: 12px; margin: 0 auto }
fieldset { position: relative; padding: 10px; padding-top: 0; padding-top: 25px9; margin-bottom: 30px; background: #F6F6F6 }
legend { padding: 6px 12px; position: absolute; left: 10px; top: -11px }
label, span { float: left; clear: left; display: block; width: 180px; padding-right: 20px; text-align: right; height: 26px; line-height: 26px }
input, textarea, select { float: left; width: 200px }
.submit { width: 100px; float: right; margin-right: 33px }
select { width: 206px }
p { overflow: hidden; margin-bottom: 10px }
p:first-of-type { padding-top: 25px }
.contact-method label { clear: none; width: auto }
.contact-method input { float: none; width: 20px }

Now that we have a base template for our form, we can start adding styles.

Border Radius

We’re going to add a rounded corner to the fieldsets, legends and submit button, add background and text colours to the legends, and change the style of the submit button. At the moment, we still have to use vendor prefixes for border-radius, so this style declaration usually consists of three lines:

#style {
    -webkit-border-radius: <value>;
    -moz-border-radius: <value>;
    border-radius: <value>;
}

Edit your css using the styles highlighted below.

form { width: 460px; padding: 20px; color: #333333; font-family: Verdana, Geneva, sans-serif; font-size: 12px; margin: 0 auto }
fieldset { position: relative; padding: 10px; padding-top: 0; padding-top: 25px9; margin-bottom: 30px; background: #F6F6F6; -webkit-border-radius: 8px; -moz-border-radius: 8px; border-radius: 8px }
legend { padding: 5px 10px; background-color: #4F709F; color: white; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px }
label, span { float: left; clear: left; display: block; width: 180px; padding-right: 20px; text-align: right; height: 26px; line-height: 26px }
input, textarea, select { float: left; width: 200px }
.submit { width: 100px; float: right; margin-right: 38px; border: 0; padding: 5px 10px; background: #4F709F; color: white; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px }
select { width: 202px }
p { overflow: hidden; margin-bottom: 10px }
p:first-of-type { padding-top: 25px }
.contact-method label { clear: none; width: auto }
.contact-method input { float: none; width: 20px }

The last fieldset on the form should look like this:

I think it’s easy to go overboard with border-radius, so we’ll leave that bit there for now.  Next up, background gradients.

Background Gradients

We have to use vendor prefixes to enable this to work in Firefox and Webkit.  Unlike border radius, each vendor uses a different syntax for their style declaration, so it can be a little confusing.  There are more properties associated with setting a background gradient style, but I’ve listed the most common ones below.

Firefox has linear gradient and radial gradient values…

#style {
    background-image: -moz-linear-gradient( [<point> || <angle>,]? <stop>, <stop> [, <stop>]* );
    background-image: -moz-radial-gradient( [<position> || <angle>,]? [<shape> || <size>,]? <stop>, <stop>[, <stop>]* )
}

…whereas Webkit allows you to specify whether the gradient is a linear or radial one within “-webkit-gradient”:

#style {
    background-image: -webkit-gradient(<type>, <point> [, <radius>]?, <point> [, <radius>]? [, <stop>]*)
}

Note that you can also specify these gradients within the “background” shorthand property.

For our form, we’ll be using a grey border on the input boxes, and a slight gradient on the fieldsets.

form { width: 460px; padding: 20px; color: #333333; font-family: Verdana, Geneva, sans-serif; font-size: 12px; margin: 0 auto }
fieldset { position: relative; padding: 10px; padding-top: 0; padding-top: 25px9; margin-bottom: 30px; background: #F6F6F6; -webkit-border-radius: 8px; -moz-border-radius: 8px; border-radius: 8px; background: -webkit-gradient(linear, left top, left bottom, from(#EEEEEE), to(#FFFFFF)); background: -moz-linear-gradient(center top, #EFEFEF, #FFFFFF 100%) }
legend { padding: 6px 12px; position: absolute; left: 10px; top: -36px; background-color: #4F709F; color: white; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px }
label, span { float: left; clear: left; display: block; width: 180px; padding-right: 20px; text-align: right; height: 26px; line-height: 26px }
input, textarea, select { float: left; width: 200px; border: 1px solid #d9d9d9 }
.submit { width: 100px; float: right; margin-right: 37px; border: 0; padding: 5px 10px; background: #4F709F; color: white; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px }
textarea { background: -webkit-gradient(linear, left top, left bottom, from(#FFFFFF), color-stop(1%, #EEEEEE), to(#FFFFFF)) }
select { width: 202px }
p { overflow: hidden; margin-bottom: 10px }
p:first-of-type { padding-top: 25px }
.contact-method label { clear: none; width: auto }
.contact-method input { float: none; width: 20px }

After these styles have been added, you should see something that looks like the image below.

Next we’re going to add a drop shadow to make the bottom corners of the fieldsets a little more prominent.

Box Shadow

Box shadow is similar to border-radius syntax-wise, you need to specify two vendor prefix properties and the official property as defined in the CSS3 spec.  Border-radius takes 3 lengths: the horizontal offset, the vertical offset, and the blur radius; and a colour.

#style {
    -webkit-box-shadow: <horz>, <vert>, <blur>, <color>;
    -moz-box-shadow: <horz>, <vert>, <blur>, <color>;
    box-shadow: <horz>, <vert>, <blur>, <color>;
}

We’re going to add a box shadow to the fieldsets, the legends, and the submit button, using the values highlighted below.  Notice that we’re also adding 4px margin to prevent the overflow: auto property on the parent paragraph tag from clipping the shadow on the bottom of the submit button.

form { width: 460px; padding: 20px; color: #333333; font-family: Verdana, Geneva, sans-serif; font-size: 12px; margin: 0 auto }
fieldset { position: relative; padding: 10px; padding-top: 0; padding-top: 25px9; margin-bottom: 30px; background: #F6F6F6; -webkit-border-radius: 8px; -moz-border-radius: 8px; border-radius: 8px; background: -webkit-gradient(linear, left top, left bottom, from(#EFEFEF), to(#FFFFFF)); background: -moz-linear-gradient(center top, #EFEFEF, #FFFFFF 100%); box-shadow: 3px 3px 10px #ccc; -moz-box-shadow: 3px 3px 10px #ccc; -webkit-box-shadow: 3px 3px 10px #ccc }
legend { padding: 6px 12px; position: absolute; left: 10px; top: -11px; background-color: #4F709F; color: white; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; box-shadow: 2px 2px 4px #888; -moz-box-shadow: 2px 2px 4px #888; -webkit-box-shadow: 2px 2px 4px #888 }
label, span { float: left; clear: left; display: block; width: 180px; padding-right: 20px; text-align: right; height: 26px; line-height: 26px }
input, textarea, select { float: left; width: 200px; border: 1px solid #d9d9d9 }
.submit { width: 100px; float: right; margin-right: 37px; border: 0; padding: 5px 10px; background: #4F709F; color: white; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; box-shadow: 2px 2px 4px #888; -moz-box-shadow: 2px 2px 4px #888; -webkit-box-shadow: 2px 2px 4px #888; margin-bottom: 4px }
textarea { background: -webkit-gradient(linear, left top, left bottom, from(#FFFFFF), color-stop(1%, #EEEEEE), to(#FFFFFF)) }
select { width: 202px }
p { overflow: hidden; margin-bottom: 10px }
p:first-of-type { padding-top: 25px }
.contact-method label { clear: none; width: auto }
.contact-method input { float: none; width: 20px }

Very Useful Tip: You can also use rgba values with box shadow, achieving an effect similar to the one I described in my CSS3 Alpha Transparent Gradients post.

Text Shadow

Next we’ll add a bit of text shadow on the legends and submit button to make them stand out a little more.  The text shadow property is pretty much the same as box shadow.  It takes 3 values and a colour, that is, the x value of the shadow, the y value of the shadow, the blur radius, and a colour.  However, this time we don’t have to use vendor prefixes, as native property is supported in all current browsers.

#style {
    text-shadow: <horz>, <vert>, <blur>, <color>;
}

Add the highlighted lines below to your css.

form { width: 460px; padding: 20px; color: #333333; font-family: Verdana, Geneva, sans-serif; font-size: 12px; margin: 0 auto }
fieldset { position: relative; padding: 10px; padding-top: 0; padding-top: 25px9; margin-bottom: 30px; background: #F6F6F6; -webkit-border-radius: 8px; -moz-border-radius: 8px; border-radius: 8px; background: -webkit-gradient(linear, left top, left bottom, from(#EFEFEF), to(#FFFFFF)); background: -moz-linear-gradient(center top, #EFEFEF, #FFFFFF 100%); box-shadow: 3px 3px 10px #ccc; -moz-box-shadow: 3px 3px 10px #ccc; -webkit-box-shadow: 3px 3px 10px #ccc }
legend { padding: 6px 12px; position: absolute; left: 10px; top: -11px; background-color: #4F709F; color: white; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; box-shadow: 2px 2px 4px #888; -moz-box-shadow: 2px 2px 4px #888; -webkit-box-shadow: 2px 2px 4px #888; text-shadow: 1px 1px 1px #333 }
label, span { float: left; clear: left; display: block; width: 180px; padding-right: 20px; text-align: right; height: 26px; line-height: 26px }
input, textarea, select { float: left; width: 200px; border: 1px solid #d9d9d9 }
.submit { width: 100px; float: right; margin-right: 37px; border: 0; padding: 5px 10px; background: #4F709F; color: white; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; box-shadow: 2px 2px 4px #888; -moz-box-shadow: 2px 2px 4px #888; -webkit-box-shadow: 2px 2px 4px #888; margin-bottom: 4px; text-shadow: 1px 1px 1px #333 }
textarea { background: -webkit-gradient(linear, left top, left bottom, from(#FFFFFF), color-stop(1%, #EEEEEE), to(#FFFFFF)) }
input:focus, textarea:focus, select:focus { background: white; border-color: #666 }
select { width: 202px }
p { overflow: hidden; margin-bottom: 10px }
p:first-of-type { padding-top: 25px }
.contact-method label { clear: none; width: auto }
.contact-method input { float: none; width: 20px }

Focus Highlighting

We’re going to add one last style, to make the input boxes more prominent when they’re in focus…

nput:focus, textarea:focus, select:focus { border-color: #666 }

…and one last tweak, to make the input boxes a little bigger.

input, textarea, select { padding: 3px; float: left; width: 200px; border: 1px solid #d9d9d9 }
select { width: 208px }

The final CSS should look like this:

form { width: 460px; padding: 20px; color: #333333; font-family: Verdana, Geneva, sans-serif; font-size: 12px; margin: 0 auto }
fieldset { position: relative; padding: 10px; padding-top: 0; margin-bottom: 30px; background: #F6F6F6; -webkit-border-radius: 8px; -moz-border-radius: 8px; border-radius: 8px; background: -webkit-gradient(linear, left top, left bottom, from(#EFEFEF), to(#FFFFFF)); background: -moz-linear-gradient(center top, #EFEFEF, #FFFFFF 100%); box-shadow: 3px 3px 10px #ccc; -moz-box-shadow: 3px 3px 10px #ccc; -webkit-box-shadow: 3px 3px 10px #ccc }
legend { padding: 6px 12px; position: absolute; left: 10px; top: -11px; background-color: #4F709F; color: white; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; box-shadow: 2px 2px 4px #888; -moz-box-shadow: 2px 2px 4px #888; -webkit-box-shadow: 2px 2px 4px #888; text-shadow: 1px 1px 1px #333 }
label, span { float: left; clear: left; display: block; width: 180px; padding-right: 20px; text-align: right; height: 26px; line-height: 26px }
input, textarea, select { padding: 3px; float: left; width: 200px; border: 1px solid #d9d9d9 }
.submit { width: 100px; float: right; margin-right: 37px; border: 0; padding: 5px 10px; background: #4F709F; color: white; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; box-shadow: 2px 2px 4px #888; -moz-box-shadow: 2px 2px 4px #888; -webkit-box-shadow: 2px 2px 4px #888; margin-bottom: 4px; text-shadow: 1px 1px 1px #333 }
textarea { background: -webkit-gradient(linear, left top, left bottom, from(#FFFFFF), color-stop(1%, #EEEEEE), to(#FFFFFF)) }
input:focus, textarea:focus, select:focus { background: white; border-color: #666 }
select { width: 208px }
p { overflow: hidden; margin-bottom: 10px }
p:first-of-type { padding-top: 25px }
.contact-method label { clear: none; width: auto }
.contact-method input { float: none; width: 20px }

View Demo

In the next tutorial, I’ll show you how to test for browser support of the new input types.

HTML5 Form Tutorial

In part one of this four five part tutorial, we’re going to create a contact form using the new HTML5 input attributes.  It must be stated outright that as browser support is still lacking for these attributes, this tutorial is for illustrative purposes only – I wouldn’t recommend using the new input types in a production environment yet.  That being said, it’s interesting to know what’s in store for us just around the corner.  Let’s get started.

HTML Template

This is the base template we’re going to use for the form.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <title>Contact Form</title>
</head>
<body>
</body>
</html>

As you can see, we’re using the HTML5 doctype.  Notice that the meta tag defining the character set is shorter than in previous versions of the language:

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

In HTML5, the http-equiv and content attributes are superfluous, so we can strip those out.  The same goes for the type attribute in the style and script tags – we no longer need to define the type as text/javascript or text/css, as it’s implied by the tag name.

Input Types

Horse and PloughThe input element, as specified in HTML4, has ten values for it’s type attribute: button, checkbox, file, hidden, image, password, radio, reset, submit, and text.  The text input in particular has become the work-horse of HTML forms, used to capture a wide range of alphanumeric data.  This will change with the adoption of the new HTML5 input types.  In the same way that div is set to be superceded by header, nav, article, section, aside, and footer, the text attribute will be displaced by more semantic type attributes (search, tel, url, email, datetime, date, month, week, time, datetime-local, number, color and range) that accurately describe the data to be entered into them.

Contact Form

The form we’re going to create will not be a run-of-the-mill contact form, it will be aimed at satisfying the needs of a freelancer.  It’s purpose will be to collect enquiries from clients regarding potential projects – this enables us to examine how these new input types might work in a real-world setting.

The data our form will capture is as follows (the input type we will use is in brackets):

  • Name (text)
  • Company (text)
  • Email (email)
  • Telephone (tel)
  • Website (url)
  • Type of Project (select)
  • Projected Completion Date (date)
  • Estimated Budget (number)
  • Additional Details (textarea)
  • Please contact me by Email | Phone (radio)
  • Best time to contact? (time, disabled unless ‘contact by phone’ selected)

We’re going to seperate these properties into 3 fieldsets to make the form a little easier on the eye, and create the label and inputs for them.  The markup will look like this:

<form>
  <fieldset>
    <p><label>Name</label><input type="text" /></p>
    <p><label>Company</label><input type="text" /></p>
    <p><label>Email</label><input type="email" /></p>
    <p><label>Telephone</label><input type="tel" /></p>
    <p><label>Website</label><input type="url" /></p>
  </fieldset>
  <fieldset>
    <p><label>Type of Project</label></p>
    <p>
      <select>
        <option>HTML/CSS Build</option>
        <option>Custom jQuery Plugin</option>
        <option>Wordpress Template</option>
        <option>Other</option>
      </select>
    </p>
    <p><label>Projected Completion Date</label><input type="date" /></p>
    <p><label>Estimated Budget (&pound;)</label><input type="number" /></p>
    <p><label>Additional Details</label><textarea rows="5"></textarea></p>
  </fieldset>
  <fieldset>
    <p class="contact-method">
      <span>Please contact me by&hellip;</span>
      <label><input type="radio" value="Phone" checked="checked" /> Phone</label>
      <label><input type="radio" value="Email" /> Email</label>
    </p>
    <p><label>Best time to contact?</label><input type="time" /></p>
    <p><input class="submit" type="submit" name="submit" /></p>
  </fieldset>
</form>

Notice that we’ve added a class to the submit button and to the paragraph containing the contact method.  In an ideal world we’d be able to use a pseudo class like nth-child to style these elements, but it isn’t supported in IE6&7, so we’ll stick with normal classes for now.  We’re going to add a bit of basic CSS, just to make the form look a little nicer for the time being.

* { margin: 0; padding: 0 }
form { width: 460px; padding: 20px; color: #333333; font-family: Verdana, Geneva, sans-serif; font-size: 12px; margin: 0 auto }
fieldset { border: 0; padding: 10px; margin-bottom: 10px; background: #F6F6F6 }
legend { padding: 5px 10px }
label, span { float: left; clear: left; display: block; width: 180px; padding-right: 20px; text-align: right }
input, textarea, select { float: left; width: 200px }
.submit { width: 100px; float: right; margin-right: 37px }
select { width: 202px }
p { overflow: hidden; margin-bottom: 10px }
.contact-method label { clear: none; width: auto }
.contact-method input { float: none; width: 20px }

We’ll revisit the CSS in the next part of this tutorial.

Form Accessibility

In it’s present state, the form isn’t very accessible to people using screen readers, so we’re going to make some improvements by adding a legend to the fieldset, adding input names IDs (correction 01/05/11: names for php enabled forms, IDs for accessible form controls.  With thanks to Nick :)), and associating the input with it’s counterpart label using the ‘for’ attribute.  A brief note – there is some debate between accessibility experts on whether it’s better to wrap the input with it’s label, rather than creating sibling elements.  I think it’s a matter of personal preference – using the ‘for’ attribute creates an explicit association between the two elements.

As this form is contained within a basic page, it’s not necessary to define tab indices; the flow of the page is pretty basic and should tab in the correct order.  However, if you’re working with more complex markup, other elements on the page may interfere with the flow of the document – in this situation it’s best practice to define tab indices to assist with navigation of the page.

Once the modifications are complete, the markup will look like this:

<form>
  <fieldset>
    <legend>Contact Details</legend>
    <p><label for="name">Name</label><input id="name" type="text" /></p>
    <p><label for="company">Company</label><input id="company" type="text" /></p>
    <p><label for="email">Email</label><input id="email" type="email" /></p>
    <p><label for="telephone">Telephone</label><input id="telephone" type="tel" /></p>
    <p><label for="website">Website</label><input id="website" type="url" /></p>
  </fieldset>
  <fieldset>
    <legend>Project Details</legend>
    <p>
      <label for="project-type">Type of Project</label>
      <select id="project-type">
        <option>HTML/CSS Build</option>
        <option>Custom jQuery Plugin</option>
        <option>Wordpress Template</option>
        <option>Other</option>
      </select>
    </p>
    <p><label for="completion-date">Projected Completion Date</label><input id="completion-date" type="date" /></p>
    <p><label for="project-budget">Estimated Budget (&pound;)</label><input id="project-budget" type="number" /></p>
    <p><label for="project-details">Additional Details</label><textarea id="project-details" rows="5"></textarea></p>
  </fieldset>
  <fieldset>
    <legend>Contact Preferences</legend>
    <p>
      <span>Please contact me by&hellip;</span>
      <label><input id="contact-method" type="radio" value="Phone" checked="checked" /> Phone</label>
      <label><input id="contact-method" type="radio" value="Email" /> Email</label>
    </p>
    <p><label for="contact-time">Best time to contact?</label><input id="contact-time" type="time" /></p>
    <p><input type="submit" id="submit" value="Submit Form" /></p>
  </fieldset>
</form>

View Demo

Next time, we’ll be looking at sprucing our form up a bit with CSS. Stay tuned.

Photo Credit: Trevor Dennis on Flickr