diff --git a/browserid/compress.sh b/browserid/compress.sh
index 8166eeaafec334165353393eb6f98286989f1c82..6872fe9fb50fcb37d9a78df5dd951f5f80003809 100755
--- a/browserid/compress.sh
+++ b/browserid/compress.sh
@@ -44,7 +44,7 @@ echo ''
 
 cd ../js
 # re-minimize everything together
-cat jquery-1.6.2.min.js json2.js browserid.js ../dialog/resources/underscore-min.js ../dialog/resources/storage.js ../dialog/resources/browserid-network.js ../dialog/resources/browserid-identities.js ../dialog/resources/tooltip.js pages/index.js pages/add_email_address.js pages/verify_email_address.js pages/manage_account.js pages/signin.js pages/signup.js pages/forgot.js > lib.js
+cat jquery-1.6.2.min.js json2.js browserid.js ../dialog/resources/underscore-min.js ../dialog/resources/storage.js ../dialog/resources/browserid-network.js ../dialog/resources/browserid-identities.js ../dialog/resources/tooltip.js ../dialog/resources/validation.js pages/index.js pages/add_email_address.js pages/verify_email_address.js pages/manage_account.js pages/signin.js pages/signup.js pages/forgot.js > lib.js
 $UGLIFY < lib.js > lib.min.js
 
 cd ../css
diff --git a/browserid/static/dialog/controllers/authenticate_controller.js b/browserid/static/dialog/controllers/authenticate_controller.js
index 2c1f2145493ac3f5210e7658de404cb8f3dbc911..1472bf4a3e0a56991ed888ba1f71cf1d854acf97 100644
--- a/browserid/static/dialog/controllers/authenticate_controller.js
+++ b/browserid/static/dialog/controllers/authenticate_controller.js
@@ -39,7 +39,8 @@
 
   var ANIMATION_TIME = 250,
       bid = BrowserID,
-      identities = bid.Identities;
+      identities = bid.Identities,
+      validation = bid.Validation;
 
   function checkEmail(el, event) {
     var email = $("#email").val(),
@@ -47,7 +48,7 @@
 
     cancelEvent(event);
 
-    if(!self.validateEmail(email)) {
+    if(!validation.email(email)) {
       return;
     }
 
@@ -67,7 +68,7 @@
 
     cancelEvent(event);
 
-    if(!self.validateEmail(email)) {
+    if(!validation.email(email)) {
       return;
     }
 
@@ -91,12 +92,7 @@
 
     cancelEvent(event);
 
-    if(!self.validateEmail(email)) {
-      return;
-    }
-
-    if(!pass) {
-      bid.Tooltip.showTooltip("#password_required");
+    if(!validation.emailAndPassword(email, pass)) {
       return;
     }
 
diff --git a/browserid/static/dialog/controllers/page_controller.js b/browserid/static/dialog/controllers/page_controller.js
index a63ea765c773d47204be28d0e671e12b18dc1bf0..aee8e85b1005e935fed0c8e3bad4b572a5092b30 100644
--- a/browserid/static/dialog/controllers/page_controller.js
+++ b/browserid/static/dialog/controllers/page_controller.js
@@ -41,21 +41,6 @@
       identities = bid.Identities;
 
 
-  function validateEmail(email) {
-    var valid = true;
-
-    if(!email) {
-      bid.Tooltip.showTooltip("#email_required");
-      valid = false;
-    }
-    else if(!bid.verifyEmail(email)) {
-      bid.Tooltip.showTooltip("#email_format");
-      valid = false;
-    }
-
-    return valid;
-  }
-
   $.Controller.extend("PageController", {
     }, {
     init: function(options) {
@@ -165,9 +150,7 @@
       event.preventDefault();
       event.stopPropagation();
       this.close("start");
-    },
-
-    validateEmail: validateEmail
+    }
   });
 
 }());
diff --git a/browserid/static/dialog/controllers/pickemail_controller.js b/browserid/static/dialog/controllers/pickemail_controller.js
index caf7f1e732a44eaf735272bf772a60e6b01bc628..fc4cb138e1c654a0cf42aebc51aa2d96806cf4e4 100644
--- a/browserid/static/dialog/controllers/pickemail_controller.js
+++ b/browserid/static/dialog/controllers/pickemail_controller.js
@@ -123,7 +123,7 @@
 
     cancelEvent(event);
 
-    if(!self.validateEmail(email)) {
+    if(!bid.Validation.email(email)) {
       return;
     }
 
diff --git a/browserid/static/dialog/css/popup.css b/browserid/static/dialog/css/popup.css
index b95a704b08bfdf92e4594f8fed4c1250a963883d..fcd2eb0d195274dd8093d743dc27482030bd0721 100644
--- a/browserid/static/dialog/css/popup.css
+++ b/browserid/static/dialog/css/popup.css
@@ -504,10 +504,12 @@ html[xmlns] .cf {
     margin: 1em 0 .5em;
     padding: 0 1em;
     line-height: 18px;
+    min-height: 110px;
 }
 
 .pickemail .inputs {
     height: 110px;
+    min-height: 0;
     overflow-y: auto;
 }
 
diff --git a/browserid/static/dialog/dialog.js b/browserid/static/dialog/dialog.js
index b163ea65e7a40f5dd986644ccaa53b6cbd91db03..061ffa15f97389721b438a9908391395fa04ed7a 100644
--- a/browserid/static/dialog/dialog.js
+++ b/browserid/static/dialog/dialog.js
@@ -54,6 +54,7 @@ steal.plugins(
                'browserid',
                'storage',
                'tooltip',
+               'validation',
                'browserid-extensions',
                'browserid-network',
                'browserid-identities',
diff --git a/browserid/static/dialog/resources/browserid.js b/browserid/static/dialog/resources/browserid.js
index ad6c7d8de2e010aeb9168400a98d27140d5dffc9..6bc596beb7a51a075dce8b6de7670f7ac18a0e47 100644
--- a/browserid/static/dialog/resources/browserid.js
+++ b/browserid/static/dialog/resources/browserid.js
@@ -39,11 +39,4 @@
   window.BrowserID = window.BrowserID || {};
 
 
-  window.BrowserID.verifyEmail = function(address) {
-    // gotten from http://blog.gerv.net/2011/05/html5_email_address_regexp/
-    // changed the requirement that there must be a ldh-str because BrowserID 
-    // is only used on internet based networks.
-    return /^[\w.!#$%&'*+\-/=?\^`{|}~]+@[a-z0-9-]+(\.[a-z0-9-]+)+$/.test(address);
-  };
-
 }());
diff --git a/browserid/static/dialog/resources/validation.js b/browserid/static/dialog/resources/validation.js
new file mode 100644
index 0000000000000000000000000000000000000000..371f60e4c539dfe3620507eae56ecca511510486
--- /dev/null
+++ b/browserid/static/dialog/resources/validation.js
@@ -0,0 +1,88 @@
+/*globals BrowserID: true */
+BrowserID.Validation = (function() {
+  var bid = BrowserID,
+      tooltip = bid.Tooltip;
+
+  bid.verifyEmail = function(address) {
+    // gotten from http://blog.gerv.net/2011/05/html5_email_address_regexp/
+    // changed the requirement that there must be a ldh-str because BrowserID 
+    // is only used on internet based networks.
+    return /^[\w.!#$%&'*+\-/=?\^`{|}~]+@[a-z0-9-]+(\.[a-z0-9-]+)+$/.test(address);
+  };
+
+
+  function validateEmail(email) {
+    var valid = false;
+
+    if (!email) {
+      tooltip.showTooltip("#email_required");
+    }
+    else if (!bid.verifyEmail(email)) {
+      tooltip.showTooltip("#email_format");
+    }
+    else {
+      valid = true;
+    }
+
+    return valid;
+  }
+
+  function validateEmailAndPassword(email, password) {
+    var valid = validateEmail(email);
+
+    if (valid) {
+      valid = passwordExists(password);
+    }
+
+    return valid;
+  }
+
+  function passwordExists(password) {
+    var valid = !!password;
+
+    if (!valid) {
+      tooltip.showTooltip("#password_required");
+    }
+
+    return valid;
+  }
+
+  function passwordLength(password) {
+    var valid = password && (password.length >= 8);
+
+    if(!valid) {
+      tooltip.showTooltip("#password_too_short");
+    }
+
+    return valid;
+  }
+
+  function validationPasswordExists(vpass) {
+    var valid = !!vpass;
+
+    if(!valid) {
+      tooltip.showTooltip("#vpassword_required");
+    }
+
+    return valid;
+  }
+
+  function passwordAndValidationPassword(pass, vpass) {
+    var valid = passwordExists(pass) && passwordLength(pass) && validationPasswordExists(vpass);
+
+    if (valid && pass !== vpass) {
+      valid = false;
+      tooltip.showTooltip("#passwords_no_match");
+    }
+
+    return valid;
+  }
+
+  return {
+    email: validateEmail,
+    emailAndPassword: validateEmailAndPassword,
+    passwordAndValidationPassword: passwordAndValidationPassword
+  };
+  
+}());
+
diff --git a/browserid/static/dialog/test/qunit/browserid_unit_test.js b/browserid/static/dialog/test/qunit/browserid_unit_test.js
index 14348486a2a4d8589db6218d6c9afe90172603b1..5755fabc56122270993a990e6bea620d0c424eb5 100644
--- a/browserid/static/dialog/test/qunit/browserid_unit_test.js
+++ b/browserid/static/dialog/test/qunit/browserid_unit_test.js
@@ -39,31 +39,6 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/browserid", fu
 
   module("browserid-unit");
   
-  var bid = BrowserID;
-
-  test("email address x@y.z is valid", function() {
-    ok(bid.verifyEmail("x@y.z"), "x@y.z is valid");
-  });
-
-  test("email address x@y.z.w is valid", function() {
-    ok(bid.verifyEmail("x@y.z.w"), "x@y.z.w is valid");
-  });
-
-  test("email address x.v@y.z.w is valid", function() {
-    ok(bid.verifyEmail("x.v@y.z.w"), "x.v@y.z.w is valid");
-  });
-
-  test("email address x_v@y.z.w is valid", function() {
-    ok(bid.verifyEmail("x_v@y.z.w"), "x_v@y.z.w is valid");
-  });
-
-  test("email address x is not valid", function() {
-    equal(bid.verifyEmail("x"), false, "x is not valid");
-  });
-
-  test("email address x@y is not valid", function() {
-    equal(bid.verifyEmail("x@y"), false, "x@y is not valid");
-  });
 
 });
 
diff --git a/browserid/static/dialog/test/qunit/qunit.js b/browserid/static/dialog/test/qunit/qunit.js
index 1faf4244f4b001658ce9191b1ccb1c4edfdb5591..b66d55a29bc6c2832dcd2775d20bffdabe59586e 100644
--- a/browserid/static/dialog/test/qunit/qunit.js
+++ b/browserid/static/dialog/test/qunit/qunit.js
@@ -1,8 +1,11 @@
 steal("/dialog/resources/browserid.js",
       "/dialog/resources/storage.js",
+      "/dialog/resources/tooltip.js",
+      "/dialog/resources/validation.js",
       "/dialog/resources/underscore-min.js")
   .plugins("funcunit/qunit")
   .then("browserid_unit_test")
-  .then("browserid-storage_unit_test")
+  .then("validation_unit_test")
+  .then("storage_unit_test")
   .then("browserid-network_test")
   .then("browserid-identities_unit_test")
diff --git a/browserid/static/dialog/test/qunit/browserid-storage_unit_test.js b/browserid/static/dialog/test/qunit/storage_unit_test.js
similarity index 100%
rename from browserid/static/dialog/test/qunit/browserid-storage_unit_test.js
rename to browserid/static/dialog/test/qunit/storage_unit_test.js
diff --git a/browserid/static/dialog/test/qunit/validation_unit_test.js b/browserid/static/dialog/test/qunit/validation_unit_test.js
new file mode 100644
index 0000000000000000000000000000000000000000..5a0be014fb44be2d3ead64b13b6f5126a564d17c
--- /dev/null
+++ b/browserid/static/dialog/test/qunit/validation_unit_test.js
@@ -0,0 +1,181 @@
+/*jshint browsers:true, forin: true, laxbreak: true */
+/*global steal: true, test: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserID: true */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla BrowserID.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/browserid", function() {
+  "use strict";
+
+  var bid = BrowserID,
+      validation = bid.Validation,
+      tooltipShown,
+      origShowTooltip;
+
+  function showTooltip(el) {
+    tooltipShown = true;
+  }
+
+  module("validation-unit", {
+    setup: function() {
+      origShowTooltip = bid.Tooltip.showTooltip;
+      bid.Tooltip.showTooltip = showTooltip;
+      tooltipShown = false;
+    },
+
+    teardown: function() {
+      bid.Tooltip.showTooltip = origShowTooltip;
+    }
+  });
+  
+  test("email address x@y.z is valid", function() {
+    ok(bid.verifyEmail("x@y.z"), "x@y.z is valid");
+  });
+
+  test("email address x@y.z.w is valid", function() {
+    ok(bid.verifyEmail("x@y.z.w"), "x@y.z.w is valid");
+  });
+
+  test("email address x.v@y.z.w is valid", function() {
+    ok(bid.verifyEmail("x.v@y.z.w"), "x.v@y.z.w is valid");
+  });
+
+  test("email address x_v@y.z.w is valid", function() {
+    ok(bid.verifyEmail("x_v@y.z.w"), "x_v@y.z.w is valid");
+  });
+
+  test("email address x is not valid", function() {
+    equal(bid.verifyEmail("x"), false, "x is not valid");
+  });
+
+  test("email address x@y is not valid", function() {
+    equal(bid.verifyEmail("x@y"), false, "x@y is not valid");
+  });
+
+  test("email address x@y. is not valid", function() {
+    equal(bid.verifyEmail("x@y."), false, "x@y. is not valid");
+  });
+
+
+  
+  test("email with valid email", function() {
+    var valid = validation.email("testuser@testuser.com");
+
+    ok(valid, "valid email is valid");
+    equal(tooltipShown, false, "valid email shows no tooltip");
+  });
+
+  test("email with empty email", function() {
+    var valid = validation.email("");
+
+    equal(valid, valid, "missing email is missing");
+    equal(tooltipShown, true, "missing email shows no tooltip");
+  });
+
+  test("email with invalid email", function() {
+    var valid = validation.email("testuser@testuser");
+
+    equal(valid, valid, "invalid email is invalid");
+    equal(tooltipShown, true, "invalid email shows no tooltip");
+  });
+
+
+  test("validateEmailAndPassword with valid email and password", function() {
+    var valid = validation.emailAndPassword("testuser@testuser.com", "password");
+
+    ok(valid, "valid email and password are valid");
+    equal(tooltipShown, false, "valid email and password shows no tooltip");
+  });
+
+  test("validateEmailAndPassword with empty email", function() {
+    var valid = validation.emailAndPassword("", "password");
+
+    equal(valid, false, "empty email is invalid");
+    equal(tooltipShown, true, "empty email shows tooltip");
+  });
+
+  test("validateEmailAndPassword with invalid email", function() {
+    var valid = validation.emailAndPassword("testuser", "password");
+
+    equal(valid, false, "invalid email is invalid");
+    equal(tooltipShown, true, "invalid email shows tooltip");
+  });
+
+  test("validateEmailAndPassword with empty password", function() {
+    var valid = validation.emailAndPassword("testuser@testuser.com", "");
+
+    equal(valid, false, "empty password is invalid");
+    equal(tooltipShown, true, "empty password shows tooltip");
+  });
+
+
+  test("passwordAndValidationPassword with empty password", function() {
+    var valid = validation.passwordAndValidationPassword("", "password");
+
+    equal(valid, false, "empty password is invalid");
+    equal(tooltipShown, true, "empty password shows tooltip");
+  });
+
+
+  test("passwordAndValidationPassword with too short password", function() {
+    var valid = validation.passwordAndValidationPassword("pass", "password");
+
+    equal(valid, false, "too short password is invalid");
+    equal(tooltipShown, true, "too short password shows tooltip");
+  });
+
+  test("passwordAndValidationPassword with empty validation password", function() {
+    var valid = validation.passwordAndValidationPassword("password", "");
+
+    equal(valid, false, "empty validation password is invalid");
+    equal(tooltipShown, true, "empty validation password shows tooltip");
+  });
+
+
+  test("passwordAndValidationPassword with different validation password", function() {
+    var valid = validation.passwordAndValidationPassword("password", "pass");
+
+    equal(valid, false, "different password is invalid");
+    equal(tooltipShown, true, "different password shows tooltip");
+  });
+
+  test("passwordAndValidationPassword all valid", function() {
+    var valid = validation.passwordAndValidationPassword("password", "password");
+
+    equal(valid, true, "passwords valid");
+    equal(tooltipShown, false, "tooltip not shown");
+  });
+
+});
+
+
diff --git a/browserid/static/js/pages/signin.js b/browserid/static/js/pages/signin.js
index 1afb9d305374daaf9bccbf6507a9abfb3bd1c840..4eca01bd74779481a34a6554104850d85366261f 100644
--- a/browserid/static/js/pages/signin.js
+++ b/browserid/static/js/pages/signin.js
@@ -37,7 +37,11 @@
 (function() {
   "use strict";
 
-  BrowserID.signIn = function () {
+  var bid = BrowserID,
+      network = bid.Network,
+      validation = bid.Validation;
+
+  bid.signIn = function () {
     $("form input[autofocus]").focus();
 
     $("#signUpForm").bind("submit", function(event) {
@@ -45,18 +49,22 @@
 
       var email = $("#email").val(),
           password = $("#password").val();
-      
-      BrowserID.Network.authenticate(email, password, function onSuccess(authenticated) {
-        if (authenticated) {
-          document.location = "/";
-        }
-        else {
-          // bad authentication
-          $(".notifications .notification.doh").fadeIn();
-        }
-      }, function onFailure() {
-        // Wah wah.  Network error
-      });
+
+      var valid = validation.emailAndPassword(email, password);
+
+      if (valid) {
+        network.authenticate(email, password, function onSuccess(authenticated) {
+          if (authenticated) {
+            document.location = "/";
+          }
+          else {
+            // bad authentication
+            $(".notifications .notification.doh").fadeIn();
+          }
+        }, function onFailure() {
+          // Wah wah.  Network error
+        });
+      }
     });
   };
 }());
diff --git a/browserid/static/js/pages/signup.js b/browserid/static/js/pages/signup.js
index 7cf7a3cf11cb1e462b8db1df8b3b7762adfca247..b834fcb7046f6f1bd3e9172bf22d722b181bd72e 100644
--- a/browserid/static/js/pages/signup.js
+++ b/browserid/static/js/pages/signup.js
@@ -37,8 +37,11 @@
 (function() {
   "use strict";
 
-  BrowserID.signUp = function () {
-    var ANIMATION_SPEED = 250;
+  var bid = BrowserID,
+      identities = bid.Identities,
+      ANIMATION_SPEED = 250;
+
+  bid.signUp = function () {
 
     function replaceWithMessage(selector) {
         $('.forminputs').fadeOut(ANIMATION_SPEED, function() {
@@ -56,8 +59,6 @@
 
 
     $(function () {
-      var identities = BrowserID.Identities;
-
       $("form input[autofocus]").focus();
 
       $("#email").bind("keyup", function(event) {
@@ -69,7 +70,13 @@
       $("#signUpForm").bind("submit", function(event) {
         event.preventDefault();
 
-        var email = $("#email").val();
+        var email = $("#email").val(),
+            valid = bid.Validation.email(email);
+
+        if (!valid) {
+          return;
+        }
+
         identities.emailRegistered(email, function(registered) {
           if (!registered) {
             identities.createUser(email, function onSuccess(keypair) {
diff --git a/browserid/static/js/pages/verify_email_address.js b/browserid/static/js/pages/verify_email_address.js
index d4355614066107055f57fcdc97d5ca1a3a152048..07642d077dc1639d25939d9e7510d3f6abc8a578 100644
--- a/browserid/static/js/pages/verify_email_address.js
+++ b/browserid/static/js/pages/verify_email_address.js
@@ -45,37 +45,14 @@
     $("#signUpForm").remove();
   }
 
-  function validate(pass, vpass) {
-    var valid = false;
-
-    if(!pass) {
-      tooltip.showTooltip("#password_required"); 
-    }
-    else if(pass.length < 8) {
-      tooltip.showTooltip("#password_too_short"); 
-    }
-    else if(!vpass) {
-      tooltip.showTooltip("#vpassword_required"); 
-    }
-    else if(pass !== vpass) {
-      tooltip.showTooltip("#passwords_no_match"); 
-    }
-    else {
-      valid = true;
-    }
-
-    return valid;
-  }
-
   bid.verifyEmailAddress = function(token) {
     $("#signUpForm").submit(function(event) {
       event.preventDefault();
 
-      var email = $("#email").val(),
-          pass = $("#password").val(),
+      var pass = $("#password").val(),
           vpass = $("#vpassword").val();
 
-      var valid = validate(pass, vpass);
+      var valid = bid.Validation.passwordAndValidationPassword(pass, vpass);
 
       if (valid) {
         bid.Network.completeUserRegistration(token, pass, function onSuccess(registered) {
diff --git a/browserid/views/layout.ejs b/browserid/views/layout.ejs
index 490e5c4659b8b1b4c5cbf9ffd30e9edfb0f7076f..1aa89ac022eb3f4cba986dc6aa5c4bbf76b84b37 100644
--- a/browserid/views/layout.ejs
+++ b/browserid/views/layout.ejs
@@ -23,6 +23,7 @@
     <script src="/dialog/resources/browserid-network.js" type="text/javascript"></script>
     <script src="/dialog/resources/browserid-identities.js" type="text/javascript"></script>
     <script src="/dialog/resources/tooltip.js" type="text/javascript"></script>
+    <script src="/dialog/resources/validation.js" type="text/javascript"></script>
     <script src="/js/pages/add_email_address.js" type="text/javascript"></script>
     <script src="/js/pages/verify_email_address.js" type="text/javascript"></script>
     <script src="/js/pages/forgot.js" type="text/javascript"></script>
diff --git a/browserid/views/signin.ejs b/browserid/views/signin.ejs
index 10f279a20d053739045245e3267a73def64e40ef..54089605e911f90aa02d54fbdd087e51f17f8548 100644
--- a/browserid/views/signin.ejs
+++ b/browserid/views/signin.ejs
@@ -1,7 +1,7 @@
 <div id="vAlign">
     <div id="signUpFormWrap">
         <!-- XXX this form submits to nowhere -->
-        <form id="signUpForm" class="cf authform">
+        <form id="signUpForm" class="cf authform" novalidate>
             <h1 class="serif">Sign In</h1>
 
             <ul class="notifications">
@@ -11,7 +11,15 @@
             <ul class="inputs">
                 <li>
                     <label class="serif" for="email">Email Address</label>
-                    <input class="sans" id="email" autofocus placeholder="Your Email" type="email" required x-moz-errormessage="Enter the email address you created your account with" tabindex="1">
+                    <input class="sans" id="email" autofocus placeholder="Your Email" type="email" tabindex="1">
+
+                    <div id="email_format" class="tooltip" for="email">
+                      This field must be an email address.
+                    </div>
+
+                    <div id="email_required" class="tooltip" for="email">
+                      The email field is required.
+                    </div>
                 </li>
                 <li>
                     <label class="serif half" for="password">Password</label>
@@ -19,7 +27,11 @@
                         <!-- XXX: this needs to be fixed -->
                         <a class="forgot" href="/forgot" tabindex="4">forgot your password?</a>
                     </div>
-                    <input class="sans" id="password" placeholder="Your Password" type="password" required x-moz-errormessage="Oops!  We need your password to sign you in" tabindex="2" maxlength="80">
+                    <input class="sans" id="password" placeholder="Your Password" type="password" tabindex="2" maxlength="80">
+
+                    <div id="password_required" class="tooltip" for="password">
+                      The password field is required.
+                    </div>
                 </li>
             </ul>
 
diff --git a/browserid/views/signup.ejs b/browserid/views/signup.ejs
index e16483caea3370c3c8495960c9632a1a70ba2755..29a6f662fbeba3818ed61d68d665b5386ce15b33 100644
--- a/browserid/views/signup.ejs
+++ b/browserid/views/signup.ejs
@@ -1,7 +1,7 @@
 <div id="vAlign">   
     <div id="signUpFormWrap">
         <!-- XXX this form submits to nowhere -->
-        <form id="signUpForm" class="cf authform">
+        <form id="signUpForm" class="cf authform" novalidate>
             <h1 class="serif">Create Account</h1>
 
             <ul class="notifications">
@@ -13,7 +13,15 @@
             <ul class="inputs forminputs">
                 <li>
                     <label class="serif" for="email">Email Address</label>
-                    <input class="sans" id="email" autofocus required placeholder="Your Email" type="email" x-moz-errormessage="Please enter the email address you would like to use">
+                    <input class="sans" id="email" autofocus placeholder="Your Email" type="email" />
+
+                    <div id="email_format" class="tooltip" for="email">
+                      This field must be an email address.
+                    </div>
+
+                    <div id="email_required" class="tooltip" for="email">
+                      The email field is required.
+                    </div>
                 </li>
             </ul>