diff --git a/data/ui/app.briar.gtk.gresource.xml b/data/ui/app.briar.gtk.gresource.xml
index b1c063e693cd555d30cc7b1017e8c5c89841552f..8cd71a21eba4b0f8b5ea6ae09d30a4773a1e2a9d 100644
--- a/data/ui/app.briar.gtk.gresource.xml
+++ b/data/ui/app.briar.gtk.gresource.xml
@@ -6,7 +6,7 @@
     <file compressed="true" preprocess="xml-stripblanks">ui/chat.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/login.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/main.ui</file>
-    <file compressed="true" preprocess="xml-stripblanks">ui/setup.ui</file>
+    <file compressed="true" preprocess="xml-stripblanks">ui/registration.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/toolbar_start.ui</file>
   </gresource>
 </gresources>
diff --git a/data/ui/application.css b/data/ui/application.css
index 6446ebfd427495a708d3740324ba3e383dfcbbe9..430e31831668894f888ddc72520079eb3a4a67dc 100644
--- a/data/ui/application.css
+++ b/data/ui/application.css
@@ -1,3 +1,4 @@
-.test {
-  color: red;
+.error-label {
+  color: @error_color;
 }
+
diff --git a/data/ui/login.ui b/data/ui/login.ui
index 27d9255be5a49d31f7c724a26e969ffc070bc411..c834a5c623ceddc2024dc5a4689327138cda49b3 100644
--- a/data/ui/login.ui
+++ b/data/ui/login.ui
@@ -1,57 +1,139 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.20"/>
-  <object class="GtkGrid" id="login">
-    <property name="visible">True</property>
+  <requires lib="gtk+" version="3.22"/>
+  <object class="GtkStack" id="login_flow_stack">
     <property name="can_focus">False</property>
-    <property name="halign">center</property>
-    <property name="valign">center</property>
-    <property name="margin_left">18</property>
-    <property name="margin_right">18</property>
-    <property name="margin_top">18</property>
-    <property name="margin_bottom">18</property>
+    <property name="hhomogeneous">True</property>
+    <property name="transition_type">GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT</property>
     <child>
-      <object class="GtkGrid" id="password_grid">
+      <object class="GtkGrid" id="login_page">
         <property name="visible">True</property>
         <property name="can_focus">False</property>
-        <property name="row_spacing">6</property>
+        <property name="halign">center</property>
+        <property name="valign">center</property>
         <property name="column_spacing">12</property>
+        <property name="row_spacing">24</property>
         <child>
-          <object class="GtkLabel" id="password_label">
+          <object class="GtkLabel">
             <property name="visible">True</property>
-            <property name="halign">center</property>
+            <property name="use_underline">True</property>
+            <property name="can_focus">False</property>
             <property name="label" translatable="yes">Password</property>
+            <property name="halign">end</property>
+            <property name="wrap">True</property>
+            <property name="wrap_mode">PANGO_WRAP_WORD_CHAR</property>
+            <property name="mnemonic_widget">password_entry</property>
+            <style>
+              <class name="dim-label"/>
+            </style>
           </object>
           <packing>
-            <property name="left_attach">1</property>
-            <property name="top_attach">0</property>
+            <property name="top-attach">1</property>
+            <property name="left-attach">0</property>
           </packing>
         </child>
         <child>
           <object class="GtkEntry" id="password_entry">
             <property name="visible">True</property>
+            <property name="max_width_chars">-1</property>
+            <property name="width_request">232</property>
+            <property name="can_focus">True</property>
             <property name="visibility">False</property>
+            <property name="input_purpose">GTK_INPUT_PURPOSE_PASSWORD</property>
           </object>
           <packing>
-            <property name="left_attach">1</property>
-            <property name="top_attach">1</property>
+            <property name="top-attach">1</property>
+            <property name="left-attach">1</property>
           </packing>
         </child>
         <child>
-          <object class="GtkButton" id="login_button">
+          <object class="GtkLabel" id="error_message">
+            <property name="visible">False</property>
+            <property name="can_focus">False</property>
+            <property name="no_show_all">True</property>
+            <property name="xalign">0</property>
+            <property name="label" translatable="yes">Couldn't log in</property>
+            <property name="wrap">True</property>
+            <property name="wrap_mode">PANGO_WRAP_WORD_CHAR</property>
+            <style>
+              <class name="error-label"/>
+            </style>
+          </object>
+          <packing>
+            <property name="top-attach">2</property>
+            <property name="left-attach">1</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="name">password</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkGrid" id="loading_animation">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="halign">center</property>
+        <property name="valign">center</property>
+        <property name="column_spacing">12</property>
+        <property name="row_spacing">24</property>
+        <child>
+          <object class="GtkSpinner">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="active">True</property>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="name">loading</property>
+      </packing>
+    </child>
+  </object>
+  <object class="GtkStack" id="login_flow_headers">
+    <property name="can_focus">False</property>
+    <property name="hhomogeneous">True</property>
+    <property name="visible_child_name" bind-source="login_flow_stack" bind-property="visible-child-name" bind-flags="sync-create"/>
+    <property name="transition_duration" bind-source="login_flow_stack" bind-property="transition-duration" bind-flags="sync-create"/>
+    <property name="transition_type" bind-source="login_flow_stack" bind-property="transition-type" bind-flags="sync-create"/>
+    <child>
+      <object class="GtkHeaderBar">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="show_close_button">True</property>
+        <property name="title">Briar</property>
+        <child>
+          <object class="GtkButton">
             <property name="visible">True</property>
-            <property name="halign">center</property>
-            <property name="label" translatable="yes" context="login page: button">Sign in</property>
+            <property name="use_underline">True</property>
+            <property name="can_focus">True</property>
+            <property name="label" translatable="yes">Log in</property>
             <signal name="clicked" handler="on_login_pressed"/>
+            <style>
+              <class name="suggested-action"/>
+            </style>
           </object>
           <packing>
-            <property name="left_attach">1</property>
-            <property name="top_attach">2</property>
+            <property name="pack_type">end</property>
           </packing>
         </child>
       </object>
+      <packing>
+        <property name="name">password</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkHeaderBar" id="loading_header">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="show_close_button">True</property>
+        <property name="width_request">360</property>
+        <property name="title" translatable="yes">Logging in</property>
+      </object>
+      <packing>
+        <property name="name">loading</property>
+      </packing>
     </child>
   </object>
 </interface>
 
-
diff --git a/data/ui/registration.ui b/data/ui/registration.ui
new file mode 100644
index 0000000000000000000000000000000000000000..86a02b03b9216f7385550d0a686cb74dabe5ecbe
--- /dev/null
+++ b/data/ui/registration.ui
@@ -0,0 +1,296 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="3.22"/>
+  <object class="GtkStack" id="registration_flow_stack">
+    <property name="can_focus">False</property>
+    <property name="hhomogeneous">True</property>
+    <property name="transition_type">GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT</property>
+    <child>
+      <object class="GtkBox" id="nickname_page">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="halign">center</property>
+        <property name="valign">center</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">18</property>
+        <child>
+          <object class="GtkImage" id="nickname_page_image">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="halign">center</property>
+            <property name="margin_start">18</property>
+            <property name="margin_end">18</property>
+            <property name="margin_top">18</property>
+            <property name="pixel_size">128</property>
+            <property name="icon_name">app.briar.gtk</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkLabel">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="label" translatable="yes">Welcome to Briar</property>
+            <property name="margin_bottom">48</property>
+            <property name="wrap">True</property>
+            <property name="wrap_mode">PANGO_WRAP_WORD_CHAR</property>
+            <attributes>
+              <attribute name="weight" value="ultrabold"/>
+              <attribute name="scale" value="1.7"/>
+            </attributes>
+          </object>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">6</property>
+            <child>
+              <object class="GtkEntry" id="nickname_entry">
+                <property name="visible">True</property>
+                <property name="halign">center</property>
+                <property name="max_width_chars">-1</property>
+                <property name="width_request">300</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkLabel">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">Choose your nickname</property>
+                <property name="wrap">True</property>
+                <property name="wrap_mode">PANGO_WRAP_WORD_CHAR</property>
+                <style>
+                  <class name="dim-label"/>
+                  <class name="small-label"/>
+                </style>
+              </object>
+            </child>
+            <child>
+              <object class="GtkLabel" id="nickname_error_label">
+                <property name="visible">False</property>
+                <property name="can_focus">False</property>
+                <property name="no_show_all">True</property>
+                <property name="label" translatable="yes">The nickname may not be empty.</property>
+                <property name="wrap">True</property>
+                <property name="wrap_mode">PANGO_WRAP_WORD_CHAR</property>
+                <style>
+                  <class name="error-label"/>
+                </style>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="name">nickname</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkGrid" id="passwords_page">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="halign">center</property>
+        <property name="valign">center</property>
+        <property name="column_spacing">12</property>
+        <property name="row_spacing">24</property>
+        <child>
+          <object class="GtkLabel">
+            <property name="visible">True</property>
+            <property name="use_underline">True</property>
+            <property name="can_focus">False</property>
+            <property name="label" translatable="yes">Password</property>
+            <property name="halign">end</property>
+            <property name="wrap">True</property>
+            <property name="wrap_mode">PANGO_WRAP_WORD_CHAR</property>
+            <property name="mnemonic_widget">password_entry</property>
+            <style>
+              <class name="dim-label"/>
+            </style>
+          </object>
+          <packing>
+            <property name="top-attach">1</property>
+            <property name="left-attach">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel">
+            <property name="visible">True</property>
+            <property name="use_underline">True</property>
+            <property name="can_focus">False</property>
+            <property name="label" translatable="yes">Confirm Password</property>
+            <property name="halign">end</property>
+            <property name="wrap">True</property>
+            <property name="wrap_mode">PANGO_WRAP_WORD_CHAR</property>
+            <property name="mnemonic_widget">password_confirm_entry</property>
+            <style>
+              <class name="dim-label"/>
+            </style>
+          </object>
+          <packing>
+            <property name="top-attach">2</property>
+            <property name="left-attach">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkEntry" id="password_entry">
+            <property name="visible">True</property>
+            <property name="max_width_chars">-1</property>
+            <property name="width_request">232</property>
+            <property name="can_focus">True</property>
+            <property name="visibility">False</property>
+            <property name="input_purpose">GTK_INPUT_PURPOSE_PASSWORD</property>
+          </object>
+          <packing>
+            <property name="top-attach">1</property>
+            <property name="left-attach">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkEntry" id="password_confirm_entry">
+            <property name="visible">True</property>
+            <property name="max_width_chars">-1</property>
+            <property name="width_request">232</property>
+            <property name="can_focus">True</property>
+            <property name="visibility">False</property>
+            <property name="input_purpose">GTK_INPUT_PURPOSE_PASSWORD</property>
+          </object>
+          <packing>
+            <property name="top-attach">2</property>
+            <property name="left-attach">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="passwords_error_label">
+            <property name="visible">False</property>
+            <property name="can_focus">False</property>
+            <property name="no_show_all">True</property>
+            <property name="xalign">0</property>
+            <property name="label" translatable="yes">The passwords do not match</property>
+            <property name="wrap">True</property>
+            <property name="wrap_mode">PANGO_WRAP_WORD_CHAR</property>
+            <style>
+              <class name="error-label"/>
+            </style>
+          </object>
+          <packing>
+            <property name="top-attach">5</property>
+            <property name="left-attach">1</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="name">passwords</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkGrid" id="loading_animation">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="halign">center</property>
+        <property name="valign">center</property>
+        <property name="column_spacing">12</property>
+        <property name="row_spacing">24</property>
+        <child>
+          <object class="GtkSpinner">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="active">True</property>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="name">loading</property>
+      </packing>
+    </child>
+  </object>
+  <object class="GtkStack" id="registration_flow_headers">
+    <property name="can_focus">False</property>
+    <property name="hhomogeneous">True</property>
+    <property name="visible_child_name" bind-source="registration_flow_stack" bind-property="visible-child-name" bind-flags="sync-create"/>
+    <property name="transition_duration" bind-source="registration_flow_stack" bind-property="transition-duration" bind-flags="sync-create"/>
+    <property name="transition_type" bind-source="registration_flow_stack" bind-property="transition-type" bind-flags="sync-create"/>
+    <child>
+      <object class="GtkHeaderBar">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="show_close_button">True</property>
+        <property name="width_request">360</property>
+        <property name="title">Briar</property>
+        <child>
+          <object class="GtkButton">
+            <property name="visible">True</property>
+            <property name="use_underline">True</property>
+            <property name="can_focus">True</property>
+            <property name="label" translatable="yes">Next</property>
+            <signal name="clicked" handler="on_nickname_next_pressed"/>
+            <style>
+              <class name="suggested-action"/>
+            </style>
+          </object>
+          <packing>
+            <property name="pack_type">end</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="name">nickname</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkHeaderBar">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="show_close_button">True</property>
+        <property name="title" translatable="yes">Passwords</property>
+        <child>
+          <object class="GtkButton">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <signal name="clicked" handler="on_create_account_back_pressed"/>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">True</property>
+                <property name="icon_name">go-previous-symbolic</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="pack_type">start</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton">
+            <property name="visible">True</property>
+            <property name="use_underline">True</property>
+            <property name="can_focus">True</property>
+            <property name="label" translatable="yes">Create Account</property>
+            <signal name="clicked" handler="on_create_account_pressed"/>
+            <style>
+              <class name="suggested-action"/>
+            </style>
+          </object>
+          <packing>
+            <property name="pack_type">end</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="name">passwords</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkHeaderBar">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="show_close_button">True</property>
+        <property name="width_request">360</property>
+        <property name="title" translatable="yes">Creating Account</property>
+      </object>
+      <packing>
+        <property name="name">loading</property>
+      </packing>
+    </child>
+  </object>
+</interface>
+
diff --git a/data/ui/setup.ui b/data/ui/setup.ui
deleted file mode 100644
index bdb915056120040624d272b7b9ff18ff593503ea..0000000000000000000000000000000000000000
--- a/data/ui/setup.ui
+++ /dev/null
@@ -1,110 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<interface>
-  <requires lib="gtk+" version="3.20"/>
-  <object class="GtkGrid" id="setup">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="halign">center</property>
-    <property name="valign">center</property>
-    <property name="margin_left">18</property>
-    <property name="margin_right">18</property>
-    <property name="margin_top">18</property>
-    <property name="margin_bottom">18</property>
-    <child>
-      <object class="GtkGrid" id="username_grid">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="row_spacing">6</property>
-        <property name="column_spacing">12</property>
-        <child>
-          <object class="GtkLabel" id="username_label">
-            <property name="visible">True</property>
-            <property name="halign">center</property>
-            <property name="label" translatable="yes">Nickname</property>
-          </object>
-          <packing>
-            <property name="left_attach">1</property>
-            <property name="top_attach">0</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkEntry" id="username_entry">
-            <property name="visible">True</property>
-            <property name="placeholder_text" translatable="yes" context="registration page: input field">Choose your nickname</property>
-          </object>
-          <packing>
-            <property name="left_attach">1</property>
-            <property name="top_attach">1</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkButton" id="username_next">
-            <property name="visible">True</property>
-            <property name="halign">center</property>
-            <property name="label" translatable="yes" context="registration page: button – from username to passwords">Next</property>
-            <signal name="clicked" handler="on_username_button_clicked"/>
-          </object>
-          <packing>
-            <property name="left_attach">1</property>
-            <property name="top_attach">2</property>
-          </packing>
-        </child>
-      </object>
-    </child>
-    <child>
-      <object class="GtkGrid" id="password_grid">
-        <property name="visible">False</property>
-        <property name="can_focus">False</property>
-        <property name="row_spacing">6</property>
-        <property name="column_spacing">12</property>
-        <child>
-          <object class="GtkLabel" id="password_label">
-            <property name="visible">True</property>
-            <property name="halign">center</property>
-            <property name="label" translatable="yes">Password</property>
-          </object>
-          <packing>
-            <property name="left_attach">1</property>
-            <property name="top_attach">0</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkEntry" id="password_entry">
-            <property name="visible">True</property>
-            <property name="visibility">False</property>
-            <property name="placeholder_text" translatable="yes" context="registration page: password input field">Choose your password</property>
-          </object>
-          <packing>
-            <property name="left_attach">1</property>
-            <property name="top_attach">1</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkEntry" id="password_confirm_entry">
-            <property name="visible">True</property>
-            <property name="visibility">False</property>
-            <property name="placeholder_text" translatable="yes" context="registration page: password input field">Confirm your password</property>
-          </object>
-          <packing>
-            <property name="left_attach">1</property>
-            <property name="top_attach">2</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkButton" id="password_next">
-            <property name="visible">True</property>
-            <property name="halign">center</property>
-            <property name="label" translatable="yes" context="registration page: button">Create Account</property>
-            <signal name="clicked" handler="on_password_button_clicked"/>
-          </object>
-          <packing>
-            <property name="left_attach">1</property>
-            <property name="top_attach">3</property>
-          </packing>
-        </child>
-      </object>
-    </child>
-  </object>
-</interface>
-
-
diff --git a/src/briar/gtk/containers/login.py b/src/briar/gtk/containers/login.py
new file mode 100644
index 0000000000000000000000000000000000000000..51b900a29c1f0c822bb9da56a1ed2ae126906057
--- /dev/null
+++ b/src/briar/gtk/containers/login.py
@@ -0,0 +1,85 @@
+# Copyright (c) 2019 Nico Alt
+# SPDX-License-Identifier: AGPL-3.0-only
+# License-Filename: LICENSE.md
+
+from gi.repository import GLib
+
+from briar.gtk.container import Container
+from briar.gtk.define import APP
+
+
+class LoginContainer(Container):
+
+    LOGIN_UI = "/app/briar/gtk/ui/login.ui"
+    STACK_NAME = "login_flow_stack"
+    HEADERS_NAME = "login_flow_headers"
+
+    def __init__(self, window):
+        super().__init__()
+        self._window = window
+        self._setup_view()
+
+    def _setup_view(self, ):
+        self.builder.add_from_resource(self.LOGIN_UI)
+        self.builder.connect_signals(self)
+
+        self._setup_login_flow_stack()
+        self._setup_login_flow_headers()
+        self._setup_keystroke_listener()
+
+    def _setup_login_flow_stack(self):
+        self.login_flow_stack = self.builder.get_object(self.STACK_NAME)
+        self.login_flow_stack.show_all()
+        self.add(self.login_flow_stack)
+
+    def _setup_login_flow_headers(self):
+        login_flow_headers = self.builder.get_object(self.HEADERS_NAME)
+        login_flow_headers.show_all()
+        self._window.set_titlebar(login_flow_headers)
+
+    def _setup_keystroke_listener(self):
+        password_entry = self.builder.get_object("password_entry")
+        password_entry.connect("key-press-event",
+                               self._password_keystroke)
+
+    # pylint: disable=unused-argument
+    def _password_keystroke(self, widget, event):
+        if event.hardware_keycode != 36 and event.hardware_keycode != 104:
+            return
+        self.on_login_pressed(None)
+
+    # pylint: disable=unused-argument
+    def on_login_pressed(self, button):
+        self._show_loading_animation()
+        self._login()
+
+    def _show_loading_animation(self):
+        loading_animation = self.builder.get_object("loading_animation")
+        self.login_flow_stack.set_visible_child(loading_animation)
+
+    def _login(self):
+        password = self.builder.get_object("password_entry").get_text()
+        APP().api.login(password, self._login_completed)
+
+    def _login_completed(self, succeeded):
+        function = self._login_failed
+        if succeeded:
+            function = self._window.on_startup_completed
+        GLib.idle_add(function)
+
+    def _login_failed(self):
+        self._show_error_message()
+        self._focus_password_entry()
+        self._show_login_page()
+
+    def _show_error_message(self):
+        error_message = self.builder.get_object("error_message")
+        error_message.show()
+
+    def _focus_password_entry(self):
+        password_entry = self.builder.get_object("password_entry")
+        password_entry.grab_focus()
+
+    def _show_login_page(self):
+        login_page = self.builder.get_object("login_page")
+        self.login_flow_stack.set_visible_child(login_page)
diff --git a/src/briar/gtk/containers/registration.py b/src/briar/gtk/containers/registration.py
new file mode 100644
index 0000000000000000000000000000000000000000..68a50f67e85cc793966f9b53a42fee1918ebe5f9
--- /dev/null
+++ b/src/briar/gtk/containers/registration.py
@@ -0,0 +1,138 @@
+# Copyright (c) 2019 Nico Alt
+# SPDX-License-Identifier: AGPL-3.0-only
+# License-Filename: LICENSE.md
+
+from gettext import gettext as _
+
+from gi.repository import GLib
+
+from briar.gtk.container import Container
+from briar.gtk.define import APP
+
+
+class RegistrationContainer(Container):
+
+    REGISTRATION_UI = "/app/briar/gtk/ui/registration.ui"
+    STACK_NAME = "registration_flow_stack"
+    HEADERS_NAME = "registration_flow_headers"
+
+    def __init__(self, window):
+        super().__init__()
+        self._window = window
+        self._setup_view()
+
+    def _setup_view(self, ):
+        self.builder.add_from_resource(self.REGISTRATION_UI)
+        self.builder.connect_signals(self)
+
+        self._setup_registration_flow_stack()
+        self._setup_registration_flow_headers()
+        self._setup_nickname_keystroke_listener()
+
+    def _setup_registration_flow_stack(self):
+        self.registration_flow_stack = self.builder.get_object(self.STACK_NAME)
+        self.registration_flow_stack.show_all()
+        self.add(self.registration_flow_stack)
+
+    def _setup_registration_flow_headers(self):
+        registration_flow_headers = self.builder.get_object(self.HEADERS_NAME)
+        registration_flow_headers.show_all()
+        self._window.set_titlebar(registration_flow_headers)
+
+    def _setup_nickname_keystroke_listener(self):
+        nickname_entry = self.builder.get_object("nickname_entry")
+        nickname_entry.connect("key-press-event",
+                               self._nickname_keystroke)
+
+    # pylint: disable=unused-argument
+    def _nickname_keystroke(self, widget, event):
+        if event.hardware_keycode != 36 and event.hardware_keycode != 104:
+            return
+        self.on_nickname_next_pressed(None)
+
+    # pylint: disable=unused-argument
+    def on_nickname_next_pressed(self, button):
+        nickname_error_label = self.builder.get_object("nickname_error_label")
+        if self._nickname_is_empty():
+            nickname_error_label.show()
+            return
+        nickname_error_label.hide()
+        self._show_passwords_page()
+
+    def _nickname_is_empty(self):
+        nickname = self.builder.get_object("nickname_entry").get_text()
+        return len(nickname) == 0
+
+    def _show_passwords_page(self):
+        passwords_page = self.builder.get_object("passwords_page")
+        self.registration_flow_stack.set_visible_child(passwords_page)
+
+        self._focus_password_entry()
+        self._setup_passwords_keystroke_listener()
+
+    def _focus_password_entry(self):
+        password_entry = self.builder.get_object("password_entry")
+        password_entry.grab_focus()
+
+    def _setup_passwords_keystroke_listener(self):
+        password_confirm_entry = self.builder.get_object(
+            "password_confirm_entry")
+        password_confirm_entry.connect(
+            "key-press-event", self._passwords_keystroke)
+
+    # pylint: disable=unused-argument
+    def _passwords_keystroke(self, widget, event):
+        if event.hardware_keycode != 36 and event.hardware_keycode != 104:
+            return
+        self.on_create_account_pressed(None)
+
+    # pylint: disable=unused-argument
+    def on_create_account_back_pressed(self, button):
+        self._show_nickname_page()
+
+    def _show_nickname_page(self):
+        nickname_page = self.builder.get_object("nickname_page")
+        self.registration_flow_stack.set_visible_child(nickname_page)
+
+    # pylint: disable=unused-argument
+    def on_create_account_pressed(self, button):
+        passwords_error_label = self.builder.get_object(
+            "passwords_error_label")
+        if not self._passwords_match():
+            passwords_error_label.show()
+            return
+        passwords_error_label.hide()
+        self._show_loading_animation()
+        self._register()
+
+    def _passwords_match(self):
+        password = self.builder.get_object("password_entry").get_text()
+        password_confirm = self.builder.get_object(
+            "password_confirm_entry").get_text()
+        return password == password_confirm
+
+    def _show_loading_animation(self):
+        loading_animation = self.builder.get_object("loading_animation")
+        self.registration_flow_stack.set_visible_child(loading_animation)
+
+    def _register(self):
+        nickname = self.builder.get_object("nickname_entry").get_text()
+        password = self.builder.get_object("password_entry").get_text()
+        APP().api.register((nickname, password),
+                           self._registration_completed)
+
+    def _registration_completed(self, succeeded):
+        function = self._registration_failed
+        if succeeded:
+            function = self._window.on_startup_completed
+        GLib.idle_add(function)
+
+    def _registration_failed(self):
+        self._show_error_message()
+        self._show_passwords_page()
+
+    def _show_error_message(self):
+        passwords_error_label = self.builder.get_object(
+            "passwords_error_label")
+        passwords_error_label.set_label(_("Couldn't register account"))
+        passwords_error_label.show()
diff --git a/src/briar/gtk/containers/startup.py b/src/briar/gtk/containers/startup.py
index ab7c0a90e313f342e37000a7d2fb7b2e898b49bd..c6a1817c75dddedd6aeaa6e84ab2f782ec534f14 100644
--- a/src/briar/gtk/containers/startup.py
+++ b/src/briar/gtk/containers/startup.py
@@ -2,57 +2,22 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 # License-Filename: LICENSE.md
 
-from gi.repository import GLib
-
 from briar.gtk.container import Container
+from briar.gtk.containers.login import LoginContainer
+from briar.gtk.containers.registration import RegistrationContainer
 from briar.gtk.define import APP
 
 
 class StartupContainer(Container):
 
-    SETUP_UI = "/app/briar/gtk/ui/setup.ui"
-    LOGIN_UI = "/app/briar/gtk/ui/login.ui"
-
-    def __init__(self):
+    def __init__(self, window):
         super().__init__()
-        self._api = APP().api
-        self._setup_view()
-
-    # pylint: disable=unused-argument
-    def on_username_button_clicked(self, button):
-        self.builder.get_object("username_grid").set_visible(False)
-        self.builder.get_object("password_grid").set_visible(True)
-        self.username = self.builder.get_object("username_entry").get_text()
-
-    # pylint: disable=unused-argument
-    def on_password_button_clicked(self, button):
-        password = self.builder.get_object("password_entry").get_text()
-        password_confirm = self.builder.get_object(
-            "password_confirm_entry").get_text()
-        if password != password_confirm:
-            raise Exception("Passwords do not match")
-        self._api.register((self.username, password),
-                           StartupContainer._startup_completed)
-
-    # pylint: disable=unused-argument
-    def on_login_pressed(self, button):
-        password = self.builder.get_object("password_entry").get_text()
-        self._api.login(password, StartupContainer._startup_completed)
+        self._setup_view(window)
 
-    def _setup_view(self):
-        self.set_hexpand(True)
-        self.set_vexpand(True)
-        if not APP().api.has_account():
-            self.builder.add_from_resource(self.SETUP_UI)
-            self.add(self.builder.get_object("setup"))
-        else:
-            self.builder.add_from_resource(self.LOGIN_UI)
-            self.add(self.builder.get_object("login"))
-        self.builder.connect_signals(self)
+    def _setup_view(self, window):
+        container = RegistrationContainer(window)
+        if APP().api.has_account():
+            container = LoginContainer(window)
 
-    @staticmethod
-    def _startup_completed(succeeded):
-        if succeeded:
-            GLib.idle_add(APP().window.on_startup_completed)
-            return
-        print("Startup failed")
+        container.show()
+        self.add(container)
diff --git a/src/briar/gtk/window.py b/src/briar/gtk/window.py
index 062e6cc978ee128bdb049a9d379d13c791f57417..dad4ff2b536e785780de915a38093a71b0c7e392 100644
--- a/src/briar/gtk/window.py
+++ b/src/briar/gtk/window.py
@@ -2,7 +2,7 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 # License-Filename: LICENSE.md
 
-from gi.repository import Gtk
+from gi.repository.Gtk import ApplicationWindow, Grid
 
 from briar.gtk.containers.add_contact import AddContactContainer
 from briar.gtk.containers.chat import ChatContainer
@@ -12,7 +12,7 @@ from briar.gtk.define import APP, APPLICATION_ID, APPLICATION_NAME
 from briar.gtk.toolbar import Toolbar
 
 
-class Window(Gtk.ApplicationWindow):
+class Window(ApplicationWindow):
 
     DEFAULT_WINDOW_SIZE = (600, 400)
 
@@ -29,14 +29,12 @@ class Window(Gtk.ApplicationWindow):
         return self._toolbar
 
     def _initialize_gtk_application_window(self):
-        Gtk.ApplicationWindow.__init__(self, application=APP(),
-                                       title=APPLICATION_NAME,
-                                       icon_name=APPLICATION_ID)
+        ApplicationWindow.__init__(self, application=APP(),
+                                   title=APPLICATION_NAME,
+                                   icon_name=APPLICATION_ID)
 
     def _setup_content(self):
         self._setup_size(self.DEFAULT_WINDOW_SIZE)
-        self._setup_toolbar()
-        self._setup_grid()
         self._setup_startup_container()
 
     def _setup_size(self, size):
@@ -49,26 +47,31 @@ class Window(Gtk.ApplicationWindow):
                isinstance(size[0], int) and\
                isinstance(size[1], int)
 
-    def _setup_toolbar(self):
-        self._toolbar = Toolbar()
-        self._toolbar.show()
-        self._toolbar.set_show_close_button(True)
-        self.set_titlebar(self._toolbar)
+    def _setup_startup_container(self):
+        self._container = StartupContainer(self)
+        self._container.show()
+        self.add(self._container)
+
+    def on_startup_completed(self):
+        self._container.destroy()
+        self._setup_grid()
+        self._setup_toolbar()
+        self._setup_main_container()
 
     def _setup_grid(self):
-        self._grid = Gtk.Grid()
+        self._grid = Grid()
         self._grid.show()
         self.add(self._grid)
 
-    def _setup_startup_container(self):
-        self._container = StartupContainer()
-        self._container.show()
-        self._grid.add(self._container)
-
-    def on_startup_completed(self):
+    def _reset_grid(self):
         self._grid.destroy()
         self._setup_grid()
-        self._setup_main_container()
+
+    def _setup_toolbar(self):
+        self._toolbar = Toolbar()
+        self._toolbar.show()
+        self._toolbar.set_show_close_button(True)
+        self.set_titlebar(self._toolbar)
 
     def _setup_main_container(self):
         self._container = MainContainer()
@@ -78,8 +81,7 @@ class Window(Gtk.ApplicationWindow):
 
     # pylint: disable=unused-argument
     def show_add_contact(self, widget):
-        self._grid.destroy()
-        self._setup_grid()
+        self._reset_grid()
         self._setup_add_contact()
 
     def _setup_add_contact(self):
@@ -90,8 +92,7 @@ class Window(Gtk.ApplicationWindow):
         self._toolbar.show_add_contact_button(False)
 
     def open_private_chat(self, contact_id):
-        self._grid.destroy()
-        self._setup_grid()
+        self._reset_grid()
         self._setup_private_chat(contact_id)
 
     def _setup_private_chat(self, contact_id):
@@ -103,7 +104,6 @@ class Window(Gtk.ApplicationWindow):
 
     # pylint: disable=unused-argument
     def back_to_main(self, widget):
-        self._grid.destroy()
-        self._setup_grid()
+        self._reset_grid()
         self._toolbar.show_back_button(False)
         self._setup_main_container()