diff --git a/resources/static/dialog/controllers/page.js b/resources/static/dialog/controllers/page.js
index 7a09282ea9e880f85b1f007cd4da60ed4295615d..b26e4372750edca51dfad49df298b7f9d8b412cc 100644
--- a/resources/static/dialog/controllers/page.js
+++ b/resources/static/dialog/controllers/page.js
@@ -114,20 +114,18 @@ BrowserID.Modules.PageModule = (function() {
     },
 
     renderDialog: function(body, body_vars) {
-      screens.form(body, body_vars);
-      $("#wait, #error").stop().fadeOut(ANIMATION_TIME);
+      screens.wait.hide();
+      screens.error.hide();
+      screens.form.show(body, body_vars);
       dom.focus("input:visible:eq(0)");
     },
 
     renderWait: function(body, body_vars) {
-      screens.wait(body, body_vars);
-      $("body").css('opacity', 1);
-      $("#wait").stop().hide().fadeIn(ANIMATION_TIME);
+      screens.wait.show(body, body_vars);
     },
 
     renderError: function(body, body_vars) {
-      screens.error(body, body_vars);
-      $("#error").stop().css('opacity', 1).hide().fadeIn(ANIMATION_TIME);
+      screens.error.show(body, body_vars);
 
       /**
        * TODO XXX - Use the error-display for this.
diff --git a/resources/static/dialog/css/popup.css b/resources/static/dialog/css/popup.css
index 2a97db5aa07aefb9df052dfd536b6a61fc73e184..32b57adb614702cd07e3e92de4c734a24cbf0d12 100644
--- a/resources/static/dialog/css/popup.css
+++ b/resources/static/dialog/css/popup.css
@@ -50,20 +50,69 @@ section > .contents {
 
 #wait {
     text-align: center;
-    z-index: 1;
     background-image: url("/i/bg.png");
+    z-index: -1;
+    opacity: 0;
+
+    -webkit-transition-property: opacity;
+    -moz-transition-property: opacity;
+    -ms-transition-property: opacity;
+    -o-transition-property: opacity;
+    transition-property: opacity;
+
+    -webkit-transition-duration: 0.25s;
+    -moz-transition-duration: 0.25s;
+    -ms-transition-duration: 0.25s;
+    -o-transition-duration: 0.25s;
+    transition-duration: 0.25s;
+
+    /* Set this to .25s for Android browser, 0.5 seconds makes it so that it
+     * does not show */
+    -webkit-transition-delay: 0.25s;
+    -moz-transition-delay: 0.5s;
+    -ms-transition-delay: 0.5s;
+    -o-transition-delay: 0.5s;
+    transition-delay: 0.5s;
+}
+
+.waiting #wait {
+    z-index: 1;
+    opacity: 1;
 }
 
 #error {
-    position: absolute;
     text-align: center;
+    z-index: -1;
+    background-color: #fff;
     display: none;
+
+    -webkit-transition-property: opacity;
+    -moz-transition-property: opacity;
+    -ms-transition-property: opacity;
+    -o-transition-property: opacity;
+    transition-property: opacity;
+
+    -webkit-transition-duration: 0.25s;
+    -moz-transition-duration: 0.25s;
+    -ms-transition-duration: 0.25s;
+    -o-transition-duration: 0.25s;
+    transition-duration: 0.25s;
+
+    -webkit-transition-delay: 0.25s;
+    -moz-transition-delay: 0.25s;
+    -ms-transition-delay: 0.25s;
+    -o-transition-delay: 0.25s;
+    transition-delay: 0.25s;
+}
+
+.error #error {
+    display: block;
     z-index: 2;
-    background-color: #fff;
+    opacity: 1;
 }
 
 #error ul, #error li {
-    list-style-type: none; 
+    list-style-type: none;
 }
 
 #wait strong, #error strong {
@@ -265,7 +314,7 @@ header {
     font-weight: bold;
     background-color: rgba(0,0,0,0.05);
     border-bottom: 1px solid rgba(0,0,0,0.15);
-    
+
     -webkit-box-shadow: 0 -5px 5px -5px rgba(0, 0, 0, 0.1) inset;
        -moz-box-shadow: 0 -5px 5px -5px rgba(0, 0, 0, 0.1) inset;
          -o-box-shadow: 0 -5px 5px -5px rgba(0, 0, 0, 0.1) inset;
diff --git a/resources/static/dialog/resources/helpers.js b/resources/static/dialog/resources/helpers.js
index 4c991ab7aebbf9b14a1cd9b3c5b56c3f80ee2a82..a986ab8ee0eeaa35ae5aac573ab1b427ec557ad5 100644
--- a/resources/static/dialog/resources/helpers.js
+++ b/resources/static/dialog/resources/helpers.js
@@ -64,8 +64,11 @@
 
   function getAssertion(email, callback) {
     var self=this;
+    var wait = bid.Screens.wait;
+    wait.show("wait", bid.Wait.generateKey);
     user.getAssertion(email, function(assert) {
       assert = assert || null;
+      wait.hide();
       animateClose(function() {
         self.close("assertion_generated", {
           assertion: assert
diff --git a/resources/static/pages/page_helpers.js b/resources/static/pages/page_helpers.js
index 619ef7e6f095b90561b097035f1577fdb3c8b913..c18b2817c54e3c336841f8bde097c31995ea95e2 100644
--- a/resources/static/pages/page_helpers.js
+++ b/resources/static/pages/page_helpers.js
@@ -90,7 +90,7 @@ BrowserID.PageHelpers = (function() {
   function getFailure(error, callback) {
     return function onFailure(info) {
       info = $.extend(info, { action: error, dialog: false });
-      bid.Screens.error("error", info);
+      bid.Screens.error.show("error", info);
       $("#errorBackground").stop().fadeIn();
       $("#error").stop().fadeIn();
 
diff --git a/resources/static/shared/screens.js b/resources/static/shared/screens.js
index 97338199fdcf84ec5a516ee01aec9e8f41519bb7..3ebf72b69a3bcda8b78d1e4230d4e3008b0d2d08 100644
--- a/resources/static/shared/screens.js
+++ b/resources/static/shared/screens.js
@@ -3,38 +3,28 @@ BrowserID.Screens = (function() {
 
   var bid = BrowserID,
       dom = BrowserID.DOM,
-      renderer = bid.Renderer;
+      renderer = bid.Renderer,
+      BODY = "body";
 
-  function render(target, body, vars) {
-    renderer.render(target + " .contents", body, vars);
-  }
-
-  function form(template, vars) {
-    render("#formWrap", template, vars);
-    dom.removeClass("body", "error");
-    dom.removeClass("body", "waiting");
-    dom.addClass("body", "form");
-  }
+  function Screen(target, className) {
+    return {
+      show: function(template, vars) {
+        renderer.render(target + " .contents", template, vars);
+        dom.addClass(BODY, className);
+        this.visible = true;
+      },
 
-  function wait(template, vars) {
-    render("#wait", template, vars);
-    dom.removeClass("body", "error");
-    dom.removeClass("body", "form");
-    dom.addClass("body", "waiting");
+      hide: function() {
+        dom.removeClass(BODY, className);
+        this.visible = false;
+      }
+    }
   }
 
-  function error(template, vars) {
-    render("#error", template, vars);
-    dom.removeClass("body", "waiting");
-    dom.removeClass("body", "form");
-    dom.addClass("body", "error");
-
-    bid.ErrorDisplay.start("#error");
-  }
 
   return {
-    form: form,
-    wait: wait,
-    error: error
+    form: new Screen("#formWrap", "form"),
+    wait: new Screen("#wait", "waiting"),
+    error: new Screen("#error", "error")
   };
 }());
diff --git a/resources/static/test/index.html b/resources/static/test/index.html
index b56abd10db2ebbdb3342b1341e51f790069f29ca..9be2191ec3936350a3ec0f7936b5478119b01e2e 100644
--- a/resources/static/test/index.html
+++ b/resources/static/test/index.html
@@ -93,6 +93,7 @@
     <script type="text/javascript" src="/shared/mediator.js"></script>
     <script type="text/javascript" src="/shared/screens.js"></script>
     <script type="text/javascript" src="/shared/browser-support.js"></script>
+    <script type="text/javascript" src="/shared/wait-messages.js"></script>
     <script type="text/javascript" src="/shared/error-messages.js"></script>
     <script type="text/javascript" src="/shared/error-display.js"></script>
     <script type="text/javascript" src="/shared/storage.js"></script>
diff --git a/resources/static/test/qunit/controllers/checkregistration_unit_test.js b/resources/static/test/qunit/controllers/checkregistration_unit_test.js
index 6fa6920cfa331ea4f8961b8c421de016a9137116..4ee5076b6db9974db90a3fc3c37536552112b83b 100644
--- a/resources/static/test/qunit/controllers/checkregistration_unit_test.js
+++ b/resources/static/test/qunit/controllers/checkregistration_unit_test.js
@@ -41,7 +41,8 @@
       bid = BrowserID,
       xhr = bid.Mocks.xhr,
       network = bid.Network,
-      register = bid.TestHelpers.register;
+      testHelpers = bid.TestHelpers,
+      register = testHelpers.register;
 
   function createController(verifier, message) {
     controller = bid.Modules.CheckRegistration.create();
@@ -54,11 +55,11 @@
 
   module("controllers/checkregistration_controller", {
     setup: function() {
-      bid.TestHelpers.setup();
+      testHelpers.setup();
     },
 
     teardown: function() {
-      bid.TestHelpers.teardown();
+      testHelpers.teardown();
       if (controller) {
         try {
           // Controller may have already destroyed itself.
@@ -102,7 +103,7 @@
     controller.startCheck();
 
     setTimeout(function() {
-      ok($("#error").is(":visible"), "Error message is visible");
+      ok(testHelpers.errorVisible(), "Error message is visible");
       start();
     }, 500);
   });
diff --git a/resources/static/test/qunit/controllers/required_email_unit_test.js b/resources/static/test/qunit/controllers/required_email_unit_test.js
index 8ffc24adda46615584cf4e1a9fde664dc12a7b62..43fa76ffbc96a70b7be482eeb6d0b55d04215f1b 100644
--- a/resources/static/test/qunit/controllers/required_email_unit_test.js
+++ b/resources/static/test/qunit/controllers/required_email_unit_test.js
@@ -42,12 +42,13 @@
       bid = BrowserID,
       xhr = bid.Mocks.xhr,
       user = bid.User,
-      register = bid.TestHelpers.register;
+      testHelpers = bid.TestHelpers,
+      register = testHelpers.register;
 
   module("controllers/required_email", {
     setup: function() {
       el = $("body");
-      bid.TestHelpers.setup();
+      testHelpers.setup();
       xhr.setContextInfo({
         authenticated: false
       });
@@ -63,7 +64,7 @@
         }
         controller = null;
       }
-      bid.TestHelpers.setup();
+      testHelpers.setup();
     }
   });
 
@@ -134,7 +135,7 @@
     });
 
     setTimeout(function() {
-      ok($("#error").is(":visible"), "Error message is visible");
+      ok(testHelpers.errorVisible(), "Error message is visible");
       start();
     }, 500);
   });
diff --git a/resources/static/test/qunit/pages/manage_account_unit_test.js b/resources/static/test/qunit/pages/manage_account_unit_test.js
index 8893a8588264e3b89f8b6d27f543178097772c98..ef3871ea46b675db170f38cfc13a420c225e8d3b 100644
--- a/resources/static/test/qunit/pages/manage_account_unit_test.js
+++ b/resources/static/test/qunit/pages/manage_account_unit_test.js
@@ -38,10 +38,10 @@
   "use strict";
 
   var bid = BrowserID,
-      network = bid.Network,
-      storage = bid.Storage,
       user = bid.User,
       xhr = bid.Mocks.xhr,
+      errorScreen = bid.Screens.error,
+      testHelpers = bid.TestHelpers,
       validToken = true,
       TEST_ORIGIN = "http://browserid.org",
       mocks = {
@@ -51,20 +51,14 @@
 
   module("pages/manage_account", {
     setup: function() {
-      network.setXHR(xhr);
-      xhr.useResult("valid");
+      testHelpers.setup();
       user.setOrigin(TEST_ORIGIN);
       $("#emailList").empty();
-      $(".error").removeClass("error");
-      $("#error").hide();
       mocks.document.location = "";
-      storage.clear();
     },
     teardown: function() {
-      network.setXHR($);
       $("#emailList").empty();
-      $(".error").removeClass("error");
-      $("#error").hide();
+      testHelpers.teardown();
     }
   });
 
@@ -88,7 +82,7 @@
     xhr.useResult("ajaxError");
 
     bid.manageAccount(mocks, function() {
-      equal($("#error").is(":visible"), true, "error message is visible on XHR error");
+      equal(testHelpers.errorVisible(), true, "error message is visible on XHR error");
       start();
     });
   });
@@ -114,7 +108,7 @@
     bid.manageAccount(mocks, function() {
       xhr.useResult("ajaxError");
       bid.manageAccount.removeEmail("testuser@testuser.com", function() {
-        equal($("#error").is(":visible"), true, "error message is visible on XHR error");
+        equal(testHelpers.errorVisible(), true, "error message is visible on XHR error");
         start();
       });
     });
@@ -136,7 +130,7 @@
       xhr.useResult("ajaxError");
 
       bid.manageAccount.removeEmail("testuser@testuser.com", function() {
-        equal($("#error").is(":visible"), true, "error message is visible on XHR error");
+        equal(testHelpers.errorVisible(), true, "error message is visible on XHR error");
         start();
       });
     });
@@ -155,7 +149,7 @@
     bid.manageAccount(mocks, function() {
       xhr.useResult("ajaxError");
       bid.manageAccount.cancelAccount(function() {
-        equal($("#error").is(":visible"), true, "error message is visible on XHR error");
+        equal(testHelpers.errorVisible(), true, "error message is visible on XHR error");
         start();
       });
     });
diff --git a/resources/static/test/qunit/shared/screens_unit_test.js b/resources/static/test/qunit/shared/screens_unit_test.js
index e82bc0dfd9099831da8918a41085b4627ce78613..499d457bda805fe3db8a0b0d7dbb6c0a1ca0c1b8 100644
--- a/resources/static/test/qunit/shared/screens_unit_test.js
+++ b/resources/static/test/qunit/shared/screens_unit_test.js
@@ -56,41 +56,50 @@
   test("form", function() {
     el = $("#formWrap .contents");
     el.empty();
-    screens.form("testBodyTemplate");
+    screens.form.show("testBodyTemplate");
 
     ok($("#templateInput").length, "the template has been written");
-    equal($("body").hasClass("error"), false, "error class taken off of body");
-    equal($("body").hasClass("waiting"), false, "waiting class taken off of body");
     equal($("body").hasClass("form"), true, "form class added to body");
+    equal(screens.form.visible, true, "screen is visible");
+
+    screens.form.hide();
+    equal($("body").hasClass("form"), false, "form class removed from body");
+    equal(screens.form.visible, false, "screen is not visible");
   });
 
   test("wait", function() {
     var el = $("#wait .contents");
     el.empty();
-    screens.wait("testBodyTemplate");
+    screens.wait.show("testBodyTemplate");
 
     ok($("#templateInput").length, "the template has been written");
-    equal($("body").hasClass("error"), false, "error class taken off of body");
-    equal($("body").hasClass("form"), false, "form class taken off of body");
     equal($("body").hasClass("waiting"), true, "waiting class added to body");
+    equal(screens.wait.visible, true, "screen is visible");
+
+    screens.wait.hide();
+    equal($("body").hasClass("waiting"), false, "waiting class removed from body");
+    equal(screens.wait.visible, false, "screen is not visible");
   });
 
   test("error", function() {
     var el = $("#error .contents");
     el.empty();
-    screens.error("testBodyTemplate");
+    screens.error.show("testBodyTemplate");
 
     ok($("#templateInput").length, "the template has been written");
-    equal($("body").hasClass("waiting"), false, "waiting class taken off of body");
-    equal($("body").hasClass("form"), false, "form class taken off of body");
     equal($("body").hasClass("error"), true, "error class added to body");
+    equal(screens.error.visible, true, "screen is visible");
+
+    screens.error.hide();
+    equal($("body").hasClass("error"), false, "error class removed from body");
+    equal(screens.error.visible, false, "screen is not visible");
   });
 
   test("XHR 503 (server unavailable) error", function() {
     var el = $("#error .contents");
     el.empty();
 
-    screens.error("error", {
+    screens.error.show("error", {
       network: {
         status: 503
       }
diff --git a/resources/static/test/qunit/testHelpers/helpers.js b/resources/static/test/qunit/testHelpers/helpers.js
index 0e76179687875a04e4c794cb2ede4357cdea6066..a55d948047a8ce038d30367496123d694c9a6cca 100644
--- a/resources/static/test/qunit/testHelpers/helpers.js
+++ b/resources/static/test/qunit/testHelpers/helpers.js
@@ -4,6 +4,7 @@
       network = bid.Network,
       storage = bid.Storage,
       xhr = bid.Mocks.xhr,
+      screens = bid.Screens,
       registrations = [];
       calls = {};
 
@@ -40,6 +41,8 @@
 
       unregisterAll();
       mediator.reset();
+      screens.wait.hide();
+      screens.error.hide();
     },
 
     teardown: function() {
@@ -48,8 +51,13 @@
       network.setXHR($);
       storage.clear();
       $("#error").html("<div class='contents'></div>").hide();
+      screens.wait.hide();
+      screens.error.hide();
     },
 
-    register: register
+    register: register,
+    errorVisible: function() {
+      return screens.error.visible;
+    }
   };
 }());