Grow your CSS skills. Land your dream job.

Part 2: Building a Unique Contact Form

Published by Chris Coyier

I'm calling this Part 2, because last week I began this adventure over on Tutorial Blog where we first designed a unique contact form:

Photoshopping a Unique Contact Form

Here we are going to pick up where that left off and actually build this thing with HTML/CSS, as well as add some validation with jQuery, and make it tick with PHP. Here is what we are building:



1. Slicing out images from Photoshop

The big background image (everything except the form elements)

The page background texture (for everywhere else)

The send button

The input area backgrounds. To create this, I cropped down around the area, exported, then changed the "color overlay" layer style on that layer and exported again. Then I opened both of those exported file, placed one on top of each other, and exported again.

The textarea background. Same technique as above.


2. Marking up the form

This is a typical <form> in most ways. Some labels and inputs with a submit button at the end. What is different about it is the Left and Right divs we need to include because this is essentially a two-column form. Also, each input is wrapped in a div, as we need a hook to properly apply the background images we created.

Here is the all the markup:

<div id="page-wrap">

	<form method="post" action="contactengine.php" id="commentForm">

		<div id="formLeft">
			<label for="Name">Name:</label>
			<div class="input-bg">
				<input type="text" id="Name" name="Name" class="required" minlength="2" />
			<label for="City">City:</label>
			<div class="input-bg">
				<input type="text" id="City" name="City" class="required" minlength="2" />
			<label for="Email">Email:</label>
			<div class="input-bg">
				<input type="text" id="Email" name="Email" class="required email" />
		<div id="formRight">
			<label for="Message">Message:</label></td>
			<div class="message-bg">
				<textarea name="Message" id="Message" rows="20" cols="20" class="required"></textarea>
			<br />
			<input type="image" src="images/send-button.jpg" name="submit" value="Submit" class="submit-button" />

		<div class="clear"></div>




3. Styling with CSS

I'm not going to explain every attribute, as I think much of this is pretty simple and self-explanatory. First I'll show the code here, then I'll explain a few of the highlights/less obvious stuff below.

* { margin: 0; padding: 0; }
body { font-size: 62.5%; font-family: Georgia, serif; background: url(images/page-bg.jpg); }
.clear { clear: both; }
fieldset { border: none; }

#page-wrap {
	width: 800px;
	margin: 0 auto;
	background: url(images/form-bg.jpg) top center no-repeat;
	min-height: 600px;
form {
	padding: 83px 0 0 76px;
h1 {
	text-align: center;
	padding-top: 200px;
#formLeft {
	width: 320px;
	float: left;
	#formLeft input {
		width: 250px;
		margin: 0 0 20px 0;
		border: none;
		text-align: center;
		background: none;
		margin: 13px 0 0 8px;
		font-size: 1.4em;
	#formLeft .input-bg {
		background: url(images/form-sm-bg.jpg) bottom left no-repeat transparent;
		height: 45px;
		margin-bottom: 10px;
		position: relative;
	#formLeft .active {
		background: url(images/form-sm-bg.jpg) top left no-repeat transparent;
#formRight {
	width: 360px;
	float: right;
	padding-right: 44px;
	#formRight textarea {
		width: 298px;
		height: 209px;
		display: block;
		border: none;
		background: none;
		margin: 0 0 0 20px;
		padding: 13px 0 13px 0;
		font-family: Helvetica, sans-serif;
		font-size: 1.3em;
	#formRight .message-bg {
		background: url(images/message-bg.jpg) bottom left no-repeat transparent;
		height: 238px;
	#formRight .active {
		background: url(images/message-bg.jpg) top left no-repeat transparent;
label {
	display: block;
	font-size: 1.3em;
	text-indent: 10px;
	font-weight: bold;
label.error {
	position: absolute;
	top: -16px;
	right: 49px;
	padding: 3px;
	color: #da3939;
	font-size: 1.0em;
	text-align: right;
	font-style: italic;
	font-weight: normal;
input.submit-button {
	float: right;
	padding-right: 31px;

Notice I'm using the * selector here even though I am using forms. The * selector and forms only don't get along when you apply border: none; there, which wrecks default styling for submit buttons and things. We are already going to do a good job of wrecking default styling, so so be it =)

Remember those hooks we threw in to wrap the inputs? We apply the background image to those hooks rather than the input itself. Inputs don't like background images. We also created an "active" class for the inputs, where the background image is shifted from bottom left to top left. Applying and removing that class is what will create our current field highlighting we will cover later.

Each of these extra hooks also has relative positioning. That is because of the "error" messaging that is going to be dynamically added to the page during the form validation. To get those messages positioned correctly, they are going to be absolutely positioned within those relatively positioned parents.

One thing that we didn't do here is apply a background image to the submit button. You get better cross-browser results by making your submit button of type="image" and giving it a src image rather than of type="submit" and applying a background image.


4. Current Field Highlighting with jQuery

Partially for good usability and partially for good aesthetics, we are going to implement current field highlighting. That is why we created those form background images with two different colors. CSS provides some support for this with it's :focus pseudo class, but that won't help us for two reasons. One, it only works on form elements, and we need to change styling of the parent element of the form element which is not possible with CSS. Two, the :focus class isn't widely supported.

We will use jQuery for this, since it supports all the event types that we need: hover, focus and blur. The desired effect is that when any of these form elements are moused over, the background image of the parent element swaps out to our alternate version indicating it is the current field.

Since we have two different types of elements we wish to do this for, we'll need to write two seperate but very similar chunks of jQuery:

<script type="text/javascript" src="js/jquery-1.2.6.min.js"></script>
<script type="text/javascript">
	$("#formLeft .input-bg").hover(function() {
	}, function() {
	$("#formLeft input").focus(function() {
	$("#formLeft input").blur(function() {
		$("#formLeft .input-bg").removeClass("active");
	$("#formRight .message-bg").hover(function() {
	}, function() {
	$("#formRight textarea").focus(function() {
	$("#formRight textarea").blur(function() {
		$("#formRight .message-bg").removeClass("active");

That probably looks more complicated than it is... all this is doing is adding and removing the class of "active" from the appropriate elements when different events occur. This makes the current field highlighting work on a mouse rollover, as well as when the inputs are tabbed to.

More on current field highlighting here.


5. Validating the form

Form validation is useful for both the sending and receiving parties of a contact form. For the sender, it makes sure they have done things like provide a valid email address. Chances are good that if they are using a contact form they wouldn't mind being contacted back, so this is an effort to make sure that field is filled out correctly. For the receiver, form validation is a good start in protection against spam. Also, more directly, it ensures that all submissions have the information that has been declared most important.

Since we are already using the almighty jQuery, let's use it to handle our form validation right on the client side. Fortunately, there is a great plugin for this already built. Simply include the script on your page (after the main jQuery library of course) and add validation to your form by referencing it by ID:

<script type="text/javascript" src="js/jquery-1.2.6.min.js"></script>
<script type="text/javascript" src="js/jquery.validate.js"></script>
<script type="text/javascript">

This plugin is looking for specific class names (and in some cases other attributes) from your inputs to do it's validation. For instance, we want our "Name" field to be a required field, and that the value is greater than or equal to two characters in length. Just add two new attributes to our input element: class="required" and minlength="2".

<input type="text" name="Name" class="required" minlength="2" />

For email address validation, just add the class name like so:

<input type="text" name="Email" class="required email" />

For more advanced and different types of validation, see the documentation for the plugin.


6. Making it work with PHP

Our form validation will prevent the submit button from triggering the "action" of our form if any of the fields don't pass muster. But if all the fields are valid, we want our for to actually do something, right? Since this is a contact form, what we want to happen is for an email to be generated and sent to a specified email address.

In our markup, the "action" for our form is declared here:

<form method="post" action="contactengine.php" id="commentForm">

This will call the "contactengine.php" file and send it variables from our form as POST variables. It is our job then, to capture these variables, create a formated email of of them, and blast off the email.

Here is how that is done. This is the entire contents of the contactengine.php file:



$Subject = "Contact Form Submission";

$Name = Trim(stripslashes($_POST['Name'])); 
$Tel = Trim(stripslashes($_POST['Tel'])); 
$Email = Trim(stripslashes($_POST['Email'])); 
$Message = Trim(stripslashes($_POST['Message'])); 

// prepare email body text
$Body = "";
$Body .= "Name: ";
$Body .= $Name;
$Body .= "\n";
$Body .= "Tel: ";
$Body .= $Tel;
$Body .= "\n";
$Body .= "Email: ";
$Body .= $Email;
$Body .= "\n";
$Body .= "Message: ";
$Body .= $Message;
$Body .= "\n";

// send email 
$success = mail($EmailTo, $Subject, $Body, "From: <$EmailFrom>");

// redirect to success page
if ($success){
  print "<meta http-equiv="refresh" content="0;URL=contactthanks.html">";
  print "<meta http-equiv="refresh" content="0;URL=error.html">";

The meat of this is the "mail" function, which actually does the sending of the email. Notice we call it by while setting a variable ($success). This variable will tell us if the mail was sent successfully or not. If TRUE, we can redirect to our "thank you" page. Otherwise, we should let the user know something went wrong and redirect to an error page.


Demo & Download

So there you have it folks! A nice looking and fully functional form.

View Demo
(Don't try to actually contact me through this form. If you need to contact me, use my regular contact form).

(Includes Photoshop File)



Safari likes to apply it's glowy blue border around all text inputs and text areas. At the time of this writing, there is no way to fight it. This doesn't ruin the usability of the form at all, it just looks a little strange with this design. UPDATE: gaga pointed out below setting outline: none on these form elements will eliminate the Safari glowy blue border. Did not know that, thanks!

IE & Opera like to put vertical scrollbars on textareas no matter what. Again, not a big deal, but I think it looks a little dumb when they aren't needed.


What about a Captcha?

As I'm sure you know, validation helps but far from eliminates form spam. If you will be creating a form that you feel will be in danger of getting spammed, you may want to consider adding a captcha. A captcha is one of those things like "Type the letters you see:" and you get a little graphic with some letters that are obscured by a bunch of lines and whatnot. Sometimes you see captchas that are as simple as "What is 1+1?", because even a very very simple captcha is proven to be very effective at stopping random form spam.

I think the nicest freely available captcha is reCAPTCHA, which works well, is fairly easy to implement, and helps digitize books.

In an old post, I showed how to use reCAPTCHA in a contact form, and it still works. So if you are interested in adding reCAPTCHA to this contact form, check out this example.


  1. great article, thanks. special thanks also to

  2. You’ve included the label-tags, but your inputs don’t have the same ID on them. You can’t click on the labels to get to the input-fields.

    You’ve got:
    <label for=”Name”>Name:</label>
    But to make it work as it should you need to add the ID-attribute to the input-field for Name:
    <input type=”text” id=”Name” name=”Name” class=”required” minlength=”2″ />

    Apart from that it’s a good article. Nice work.

  3. @koew: Good catch, thanks. I added those ID’s.

  4. gaga

    For the blue outline with Safari… try adding input, textarea { outline: none; } it should work ;-)

  5. Thanks gaga! Did not know that. Updated the article.

  6. gaga

    For textarea try setting an overflow: auto on it. It should work for IE but Opera … not so much.

  7. I’ve heard that if you declare: * {margin:0; padding:0;} it can slow down the rendering of the page because it then has to check every single element on the page whether they’re inline or block and apply the style to it rather than just the elements you want to reset to a normalized padding and margin.

    Just a thought for resetting styles and optimum page performance.

  8. @Chad: It’s true the * selector makes the rendering engine do a bit more work, but I have never ever seen hard evidence of exactly how much more work and how much delay it causes. I’ve used it for years and have never heard a complaint. I’m all for improving performance, but I have a feeling the difference is extremely negligible.

  9. Really appreciate the article. Very helpful in the jQuery and I really need to start using/learn more.

    There are a couple of (teensy) UI issues I’m not comfortable with:

    1) When the cursor is in a field, if you hover another field, it changes the background in both the current focused field and the hovered field. In this example, I see the background color as the focus of the field and having it change during mouse movements… well, I’m not sure I like that. Could be a little confusing to the user.

    2) On Safari, you can resize textarea fields. And the background will not grow to enclose the new textarea if resized (expected). Even worse, since the textareas have the outline style set to none, you can no longer see the bounds of the text area. Again, only for Safari users. Maybe there is a way to fix textarea bounds so they can not be resized?

    3) I’m not so hot on the text-align: center on input fields. Don’t see that very often.

    Please don’t take this as a criticism. I just wanted to make a couple of points for others to consider when designing forms.

  10. james

    Another great article. You always manage to write articles just as I need them just like the new jquery video. Thanks!

  11. This is exactly what I have been looking for, for a long time. Thanks for putting together such a nice, easy to follow tutorial. Great work! Keep ‘em comin’ :)


  12. this is really a great article. now a long night waiting mt because i am going to try to do this.

  13. I can’t seem to get the php bit to work!

    I’ve set up Xampp and the webserver runs fine, but the submit takes me straight to the error.html page I created.

  14. PsychoAlienDog
    Permalink to comment#

    This form is incredibly insecure. Client-side validation is only for user convenience. It doesn’t prevent spam, hackers, or annoying web devs (like me :P ). All a hacker would have to do is create their own HTML file without all javascript. Spam bots wouldn’t even use the form they’ll just parse it for the id’s and send raw packets. Always check the input on the server. Never trust the user.

  15. Very informative article. I learn so much reading the code and following the steps in such article.s. Thanks for taking the time to post this.

  16. jan
    Permalink to comment#

    IS it possible to get the validation to work on opera? as it does not validate it at all.

  17. This seems to be a known issue with Opera 9.5, jQuery 1.2.6, and this plugin. Perhaps try rolling back to jQuery 1.2.4?

  18. Craig
    Permalink to comment#

    Is there a way to make it all one form? so that it shows a thank you message on the existing form, but you don’t send to another page???


  19. Permalink to comment#

    I am trying to get your comment form to work on my wife’s website, but it won’t validate or highlight the fields.

    I am not sure what I am doing wrong here. If you could help me out that would be great.

  20. Looks like you have a bunch of javascript libraries on that page, that might causing a conflict? I’d download in install and use Firebug so you can see the javascript errors the page is spitting out. Then maybe download a fresh copy of the form and work backwards implementing it, checking it’s functionality after each change.

  21. Permalink to comment#

    Great article. I always appreciate how thorough your tutorials are. I’m having a little bit of a problem here though.

    I’m working on implementing a variation of this on my portfolio site, however I am stuck because the xhtml for this form doesn’t validate. It looks like the minlength attribute is deprecated, and only maxlength still exists. Do you know of any easy fix for the script or an attribute that can be substituted for minlength to achieve the same result (I know the jQ would have to be altered accordingly)? Thanks.

  22. Permalink to comment#

    is there any way to use this type of contact form in wordpress? it’s great tutorial! thank you!

  23. thanks

  24. Permalink to comment#


  25. Permalink to comment#


  26. thnks

  27. Permalink to comment#

    Great article. Nice Work

  28. Cheryl
    Permalink to comment#


    Just to point out for anyone reading this from the tutorial code you forgot to put the backslash before the double quotation marks in the PHP file. (It is fine in the source files though) It should be:
    if ($success){
    print "< meta http-equiv=\"refresh\" content=\"0;URL=contactthanks.html\">";}else{
    print "< meta http-equiv=\"refresh\" content=\"0;URL=error.html\">";

  29. Shepherd
    Permalink to comment#

    I’ve put in the code correctly as written above but when I click send it wants to download the “contactengine.php” file instead of sending the email. What am I doing wrong?

  30. vijay

    how to get values from selectbox in contactengine.php and how to get multiple values ?

  31. vijay
    Permalink to comment#

    How to give style to contactengine.php?

This comment thread is closed. If you have important information to share, you can always contact me.

*May or may not contain any actual "CSS" or "Tricks".