diff --git a/resources/static/css/style.css b/resources/static/css/style.css
index 69a40d3e13e57527a1f83c9fe6315070ecc16d12..fc2e755689ed39f6d5776fcdf5a37eebac25768f 100644
--- a/resources/static/css/style.css
+++ b/resources/static/css/style.css
@@ -105,6 +105,48 @@ hr {
   width: 700px;
 }
 
+#errorBackground {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  display: none;
+  z-index: 1000;
+  background-color: rgba(0,0,0,.6);
+}
+
+#error > div {
+  background-color: #fff;
+  position: absolute;
+  top: 35%;
+  border: 2px solid #000;
+  border-radius: 5px;
+  padding: 10px;
+  z-index: 1001;
+  text-align: center;
+}
+
+#error ul, #error li {
+    list-style-type: none; 
+}
+
+#wait strong, #error strong {
+    color: #222;
+    font-weight: bold;
+}
+
+#error #moreInfo {
+    display: none;
+}
+
+#error a {
+    color: #549FDC;
+    text-decoration: underline;
+}
+
+
+
 .right {
   text-align: right;
 }
diff --git a/resources/static/dialog/css/popup.css b/resources/static/dialog/css/popup.css
index 9318251411c775fb6b9bfe8e603d0002387785c3..c3231ddda30403eeb3b65c845978a04e856d2c0b 100644
--- a/resources/static/dialog/css/popup.css
+++ b/resources/static/dialog/css/popup.css
@@ -117,16 +117,15 @@ section > .contents {
     height: 250px;
 }
 
-#wait, #error {
-    text-align: center;
-}
-
 #wait {
+    text-align: center;
     z-index: 1;
     background-image: url("/i/bg.png");
 }
 
 #error {
+    position: absolute;
+    text-align: center;
     display: none;
     z-index: 2;
     background-color: #fff;
diff --git a/resources/static/dialog/qunit.html b/resources/static/dialog/qunit.html
index 17d4b0b53ea6fe60227513bef3e727515ea92545..edfa72fae3f32be78de27aa41f4ffea442525147 100644
--- a/resources/static/dialog/qunit.html
+++ b/resources/static/dialog/qunit.html
@@ -52,11 +52,18 @@
       This is a long tooltip.  This should remain on the screen for about 5 seconds.
     </div>
 
+    <div class="forminputs">
+    </div>
+
     <ul class="notifications">
       <li class="notification emailsent">Email Sent</li> 
       <li class="notification doh">doh</li> 
     </ul>
 
+    <script type="text/html" id="templateError">
+      <div class="message">{{ action.title }}</div>
+    </script>
+
     <script type="text/html" id="templateTooltip">
       <div class="tooltip">
         {{ contents }}
diff --git a/resources/static/dialog/resources/error-display.js b/resources/static/dialog/resources/error-display.js
new file mode 100644
index 0000000000000000000000000000000000000000..c70452133340fc07413b71d79ab6fa6bd8004060
--- /dev/null
+++ b/resources/static/dialog/resources/error-display.js
@@ -0,0 +1,30 @@
+BrowserID.ErrorDisplay = (function() {
+  "use strict";
+
+  function render(target, template, data) {
+      template = $(template).html();
+      _.templateSettings = {
+          interpolate : /\{\{(.+?)\}\}/g,
+          evaluate : /\{\%(.+?)\%\}/g
+      };
+      var display = $(_.template(template, data)).appendTo(target);
+
+      /**
+       * What a big steaming pile, use CSS animations for this!
+       */
+      display.find("#openMoreInfo").click(function(event) {
+        event.preventDefault();
+
+        display.find("#moreInfo").slideDown();
+        display.find("#openMoreInfo").css({visibility: "hidden"});
+      });
+    
+      return display;
+  }
+
+  return {
+    render: render
+  };
+
+}());
+
diff --git a/resources/static/dialog/resources/error-messages.js b/resources/static/dialog/resources/error-messages.js
index 3f6aeefc37aaea3b8209523edef1df81bd182377..0b760950f62b26b666ac3f85e30a0059b514131a 100644
--- a/resources/static/dialog/resources/error-messages.js
+++ b/resources/static/dialog/resources/error-messages.js
@@ -45,10 +45,18 @@ BrowserID.Errors = (function(){
       title: "Adding Address"
     },
 
+    cancelUser: {
+      title: "Cancelling User Account"
+    },
+
     checkAuthentication: {
       title: "Checking Authentication"
     },
 
+    completeUserRegistration: {
+      title: "Completing User Registration"
+    },
+    
     createUser: {
       title: "Creating Account"
     },
@@ -83,14 +91,26 @@ BrowserID.Errors = (function(){
       title: "Resetting Password"
     },
 
+    removeEmail: {
+      title: "Remove Email Address from Account"
+    },
+
     signIn: {
       title: "Signin Failed"
     },
 
+    signUp: {
+      title: "Signup Failed"
+    },
+
     syncAddress: {
       title: "Syncing Address"
     },
 
+    syncEmails: {
+      title: "Syncing Email Addresses"
+    },
+
     xhrError: {
       title: "Communication Error"
     }
diff --git a/resources/static/dialog/resources/tooltip.js b/resources/static/dialog/resources/tooltip.js
index 281e664db3d1c0d4884b71e0614c6d178e53cd13..01a3b34a2b4974f431ce7e14406d2a0d0e47ccbf 100644
--- a/resources/static/dialog/resources/tooltip.js
+++ b/resources/static/dialog/resources/tooltip.js
@@ -45,7 +45,7 @@ BrowserID.Tooltip = (function() {
   function createTooltip(el) {
       var contents = el.html();
       var template = $("#templateTooltip").html();
-      _.templateSettings = {
+      _.templatesettings = {
           interpolate : /\{\{(.+?)\}\}/g
       };
       var tooltip = $(_.template(template, {
diff --git a/resources/static/dialog/test/qunit/pages/forgot_unit_test.js b/resources/static/dialog/test/qunit/pages/forgot_unit_test.js
index ea45fed4411b9c4f4e2bcf001bc2c1724ee4286e..1a4107ade72310030327a456d0428bbaf8feb591 100644
--- a/resources/static/dialog/test/qunit/pages/forgot_unit_test.js
+++ b/resources/static/dialog/test/qunit/pages/forgot_unit_test.js
@@ -58,18 +58,24 @@ steal.plugins("jquery").then("/dialog/test/qunit/mocks/xhr", "/dialog/resources/
     }
   });
 
-  test("requestPasswordReset with invalid email", function() {
-    $("#email").val("invalid");
-
-    xhr.useResult("invalid");
+  function testEmailNotSent(extraTests) {
     bid.forgot.submit();
 
     setTimeout(function() {
       equal($(".emailsent").is(":visible"), false, "email not sent");
-      start();
+      if (extraTests) extraTests();
+      else start();
     }, CHECK_DELAY);
 
     stop();
+  }
+
+  test("requestPasswordReset with invalid email", function() {
+    $("#email").val("invalid");
+
+    xhr.useResult("invalid");
+
+    testEmailNotSent();
   });
 
   test("requestPasswordReset with known email", function() {
@@ -86,44 +92,27 @@ steal.plugins("jquery").then("/dialog/test/qunit/mocks/xhr", "/dialog/resources/
 
   test("requestPasswordReset with unknown email", function() {
     $("#email").val("unregistered@testuser.com");
-    bid.forgot.submit();
-
-    setTimeout(function() {
-      equal($(".emailsent").is(":visible"), false, "email not sent");
-      start();
-    }, CHECK_DELAY);
 
-    stop();
+    testEmailNotSent();
   });
 
   test("requestPasswordReset with throttling", function() {
     xhr.useResult("throttle");
 
     $("#email").val("throttled@testuser.com");
-    bid.forgot.submit();
-
-    setTimeout(function() {
-      equal($(".emailsent").is(":visible"), false, "email not sent");
-      start();
-    }, CHECK_DELAY);
 
-    stop();
+    testEmailNotSent();
   });
 
   test("requestPasswordReset with XHR Error", function() {
     xhr.useResult("ajaxError");
 
     $("#email").val("testuser@testuser.com");
-    bid.forgot.submit();
 
-    setTimeout(function() {
-      equal($(".emailsent").is(":visible"), false, "email not sent");
-      equal($(".doh").is(":visible"), true, "XHR error message is displayed");
+    testEmailNotSent(function() {
+      equal($("#error").is(":visible"), true, "error is visible");  
       start();
-    }, CHECK_DELAY);
-
-    stop();
-
+    });
   });
 
 });
diff --git a/resources/static/dialog/test/qunit/pages/signin_unit_test.js b/resources/static/dialog/test/qunit/pages/signin_unit_test.js
new file mode 100644
index 0000000000000000000000000000000000000000..5bb7a20aded96b4ba8aa8c362c51fdf64a91b8ae
--- /dev/null
+++ b/resources/static/dialog/test/qunit/pages/signin_unit_test.js
@@ -0,0 +1,128 @@
+/*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").then("/dialog/test/qunit/mocks/xhr", "/dialog/resources/network", "/dialog/resources/user", "/js/pages/signin", function() {
+  "use strict";
+
+  var bid = BrowserID,
+      network = bid.Network,
+      user = bid.User,
+      xhr = bid.Mocks.xhr,
+      CHECK_DELAY = 500,
+      docMock = {
+        location: "signin"
+      }
+
+  module("pages/signin", {
+    setup: function() {
+      network.setXHR(xhr);
+      $("#error").stop().hide();
+      xhr.useResult("valid");
+      docMock.location = "signin";
+      bid.signIn({document: docMock});
+    },
+    teardown: function() {
+      network.setXHR($);
+      $("#error").stop().hide();
+      $("#error .message").remove();
+      bid.signIn.reset();
+    }
+  });
+
+  function testUserNotSignedIn(extraTests) {
+    bid.signIn.submit();
+
+    setTimeout(function() {
+      equal(docMock.location, "signin", "user not signed in");
+      if (extraTests) extraTests();
+      else start();
+    }, 100);
+
+    stop();
+  }
+
+  test("signin with valid email and password", function() {
+    $("#email").val("registered@testuser.com");
+    $("#password").val("password");
+
+    bid.signIn.submit();
+
+    setTimeout(function() {
+      equal(docMock.location, "/", "user signed in, page redirected");
+      start();
+    }, 100);
+
+    stop();
+  });
+
+  test("signin with missing email", function() {
+    $("#email").val("");
+    $("#password").val("password");
+
+    testUserNotSignedIn();
+  });
+
+  test("signin with missing password", function() {
+    $("#email").val("registered@testuser.com");
+    $("#password").val("");
+
+    testUserNotSignedIn();
+  });
+
+
+  test("signin with bad username/password", function() {
+    xhr.useResult("invalid");
+    $("#email").val("registered@testuser.com");
+    $("#password").val("password");
+
+    testUserNotSignedIn();
+  });
+
+  test("signin with XHR error", function() {
+    xhr.useResult("ajaxError");
+    $("#email").val("registered@testuser.com");
+    $("#password").val("password");
+
+    testUserNotSignedIn(function() {
+      setTimeout(function() {
+        equal($("#error").is(":visible"), true, "error is visible");  
+        start();
+      }, 500);
+    });
+  });
+
+
+});
diff --git a/resources/static/dialog/test/qunit/pages/signup_unit_test.js b/resources/static/dialog/test/qunit/pages/signup_unit_test.js
new file mode 100644
index 0000000000000000000000000000000000000000..473ab14fa5d96a70d1f0ceac3e20f37240c6379b
--- /dev/null
+++ b/resources/static/dialog/test/qunit/pages/signup_unit_test.js
@@ -0,0 +1,108 @@
+/*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").then("/dialog/test/qunit/mocks/xhr", "/dialog/resources/network", "/dialog/resources/user", "/js/pages/signup", function() {
+  "use strict";
+
+  var bid = BrowserID,
+      network = bid.Network,
+      user = bid.User,
+      xhr = bid.Mocks.xhr,
+      CHECK_DELAY = 500;
+
+  module("pages/signup", {
+    setup: function() {
+      network.setXHR(xhr);
+      $("#error").stop().hide();
+      $(".notification").stop().hide();
+      xhr.useResult("valid");
+      bid.signUp();
+    },
+    teardown: function() {
+      network.setXHR($);
+      $("#error").stop().hide();
+      $(".notification").stop().hide();
+      $("#error .message").remove();
+      bid.signUp.reset();
+    }
+  });
+
+  function testNoticeNotVisible(extraTests) {
+    bid.signUp.submit();
+
+    setTimeout(function() {
+      equal($(".emailsent").is(":visible"), false, "email not sent, notice not visible");
+      if(extraTests) extraTests();
+      else start();
+    }, CHECK_DELAY);
+    stop();
+  }
+
+  test("signup with valid unregistered email", function() {
+    $("#email").val("unregistered@testuser.com");
+
+    bid.signUp.submit();
+
+    setTimeout(function() {
+      equal($(".emailsent").is(":visible"), true, "email sent, notice visible");
+      start();
+    }, CHECK_DELAY);
+    stop();
+  });
+
+  test("signup with valid registered email", function() {
+    $("#email").val("registered@testuser.com");
+
+    testNoticeNotVisible();
+  });
+
+  test("signup with invalid email address", function() {
+    $("#email").val("invalid");
+
+    testNoticeNotVisible();
+  });
+
+  test("signup with invalid XHR error", function() {
+    xhr.useResult("invalid");
+    $("#email").val("unregistered@testuser.com");
+
+    testNoticeNotVisible(function() {
+      equal($("#error").is(":visible"), true, "error message displayed");
+      start();
+    });
+  });
+
+});
diff --git a/resources/static/dialog/test/qunit/pages/verify_email_address_test.js b/resources/static/dialog/test/qunit/pages/verify_email_address_test.js
index 306540fbd8a5ccb37050543520c9a1bc26430049..c4cc27e0c84bd07b1528d393e60a78bf9208990a 100644
--- a/resources/static/dialog/test/qunit/pages/verify_email_address_test.js
+++ b/resources/static/dialog/test/qunit/pages/verify_email_address_test.js
@@ -47,12 +47,12 @@ steal.plugins("jquery").then("/dialog/test/qunit/mocks/xhr", "/dialog/resources/
     setup: function() {
       network.setXHR(xhr);  
       xhr.useResult("valid");
-      $(".error").stop().hide();
+      $("#error,.error").stop().hide();
       $(".website").text("");
     },
     teardown: function() {
       network.setXHR($);  
-      $(".error").stop().hide();
+      $("#error,.error").stop().hide();
       $(".website").text("");
     }
   });
@@ -103,7 +103,7 @@ steal.plugins("jquery").then("/dialog/test/qunit/mocks/xhr", "/dialog/resources/
     bid.verifyEmailAddress("token");
 
     setTimeout(function() {
-      ok($("#cannotcommunicate").is(":visible"), "cannot communicate box is visible");
+      ok($("#error").is(":visible"), "cannot communicate box is visible");
       start();
     }, 500);
     stop();
diff --git a/resources/static/dialog/test/qunit/qunit.js b/resources/static/dialog/test/qunit/qunit.js
index a0eb186337863c4843cd0c773c69f2ab92408082..c0ed533a58d256b870ff5d7ddcb59cd85aa15c8d 100644
--- a/resources/static/dialog/test/qunit/qunit.js
+++ b/resources/static/dialog/test/qunit/qunit.js
@@ -1,6 +1,7 @@
 steal("/dialog/resources/browserid.js",
       "/dialog/resources/browser-support.js",
       "/dialog/resources/error-messages.js",
+      "/dialog/resources/error-display.js",
       "/dialog/resources/storage.js",
       "/dialog/resources/tooltip.js",
       "/dialog/resources/validation.js",
@@ -26,7 +27,10 @@ steal("/dialog/resources/browserid.js",
   .then("pages/add_email_address_test")
   .then("pages/verify_email_address_test")
   .then("pages/forgot_unit_test")
+  .then("pages/signin_unit_test")
+  .then("pages/signup_unit_test")
   .then("resources/tooltip_unit_test")
+  .then("resources/error-display_unit_test")
   .then("resources/channel_unit_test")
   .then("resources/browser-support_unit_test")
   .then("resources/validation_unit_test")
diff --git a/resources/static/dialog/test/qunit/resources/error-display_unit_test.js b/resources/static/dialog/test/qunit/resources/error-display_unit_test.js
new file mode 100644
index 0000000000000000000000000000000000000000..842ba40d82ad0adfa4b01f08baf578427c84dac7
--- /dev/null
+++ b/resources/static/dialog/test/qunit/resources/error-display_unit_test.js
@@ -0,0 +1,60 @@
+/*jshint browser:true, jQuery: true, forin: true, laxbreak:true */                                             
+/*globals BrowserID: true, _: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 bid.
+ *
+ * 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/error-display", function() {
+  "use strict";
+
+  var bid = BrowserID,
+      errorDisplay = bid.ErrorDisplay;
+
+  module("/resources/error-display", {
+    setup: function() {
+    },
+    teardown: function() {
+    }
+  });
+
+  test("can show an error", function() {
+    var target = $("#error .contents");
+    target.empty();
+
+    errorDisplay.render(target, "#templateError", { action: { title: "Error Message" } });
+
+    ok(target.html(), "Error has some contents");
+  });
+
+
+});
diff --git a/resources/static/js/page_helpers.js b/resources/static/js/page_helpers.js
index 1e0bb323885460864dbb5ddd2582ff1504de1ccc..f53a0844ab5ebaaae2582c3604c3488a709430e5 100644
--- a/resources/static/js/page_helpers.js
+++ b/resources/static/js/page_helpers.js
@@ -39,7 +39,8 @@ BrowserID.PageHelpers = (function() {
 
   var win = window,
       locStorage = win.localStorage,
-      bid = BrowserID;
+      bid = BrowserID,
+      errorDisplay = bid.ErrorDisplay;
 
   function setStoredEmail(email) {
     locStorage.signInEmail = email;
@@ -83,11 +84,21 @@ BrowserID.PageHelpers = (function() {
       return decodeURIComponent(results[1].replace(/\+/g, " "));
   }
 
+  function getFailure(error) {
+    return function onFailure(info) {
+      info = $.extend(info, { action: error });
+      errorDisplay.render("#error", "#templateError", info);
+      $("#errorBackground").fadeIn();
+      $("#error").fadeIn();
+    }
+  }
+
   return {
     setupEmail: prefillEmail,
     setStoredEmail: setStoredEmail,
     clearStoredEmail: clearStoredEmail,
     getStoredEmail: getStoredEmail,
-    getParameterByName: getParameterByName
+    getParameterByName: getParameterByName,
+    getFailure: getFailure
   };
 }());
diff --git a/resources/static/js/pages/forgot.js b/resources/static/js/pages/forgot.js
index 32004aef4ed8602904b56fd38a62f18ff2abf114..ab7c8c0adbf5dbb87479e5272b3b9d5ef50b0c09 100644
--- a/resources/static/js/pages/forgot.js
+++ b/resources/static/js/pages/forgot.js
@@ -63,9 +63,7 @@ BrowserID.forgot = (function() {
           var tooltipEl = info.reason === "throttle" ? "#could_not_add" : "#not_registered";
           tooltip.showTooltip(tooltipEl);
         }
-      }, function onFailure() {
-        $(".notifications .notification.doh").fadeIn();
-      });
+      }, pageHelpers.getFailure(bid.Errors.requestPasswordReset));
     }
   };
 
@@ -74,19 +72,17 @@ BrowserID.forgot = (function() {
 
     pageHelpers.setupEmail();
 
-    $("#signUpForm").bind("submit", submit);
+    $("form").bind("submit", submit);
   }
 
   function reset() {
-    $("#signUpForm").unbind("submit", submit);
+    $("form").unbind("submit", submit);
   }
 
+  init.submit = submit; 
+  init.reset = reset;
 
-  var forgot = init;
-  forgot.submit = submit; 
-  forgot.reset = reset;
-
-  return forgot;
+  return init;
 
 }());
 
diff --git a/resources/static/js/pages/manage_account.js b/resources/static/js/pages/manage_account.js
index 4768d8fe9e7df4ed2c53e75449171ecbf73a6b9c..ee8ac18dfbe9b8ccc3b7317ea7802c002356d33f 100644
--- a/resources/static/js/pages/manage_account.js
+++ b/resources/static/js/pages/manage_account.js
@@ -37,7 +37,10 @@
 (function() {
   "use strict";
 
-  var User = BrowserID.User;
+  var bid = BrowserID,
+      User = bid.User,
+      errors = bid.Errors,
+      pageHelpers = bid.PageHelpers;
 
   function relativeDate(date) {
     var diff = (((new Date()).getTime() - date.getTime()) / 1000),
@@ -133,7 +136,7 @@
         $("#vAlign").hide();
         displayEmails(emails);
       }
-    });
+    }, pageHelpers.getFailure(errors.syncEmails));
   }
 
   function onRemoveEmail(email, event) {
@@ -143,14 +146,14 @@
 
     if (_.size(emails) > 1) {
       if (confirm("Remove " + email + " from your BrowserID?")) {
-        User.removeEmail(email, syncAndDisplayEmails);
+        User.removeEmail(email, syncAndDisplayEmails, pageHelpers.getFailure(errors.removeEmail));
       }
     }
     else {
       if (confirm('Removing the last address will cancel your BrowserID account.\nAre you sure you want to continue?')) {
         User.cancelUser(function() {
           document.location="/";
-        });
+        }, pageHelpers.getFailure(errors.cancelUser));
       }
     }
   }
@@ -186,7 +189,7 @@
       if (confirm('Are you sure you want to cancel your BrowserID account?')) {
         User.cancelUser(function() {
           document.location="/";
-        });
+        }, pageHelpers.getFailure(errors.cancelUser));
       }
     });
 
diff --git a/resources/static/js/pages/signin.js b/resources/static/js/pages/signin.js
index 69590b274a4aa2b63db65589d50d8bbd775a6958..bdb1d051a20f9ba9adba649ecd494c726a1755f8 100644
--- a/resources/static/js/pages/signin.js
+++ b/resources/static/js/pages/signin.js
@@ -34,43 +34,56 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
-(function() {
+BrowserID.signIn = (function() {
   "use strict";
 
   var bid = BrowserID,
       user = bid.User,
       pageHelpers = bid.PageHelpers,
-      validation = bid.Validation;
+      validation = bid.Validation,
+      doc = document;
+
+  function submit(event) {
+    if (event) event.preventDefault();
+
+    var email = $("#email").val(),
+        password = $("#password").val();
+
+    var valid = validation.emailAndPassword(email, password);
+
+    if (valid) {
+      user.authenticate(email, password, function onSuccess(authenticated) {
+        if (authenticated) {
+          pageHelpers.clearStoredEmail();
+          doc.location = "/";
+        }
+        else {
+          // bad authentication
+          $(".notifications .notification.badlogin").fadeIn();
+        }
+      }, pageHelpers.getFailure(bid.Errors.authenticate));
+    }
+  }
+
+  function init(options) {
+    if(options && options.document) doc = options.document;
 
-  bid.signIn = function () {
     $("form input[autofocus]").focus();
 
     pageHelpers.setupEmail();
 
-    $("#signUpForm").bind("submit", function(event) {
-      event.preventDefault();
-
-      var email = $("#email").val(),
-          password = $("#password").val();
-
-      var valid = validation.emailAndPassword(email, password);
-
-      if (valid) {
-        user.authenticate(email, password, function onSuccess(authenticated) {
-          if (authenticated) {
-            pageHelpers.clearStoredEmail();
-            document.location = "/";
-          }
-          else {
-            // bad authentication
-            $(".notifications .notification.doh").fadeIn();
-          }
-        }, function onFailure() {
-          // Wah wah.  Network error
-        });
-      }
-    });
-  };
+    $("form").bind("submit", submit);
+  }
+
+  function reset() {
+    $("form").unbind("submit", submit);
+  }
+
+  init.submit = submit;
+  init.reset = reset;
+
+  return init;
+
 }());
 
 
diff --git a/resources/static/js/pages/signup.js b/resources/static/js/pages/signup.js
index 429fbccc66c0bfcb29a5eed5324101cee7ca9dcf..02449bb9fba41957407281621d229f8b67db6936 100644
--- a/resources/static/js/pages/signup.js
+++ b/resources/static/js/pages/signup.js
@@ -34,15 +34,15 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
-(function() {
+BrowserID.signUp = (function() {
   "use strict";
 
   var bid = BrowserID,
       user = bid.User,
       pageHelpers = bid.PageHelpers,
+      errors = bid.Errors,
       ANIMATION_SPEED = 250;
 
-  bid.signUp = function () {
     function replaceWithMessage(selector) {
         $('.forminputs').fadeOut(ANIMATION_SPEED, function() {
           $(selector).fadeIn(ANIMATION_SPEED);
@@ -53,47 +53,51 @@
       $(selector).fadeIn(ANIMATION_SPEED);
     }
 
-    function onFailure() {
-      replaceWithMessage(".doh");
+    function submit(event) { 
+      if (event) event.preventDefault();
+
+      var email = $("#email").val(),
+          valid = bid.Validation.email(email);
+
+      if (!valid) {
+        return;
+      }
+
+      user.isEmailRegistered(email, function(registered) {
+        if (!registered) {
+          pageHelpers.clearStoredEmail();
+          user.createUser(email, function onSuccess(keypair) {
+            $('#sentToEmail').html(email);
+            replaceWithMessage(".emailsent");
+          }, pageHelpers.getFailure(errors.createUser));
+        }
+        else {
+          $('#registeredEmail').html(email);
+          showNotice(".alreadyRegistered");
+        }
+      }, pageHelpers.getFailure(errors.isEmailRegistered));
     }
 
+    function onEmailKeyUp(event) {
+      if (event.which !== 13) $(".notification").fadeOut(ANIMATION_SPEED);
+    }
 
-    $(function () {
+    function init() {
       $("form input[autofocus]").focus();
 
       pageHelpers.setupEmail();
 
-      $("#email").bind("keyup", function(event) {
-        if (event.which !== 13) {
-          $(".notification").fadeOut(ANIMATION_SPEED);
-        }
-      });
-
-      $("#signUpForm").bind("submit", function(event) {
-        event.preventDefault();
-
-        var email = $("#email").val(),
-            valid = bid.Validation.email(email);
+      $("#email").bind("keyup", onEmailKeyUp);
+      $("form").bind("submit", submit);
+    }
 
-        if (!valid) {
-          return;
-        }
+    function reset() {
+      $("form").unbind("submit", submit);
+      $("#email").unbind("keyup", onEmailKeyUp);
+    }
 
-        user.isEmailRegistered(email, function(registered) {
-          if (!registered) {
-            pageHelpers.clearStoredEmail();
-            user.createUser(email, function onSuccess(keypair) {
-              $('#sentToEmail').html(email);
-              replaceWithMessage(".emailsent");
-            }, onFailure);
-          }
-          else {
-            $('#registeredEmail').html(email);
-            showNotice(".alreadyRegistered");
-          }
-        }, onFailure);
-      });
+    init.submit = submit;
+    init.reset = reset;
 
-    });
-  };
+    return init;
 }());
diff --git a/resources/static/js/pages/verify_email_address.js b/resources/static/js/pages/verify_email_address.js
index 5d3002a606d8d05e6fa05e7a7236e47f60c76344..6083e06c271dd4b8d284d1639e910fddefbdfcb4 100644
--- a/resources/static/js/pages/verify_email_address.js
+++ b/resources/static/js/pages/verify_email_address.js
@@ -38,6 +38,8 @@
   "use strict";
 
   var bid = BrowserID,
+      errors = bid.Errors,
+      pageHelpers = bid.PageHelpers,
       token;
 
   function showError(el) {
@@ -62,9 +64,7 @@
         else {
           showError("#cannotcomplete");
         }
-      }, function onFailure() {
-        showError("#cannotcommunicate");
-      });
+      }, pageHelpers.getFailure(errors.completeUserRegistration));
     }
   }
 
@@ -88,9 +88,7 @@
       else {
         showError("#cannotconfirm");
       }
-    }, function() {
-        showError("#cannotcommunicate");
-    });
+    }, pageHelpers.getFailure(errors.completeUserRegistration));
   }
 
   function reset() {
diff --git a/resources/views/forgot.ejs b/resources/views/forgot.ejs
index 763420c42f8d2d04d8ee660550bb85a42fd0a2ab..93c8ed7f4d9b514dba84dac304c5e0f19d827173 100644
--- a/resources/views/forgot.ejs
+++ b/resources/views/forgot.ejs
@@ -4,8 +4,6 @@
         <form id="signUpForm" class="cf authform" novalidate>
             <h1 class="serif">Forgot Password</h1>
             <div class="notifications">
-                <div class="notification error doh">Doh! Something went wrong :-( </div>
-                <div class="notification error mismatchpassword">XXX: Your passwords need to match</div>
                 <div class="notification emailsent">A confirmation email has been sent to you at <strong id="sent_to_email"></strong>. Check it!</div>
             </div>
             <div id="forminputs">
diff --git a/resources/views/layout.ejs b/resources/views/layout.ejs
index c8009278cbd81dbc653f4d5c123d01671a6733a7..02906973378a8d10cf49d9790d68ecf5270f0699 100644
--- a/resources/views/layout.ejs
+++ b/resources/views/layout.ejs
@@ -20,6 +20,8 @@
     <script src="/dialog/resources/underscore-min.js" type="text/javascript"></script>
     <script src="/dialog/resources/browserid-extensions.js" type="text/javascript"></script>
     <script src="/dialog/resources/browserid.js" type="text/javascript"></script>
+    <script src="/dialog/resources/error-display.js" type="text/javascript"></script>
+    <script src="/dialog/resources/error-messages.js" type="text/javascript"></script>
     <script src="/js/page_helpers.js" type="text/javascript"></script>
     <script src="/js/browserid.js" type="text/javascript"></script>
     <script src="/js/pages/index.js" type="text/javascript"></script>
@@ -39,6 +41,8 @@
 </head>
 <body>
 
+<div id="errorBackground"></div>
+
 <div id="wrapper">
 
     <header id="header">
@@ -55,6 +59,8 @@
         </ul>
     </header>
 
+    <div id="error"></div>
+
     <%- body %>
 
     <footer id="footer">
@@ -75,5 +81,52 @@
   </div>
 </script>
 
+<script type="text/html" id="templateError">
+  <div>
+
+  <h2>We are very sorry, there has been an error!</h2>
+
+  <p>
+    To retry, you will have to reload the page and try again.  More info can be found by opening the expanded info below.
+  </p>
+
+  <a href="#" id="openMoreInfo">See more info</a>
+
+  <ul id="moreInfo">
+    {% if (typeof action !== "undefined") { %}
+      <li>
+        <strong id="action">Action: </strong>{{ action.title }}
+
+        {% if (action.message) { %}
+          <p>
+            {{ action.message }}
+          </p>  
+        {% } %}
+      </li>
+    {% } %}
+
+    {% if (typeof network !== "undefined") { %}
+      <li>
+
+        <strong id="network">Network Info:</strong> {{ network.type }}: {{ network.url }}
+
+        <p>
+          <strong>Response Code - </strong> {{ network.textStatus }} 
+        </p>
+
+        {% if (network.errorThrown) { %}
+          <p>
+            <strong>Error Type:</strong> {{ network.errorThrown }}
+          </p>  
+        {% } %}
+
+      </li>
+
+    {% } %}
+
+  </ul>
+  </div>
+</script>
+
 </body>
 </html>
diff --git a/resources/views/signin.ejs b/resources/views/signin.ejs
index b4623d8ebb13156d9855ad81d15b0bbd93a381c0..e914e765370573637a7793ba55bd02462d7a05da 100644
--- a/resources/views/signin.ejs
+++ b/resources/views/signin.ejs
@@ -5,7 +5,7 @@
             <h1 class="serif">Sign In</h1>
 
             <ul class="notifications">
-                <li class="notification error doh">Bad Login. Check your email and password.</li>
+                <li class="notification error badlogin">Bad Login. Check your email and password.</li>
             </ul>
 
             <ul class="inputs">
diff --git a/resources/views/verifyemail.ejs b/resources/views/verifyemail.ejs
index ffaf2540dfe3fb18e1bb66fa1ef96b2ce706c198..68331c637ead2503d470b213aa5bb601cbee9763 100644
--- a/resources/views/verifyemail.ejs
+++ b/resources/views/verifyemail.ejs
@@ -4,7 +4,6 @@
             <h1 class="serif">Email Verification</h1>
 
             <ul class="notifications">
-                <li class="notification error" id="cannotcommunicate">Error comunicating with server.</li>
                 <li class="notification error" id="cannotconfirm">Error encountered while attempting to confirm your address. Have you previously verified this address?</li>
                 <li class="notification error" id="cannotcomplete">Error encountered trying to complete registration.</li>
             </ul>