diff --git a/resources/qml/MatrixTextField.qml b/resources/qml/MatrixTextField.qml
index af077124d357fa622f17807f0316a1b00b64b60e..655d53f16ecdce901c5232c5e069c02b5808b46c 100644
--- a/resources/qml/MatrixTextField.qml
+++ b/resources/qml/MatrixTextField.qml
@@ -8,29 +8,131 @@ import QtQuick.Controls 2.12
 import QtQuick.Layouts 1.12
 import im.nheko 1.0
 
-TextField {
-    id: input
 
-    property alias backgroundColor: backgroundRect.color
+ColumnLayout {
+    id: c
+    property color backgroundColor: Nheko.colors.base
+    property alias color: labelC.color
+    property alias textPadding: input.padding
+    property alias text: input.text
+    property alias label: labelC.text
+    property alias placeholderText: input.placeholderText
+    property alias font: input.font
+    property alias echoMode: input.echoMode
+    property alias selectByMouse: input.selectByMouse
+
+    signal textEdited
+    signal accepted
+    signal editingFinished
+
+    function forceActiveFocus() {
+        input.forceActiveFocus();
+    }
+
+    ToolTip.delay: Nheko.tooltipDelay
+    ToolTip.visible: hover.hovered
+
+    spacing: 0
+
+    Item {
+        Layout.fillWidth: true
+        Layout.preferredHeight: labelC.contentHeight
+        Layout.margins: input.padding
+        Layout.bottomMargin: Nheko.paddingSmall
+        visible: labelC.text
+
+        z: 1
+
+        Label {
+            id: labelC
+
+            y: contentHeight + input.padding + Nheko.paddingSmall
+            enabled: false
+
+            palette: Nheko.colors
+            color: Nheko.colors.text
+            font.pixelSize: input.font.pixelSize
+            font.weight: Font.DemiBold
+            font.letterSpacing: input.font.pixelSize * 0.02
+            width: parent.width
+
+            state: labelC.text && (input.activeFocus == true || input.text) ? "focused" : ""
+
+            states: State {
+                name: "focused"
+
+                PropertyChanges {
+                    target: labelC
+                    y: 0
+                }
+
+                PropertyChanges {
+                    target: input
+                    opacity: 1
+                }
+
+            }
+
+            transitions: Transition {
+                from: ""
+                to: "focused"
+                reversible: true
+
+                NumberAnimation {
+                    target: labelC
+                    properties: "y"
+                    duration: 210
+                    easing.type: Easing.InCubic
+                    alwaysRunToEnd: true
+                }
+
+                NumberAnimation {
+                    target: input
+                    properties: "opacity"
+                    duration: 210
+                    easing.type: Easing.InCubic
+                    alwaysRunToEnd: true
+                }
+
+            }
+        }
+    }
 
-    palette: Nheko.colors
-    color: Nheko.colors.text
+    TextField {
+        id: input
+        Layout.fillWidth: true
+
+        palette: Nheko.colors
+        color: labelC.color
+        opacity: labelC.text ? 0 : 1
+
+        onTextEdited: c.textEdited()
+        onAccepted: c.accepted()
+        onEditingFinished: c.editingFinished()
+
+
+        background: Rectangle {
+            id: backgroundRect
+
+            color: labelC.text ? "transparent" : backgroundColor
+        }
+
+    }
 
     Rectangle {
         id: blueBar
 
-        anchors.top: parent.bottom
-        anchors.horizontalCenter: parent.horizontalCenter
+        Layout.fillWidth: true
+
         color: Nheko.colors.highlight
         height: 1
-        width: parent.width
 
         Rectangle {
             id: blackBar
 
-            anchors.verticalCenter: blueBar.verticalCenter
+            anchors.top: parent.top
             anchors.horizontalCenter: parent.horizontalCenter
-            height: parent.height + 1
+            height: parent.height*2
             width: 0
             color: Nheko.colors.text
 
@@ -50,11 +152,12 @@ TextField {
                 to: "focused"
                 reversible: true
 
+
                 NumberAnimation {
                     target: blackBar
                     properties: "width"
-                    duration: 500
-                    easing.type: Easing.InOutQuad
+                    duration: 310
+                    easing.type: Easing.InCubic
                     alwaysRunToEnd: true
                 }
 
@@ -64,10 +167,8 @@ TextField {
 
     }
 
-    background: Rectangle {
-        id: backgroundRect
-
-        color: Nheko.colors.base
+    HoverHandler {
+        id: hover
+        enabled: c.ToolTip.text
     }
-
 }
diff --git a/resources/qml/QuickSwitcher.qml b/resources/qml/QuickSwitcher.qml
index 174951a0e86c2a8dd9b4d7064f47c1c3877500cf..6d217c723951df4a7bc27e7b48db7d25f168fa16 100644
--- a/resources/qml/QuickSwitcher.qml
+++ b/resources/qml/QuickSwitcher.qml
@@ -11,7 +11,6 @@ Popup {
     id: quickSwitcher
 
     property int textHeight: Math.round(Qt.application.font.pixelSize * 2.4)
-    property int textMargin: Math.round(textHeight / 8)
 
     background: null
     width: Math.round(parent.width / 2)
@@ -34,7 +33,6 @@ Popup {
 
         anchors.fill: parent
         font.pixelSize: Math.ceil(quickSwitcher.textHeight * 0.6)
-        padding: textMargin
         color: Nheko.colors.text
         onTextEdited: {
             completerPopup.completer.searchString = text;
@@ -60,7 +58,7 @@ Popup {
         id: completerPopup
 
         x: roomTextInput.x
-        y: roomTextInput.y + quickSwitcher.textHeight + quickSwitcher.textMargin
+        y: roomTextInput.y + roomTextInput.height
         visible: roomTextInput.length > 0
         width: parent.width
         completerName: "room"
diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml
index 004169e1282198c8775911309ae0a29ad1d1422d..f3976cc0b325b1a6d432b21ba954c85eabbd02ac 100644
--- a/resources/qml/Root.qml
+++ b/resources/qml/Root.qml
@@ -155,6 +155,11 @@ Pane {
 
     }
 
+    Shortcut {
+        sequence: StandardKey.Quit
+        onActivated: Qt.quit()
+    }
+
     Shortcut {
         sequence: "Ctrl+K"
         onActivated: {
@@ -366,8 +371,13 @@ Pane {
         id: mainWindow
 
         anchors.fill: parent
-        initialItem: WelcomePage {
-            //anchors.fill: parent
+        initialItem: welcomePage
+    }
+
+    Component {
+        id: welcomePage
+
+        WelcomePage {
         }
     }
 
@@ -378,10 +388,19 @@ Pane {
         }
     }
 
+    Component {
+        id: loginPage
+
+        LoginPage {
+        }
+    }
+
     Connections {
         function onSwitchToChatPage() {
-            console.log("AAAA");
-            mainWindow.replace(chatPage);
+            mainWindow.replace(null, chatPage);
+        }
+        function onSwitchToLoginPage(error) {
+            mainWindow.replace(welcomePage, {}, loginPage, {"error": error}, StackView.PopTransition);
         }
         target: MainWindow
     }
diff --git a/resources/qml/dialogs/ImagePackEditorDialog.qml b/resources/qml/dialogs/ImagePackEditorDialog.qml
index 9c46a4908a0f63fe3b053a5fca5fef44236b2bd9..eb420fced5c423d5811091620ec775707864f2cd 100644
--- a/resources/qml/dialogs/ImagePackEditorDialog.qml
+++ b/resources/qml/dialogs/ImagePackEditorDialog.qml
@@ -173,39 +173,37 @@ ApplicationWindow {
                         }
                     }
 
-                    MatrixText {
-                        visible: imagePack.roomid
-                        text: qsTr("State key")
-                    }
-
                     MatrixTextField {
+                        id: statekeyField
+
                         visible: imagePack.roomid
                         Layout.fillWidth: true
+                        Layout.columnSpan: 2
+                        label: qsTr("State key")
                         text: imagePack.statekey
                         onTextEdited: imagePack.statekey = text
                     }
 
-                    MatrixText {
-                        text: qsTr("Packname")
-                    }
-
                     MatrixTextField {
                         Layout.fillWidth: true
+                        Layout.columnSpan: 2
+                        label: qsTr("Packname")
                         text: imagePack.packname
                         onTextEdited: imagePack.packname = text
                     }
 
-                    MatrixText {
-                        text: qsTr("Attribution")
-                    }
-
                     MatrixTextField {
                         Layout.fillWidth: true
+                        Layout.columnSpan: 2
+                        label: qsTr("Attribution")
                         text: imagePack.attribution
                         onTextEdited: imagePack.attribution = text
                     }
 
                     MatrixText {
+                        Layout.margins: statekeyField.textPadding
+                        font.weight: Font.DemiBold
+                        font.letterSpacing: font.pixelSize * 0.02
                         text: qsTr("Use as Emoji")
                     }
 
@@ -216,6 +214,9 @@ ApplicationWindow {
                     }
 
                     MatrixText {
+                        Layout.margins: statekeyField.textPadding
+                        font.weight: Font.DemiBold
+                        font.letterSpacing: font.pixelSize * 0.02
                         text: qsTr("Use as Sticker")
                     }
 
@@ -251,27 +252,28 @@ ApplicationWindow {
                         Layout.alignment: Qt.AlignHCenter
                     }
 
-                    MatrixText {
-                        text: qsTr("Shortcode")
-                    }
-
                     MatrixTextField {
                         Layout.fillWidth: true
+                        Layout.columnSpan: 2
+                        label: qsTr("Shortcode")
                         text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode)
                         onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.ShortCode)
                     }
 
-                    MatrixText {
-                        text: qsTr("Body")
-                    }
-
                     MatrixTextField {
+                        id: bodyField
+
                         Layout.fillWidth: true
+                        Layout.columnSpan: 2
+                        label: qsTr("Body")
                         text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Body)
                         onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.Body)
                     }
 
                     MatrixText {
+                        Layout.margins: bodyField.textPadding
+                        font.weight: Font.DemiBold
+                        font.letterSpacing: font.pixelSize * 0.02
                         text: qsTr("Use as Emoji")
                     }
 
@@ -282,6 +284,9 @@ ApplicationWindow {
                     }
 
                     MatrixText {
+                        Layout.margins: bodyField.textPadding
+                        font.weight: Font.DemiBold
+                        font.letterSpacing: font.pixelSize * 0.02
                         text: qsTr("Use as Sticker")
                     }
 
@@ -292,6 +297,9 @@ ApplicationWindow {
                     }
 
                     MatrixText {
+                        Layout.margins: bodyField.textPadding
+                        font.weight: Font.DemiBold
+                        font.letterSpacing: font.pixelSize * 0.02
                         text: qsTr("Remove from pack")
                     }
 
diff --git a/resources/qml/dialogs/RoomDirectory.qml b/resources/qml/dialogs/RoomDirectory.qml
index 99da63bb7592c3ba587ed4ab46738c5095bf8323..36c29a0b6cf1720319c57198e94db8a266cf396d 100644
--- a/resources/qml/dialogs/RoomDirectory.qml
+++ b/resources/qml/dialogs/RoomDirectory.qml
@@ -188,7 +188,6 @@ ApplicationWindow {
             Layout.fillWidth: true
             selectByMouse: true
             font.pixelSize: fontMetrics.font.pixelSize
-            padding: Nheko.paddingMedium
             color: Nheko.colors.text
             placeholderText: qsTr("Search for public rooms")
             onTextChanged: searchTimer.restart()
@@ -199,7 +198,6 @@ ApplicationWindow {
 
             Layout.minimumWidth: 0.3 * header.width
             Layout.maximumWidth: 0.3 * header.width
-            padding: Nheko.paddingMedium
             color: Nheko.colors.text
             placeholderText: qsTr("Choose custom homeserver")
             onTextChanged: publicRooms.setMatrixServer(text)
diff --git a/resources/qml/pages/LoginPage.qml b/resources/qml/pages/LoginPage.qml
new file mode 100644
index 0000000000000000000000000000000000000000..0beb2bdc03d11c8cec518aa57fdd81910fb5cd1d
--- /dev/null
+++ b/resources/qml/pages/LoginPage.qml
@@ -0,0 +1,178 @@
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.2
+import QtQuick.Window 2.15
+import im.nheko 1.0
+import "../components/"
+import "../ui/"
+import "../"
+
+Item {
+    id: loginPage
+    property int maxExpansion: 800
+
+    property string error: login.error
+
+    Login {
+        id: login
+    }
+
+    ScrollView {
+        id: scroll
+
+        clip: false
+        palette: Nheko.colors
+        ScrollBar.horizontal.visible: false
+        anchors.left: parent.left
+        anchors.right: parent.right
+        anchors.verticalCenter: parent.verticalCenter
+        height: Math.min(loginPage.height, col.implicitHeight)
+        anchors.margins: Nheko.paddingLarge
+
+        contentWidth: availableWidth
+
+        ColumnLayout {
+            id: col
+
+            spacing: Nheko.paddingMedium
+
+            anchors.horizontalCenter: parent.horizontalCenter
+            width: Math.min(loginPage.maxExpansion, scroll.width- Nheko.paddingLarge*2)
+
+            Image {
+                Layout.alignment: Qt.AlignHCenter
+                source: "qrc:/logos/login.png"
+                height: 128
+                width: 128
+            }
+
+            RowLayout {
+                spacing: Nheko.paddingLarge
+
+                Layout.fillWidth: true
+                MatrixTextField {
+                    id: matrixIdLabel
+                    label: qsTr("Matrix ID")
+                    placeholderText: qsTr("e.g @joe:matrix.org")
+                    onEditingFinished: login.mxid = text
+
+                    ToolTip.text: qsTr("Your login name. A mxid should start with @ followed by the user id. After the user id you need to include your server name after a :.\nYou can also put your homeserver address there, if your server doesn't support .well-known lookup.\nExample: @user:server.my\nIf Nheko fails to discover your homeserver, it will show you a field to enter the server manually.")
+                    Keys.forwardTo: [pwBtn, ssoBtn]
+                }
+
+
+                Spinner {
+                    height: matrixIdLabel.height/2
+                    Layout.alignment: Qt.AlignBottom
+
+                    visible: running
+                    running: login.lookingUpHs
+                    foreground: Nheko.colors.mid
+                }
+            }
+
+            MatrixText {
+                textFormat: Text.PlainText
+                color: Nheko.theme.error
+                text: login.mxidError
+                visible: text
+            }
+
+            MatrixTextField {
+                id: passwordLabel
+                Layout.fillWidth: true
+                label: qsTr("Password")
+                echoMode: TextInput.Password
+                ToolTip.text: qsTr("Your password.")
+                visible: login.passwordSupported
+                Keys.forwardTo: [pwBtn, ssoBtn]
+            }
+
+            MatrixTextField {
+                id: deviceNameLabel
+                Layout.fillWidth: true
+                label: qsTr("Device name")
+                placeholderText: login.initialDeviceName()
+                ToolTip.text: qsTr("A name for this device, which will be shown to others, when verifying your devices. If none is provided a default is used.")
+                Keys.forwardTo: [pwBtn, ssoBtn]
+            }
+
+            MatrixTextField {
+                id: hsLabel
+                enabled: visible
+                visible: login.homeserverNeeded
+
+                Layout.fillWidth: true
+                label: qsTr("Homeserver address")
+                placeholderText: qsTr("server.my:8787")
+                text: login.homeserver
+                onEditingFinished: login.homeserver = text
+                ToolTip.text: qsTr("The address that can be used to contact you homeservers client API.\nExample: https://server.my:8787")
+                Keys.forwardTo: [pwBtn, ssoBtn]
+            }
+
+            Item {
+                height: Nheko.avatarSize
+                Layout.fillWidth: true
+
+                Spinner {
+                    height: parent.height
+                    anchors.centerIn: parent
+
+                    visible: running
+                    running: login.loggingIn
+                    foreground: Nheko.colors.mid
+                }
+            }
+
+            MatrixText {
+                textFormat: Text.PlainText
+                color: Nheko.theme.error
+                text: loginPage.error
+                visible: text
+            }
+
+            FlatButton {
+                id: pwBtn
+                visible: login.passwordSupported
+                enabled: login.homeserverValid && matrixIdLabel.text == login.mxid && login.homeserver == hsLabel.text
+                Layout.alignment: Qt.AlignHCenter
+                text: qsTr("LOGIN")
+                function pwLogin() {
+                    login.onLoginButtonClicked(Login.Password, matrixIdLabel.text, passwordLabel.text, deviceNameLabel.text)
+                }
+                onClicked: pwBtn.pwLogin()
+                Keys.onEnterPressed: pwBtn.pwLogin()
+                Keys.onReturnPressed: pwBtn.pwLogin()
+                Keys.enabled: pwBtn.enabled && login.passwordSupported
+            }
+            FlatButton {
+                id: ssoBtn
+                visible: login.ssoSupported
+                enabled: login.homeserverValid && matrixIdLabel.text == login.mxid && login.homeserver == hsLabel.text
+                Layout.alignment: Qt.AlignHCenter
+                text: qsTr("SSO LOGIN")
+                function ssoLogin() {
+                    login.onLoginButtonClicked(Login.SSO, matrixIdLabel.text, passwordLabel.text, deviceNameLabel.text)
+                }
+                onClicked: ssoBtn.ssoLogin()
+                Keys.onEnterPressed: ssoBtn.ssoLogin()
+                Keys.onReturnPressed: ssoBtn.ssoLogin()
+                Keys.enabled: ssoBtn.enabled && !login.passwordSupported
+            }
+
+        }
+    }
+
+    ImageButton {
+        anchors.top: parent.top
+        anchors.left: parent.left
+        anchors.margins: Nheko.paddingMedium
+        width: Nheko.avatarSize
+        height: Nheko.avatarSize
+        image: ":/icons/icons/ui/angle-arrow-left.svg"
+        ToolTip.visible: hovered
+        ToolTip.text: qsTr("Back")
+        onClicked: mainWindow.pop()
+    }
+}
diff --git a/resources/qml/pages/WelcomePage.qml b/resources/qml/pages/WelcomePage.qml
index d95b6104652807ed293c6cbe7a0b60393489658b..43050d8efd454b9063b92195ed1e5e85840d2550 100644
--- a/resources/qml/pages/WelcomePage.qml
+++ b/resources/qml/pages/WelcomePage.qml
@@ -6,15 +6,6 @@ import im.nheko 1.0
 import "../components/"
 
 ColumnLayout {
-    FontMetrics {
-        id: fontMetrics
-    }
-
-    Shortcut {
-        sequence: StandardKey.Quit
-        onActivated: Qt.quit()
-    }
-
     Item {
         Layout.fillHeight: true
     }
@@ -49,27 +40,28 @@ ColumnLayout {
     }
 
     RowLayout {
-    Item {
-        Layout.fillWidth: true
-    }
-    FlatButton {
-        Layout.margins: Nheko.paddingLarge
-        Layout.alignment: Qt.AlignHCenter
-        text: qsTr("REGISTER")
-        onClicked: {
+        Item {
+            Layout.fillWidth: true
         }
-    }
-    FlatButton {
-        Layout.margins: Nheko.paddingLarge
-        Layout.alignment: Qt.AlignHCenter
-        text: qsTr("LOGIN")
-        onClicked: {
+        FlatButton {
+            Layout.margins: Nheko.paddingLarge
+            Layout.alignment: Qt.AlignHCenter
+            text: qsTr("REGISTER")
+            onClicked: {
+            }
+        }
+        FlatButton {
+            Layout.margins: Nheko.paddingLarge
+            Layout.alignment: Qt.AlignHCenter
+            text: qsTr("LOGIN")
+            onClicked: {
+                mainWindow.push(loginPage);
+            }
+        }
+        Item {
+            Layout.fillWidth: true
         }
     }
-    Item {
-        Layout.fillWidth: true
-    }
-}
     Item {
         Layout.fillHeight: true
     }
diff --git a/resources/res.qrc b/resources/res.qrc
index 0222619b60aaba723af485c65c5edf5a3be85206..efc0e74a764d5b02133ae6cad3fdc33388a3d000 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -111,6 +111,7 @@
         <file>qml/NotificationWarning.qml</file>
         <file>qml/pages/UserSettingsPage.qml</file>
         <file>qml/pages/WelcomePage.qml</file>
+        <file>qml/pages/LoginPage.qml</file>
         <file>qml/components/AdaptiveLayout.qml</file>
         <file>qml/components/AdaptiveLayoutElement.qml</file>
         <file>qml/components/AvatarListTile.qml</file>
diff --git a/src/LoginPage.cpp b/src/LoginPage.cpp
index 4c28f3645309164467a5901e43999f6d1097ae9b..cfc600ae702859f0c650370a6a8d1daccee28328 100644
--- a/src/LoginPage.cpp
+++ b/src/LoginPage.cpp
@@ -5,11 +5,6 @@
 // SPDX-License-Identifier: GPL-3.0-or-later
 
 #include <QDesktopServices>
-#include <QFontMetrics>
-#include <QLabel>
-#include <QPainter>
-#include <QStyleOption>
-#include <QtMath>
 
 #include <mtx/identifiers.hpp>
 #include <mtx/requests.hpp>
@@ -18,247 +13,94 @@
 #include "Config.h"
 #include "Logging.h"
 #include "LoginPage.h"
+#include "MainWindow.h"
 #include "MatrixClient.h"
 #include "SSOHandler.h"
 #include "UserSettingsPage.h"
-#include "ui/FlatButton.h"
-#include "ui/LoadingIndicator.h"
-#include "ui/OverlayModal.h"
-#include "ui/RaisedButton.h"
-#include "ui/TextField.h"
 
 Q_DECLARE_METATYPE(LoginPage::LoginMethod)
 
 using namespace mtx::identifiers;
 
-LoginPage::LoginPage(QWidget *parent)
-  : QWidget(parent)
+LoginPage::LoginPage(QObject *parent)
+  : QObject(parent)
   , inferredServerAddress_()
 {
-    qRegisterMetaType<LoginPage::LoginMethod>("LoginPage::LoginMethod");
-
-    top_layout_ = new QVBoxLayout();
-
-    top_bar_layout_ = new QHBoxLayout();
-    top_bar_layout_->setSpacing(0);
-    top_bar_layout_->setContentsMargins(0, 0, 0, 0);
-
-    back_button_ = new FlatButton(this);
-    back_button_->setMinimumSize(QSize(30, 30));
-
-    top_bar_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter);
-    top_bar_layout_->addStretch(1);
-
-    QIcon icon;
-    icon.addFile(QStringLiteral(":/icons/icons/ui/angle-arrow-left.svg"));
-
-    back_button_->setIcon(icon);
-    back_button_->setIconSize(QSize(32, 32));
-
-    QIcon logo;
-    logo.addFile(QStringLiteral(":/logos/login.png"));
-
-    logo_ = new QLabel(this);
-    logo_->setPixmap(logo.pixmap(128));
-
-    logo_layout_ = new QHBoxLayout();
-    logo_layout_->setContentsMargins(0, 0, 0, 20);
-    logo_layout_->addWidget(logo_, 0, Qt::AlignHCenter);
-
-    form_wrapper_ = new QHBoxLayout();
-    form_widget_  = new QWidget();
-    form_widget_->setMinimumSize(QSize(350, 200));
-
-    form_layout_ = new QVBoxLayout();
-    form_layout_->setSpacing(20);
-    form_layout_->setContentsMargins(0, 0, 0, 30);
-    form_widget_->setLayout(form_layout_);
-
-    form_wrapper_->addStretch(1);
-    form_wrapper_->addWidget(form_widget_);
-    form_wrapper_->addStretch(1);
-
-    matrixid_input_ = new TextField(this);
-    matrixid_input_->setLabel(tr("Matrix ID"));
-    matrixid_input_->setRegexp(QRegularExpression(QStringLiteral("@.+?:.{3,}")));
-    matrixid_input_->setPlaceholderText(tr("e.g @joe:matrix.org"));
-    matrixid_input_->setToolTip(
-      tr("Your login name. A mxid should start with @ followed by the user id. After the user "
-         "id you need to include your server name after a :.\nYou can also put your homeserver "
-         "address there, if your server doesn't support .well-known lookup.\nExample: "
-         "@user:server.my\nIf Nheko fails to discover your homeserver, it will show you a "
-         "field to enter the server manually."));
-
-    spinner_ = new LoadingIndicator(this);
-    spinner_->setFixedHeight(40);
-    spinner_->setFixedWidth(40);
-    spinner_->hide();
-
-    errorIcon_ = new QLabel(this);
-    errorIcon_->setPixmap(QPixmap(QStringLiteral(":/icons/icons/error.png")));
-    errorIcon_->hide();
-
-    matrixidLayout_ = new QHBoxLayout();
-    matrixidLayout_->addWidget(matrixid_input_, 0, Qt::AlignVCenter);
-
-    QFont font;
-
-    error_matrixid_label_ = new QLabel(this);
-    error_matrixid_label_->setFont(font);
-    error_matrixid_label_->setWordWrap(true);
-
-    password_input_ = new TextField(this);
-    password_input_->setLabel(tr("Password"));
-    password_input_->setEchoMode(QLineEdit::Password);
-    password_input_->setToolTip(tr("Your password."));
-
-    deviceName_ = new TextField(this);
-    deviceName_->setLabel(tr("Device name"));
-    deviceName_->setToolTip(
-      tr("A name for this device, which will be shown to others, when verifying your devices. "
-         "If none is provided a default is used."));
-
-    serverInput_ = new TextField(this);
-    serverInput_->setLabel(tr("Homeserver address"));
-    serverInput_->setPlaceholderText(tr("server.my:8787"));
-    serverInput_->setToolTip(tr("The address that can be used to contact you homeservers "
-                                "client API.\nExample: https://server.my:8787"));
-    serverInput_->hide();
-
-    serverLayout_ = new QHBoxLayout();
-    serverLayout_->addWidget(serverInput_, 0, Qt::AlignVCenter);
-
-    form_layout_->addLayout(matrixidLayout_);
-    form_layout_->addWidget(error_matrixid_label_, 0, Qt::AlignHCenter);
-    form_layout_->addWidget(password_input_);
-    form_layout_->addWidget(deviceName_, Qt::AlignHCenter);
-    form_layout_->addLayout(serverLayout_);
-
-    error_matrixid_label_->hide();
-
-    button_layout_ = new QHBoxLayout();
-    button_layout_->setSpacing(20);
-    button_layout_->setContentsMargins(0, 0, 0, 30);
-
-    login_button_ = new RaisedButton(tr("LOGIN"), this);
-    login_button_->setMinimumSize(150, 65);
-    login_button_->setFontSize(20);
-    login_button_->setCornerRadius(3);
-
-    sso_login_button_ = new RaisedButton(tr("SSO LOGIN"), this);
-    sso_login_button_->setMinimumSize(150, 65);
-    sso_login_button_->setFontSize(20);
-    sso_login_button_->setCornerRadius(3);
-    sso_login_button_->setVisible(false);
-
-    button_layout_->addStretch(1);
-    button_layout_->addWidget(login_button_);
-    button_layout_->addWidget(sso_login_button_);
-    button_layout_->addStretch(1);
-
-    error_label_ = new QLabel(this);
-    error_label_->setFont(font);
-    error_label_->setWordWrap(true);
-
-    top_layout_->addLayout(top_bar_layout_);
-    top_layout_->addStretch(1);
-    top_layout_->addLayout(logo_layout_);
-    top_layout_->addLayout(form_wrapper_);
-    top_layout_->addStretch(1);
-    top_layout_->addLayout(button_layout_);
-    top_layout_->addWidget(error_label_, 0, Qt::AlignHCenter);
-    top_layout_->addStretch(1);
-
-    setLayout(top_layout_);
+    [[maybe_unused]] static auto ignored =
+      qRegisterMetaType<LoginPage::LoginMethod>("LoginPage::LoginMethod");
 
     connect(this, &LoginPage::versionOkCb, this, &LoginPage::versionOk, Qt::QueuedConnection);
     connect(this, &LoginPage::versionErrorCb, this, &LoginPage::versionError, Qt::QueuedConnection);
-
-    connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked()));
-    connect(login_button_, &RaisedButton::clicked, this, [this]() {
-        onLoginButtonClicked(passwordSupported ? LoginMethod::Password : LoginMethod::SSO);
-    });
-    connect(sso_login_button_, &RaisedButton::clicked, this, [this]() {
-        onLoginButtonClicked(LoginMethod::SSO);
-    });
-    connect(this,
-            &LoginPage::showErrorMessage,
-            this,
-            static_cast<void (LoginPage::*)(QLabel *, const QString &)>(&LoginPage::showError),
-            Qt::QueuedConnection);
-    connect(matrixid_input_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
-    connect(password_input_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
-    connect(deviceName_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
-    connect(serverInput_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
-    connect(matrixid_input_, SIGNAL(editingFinished()), this, SLOT(onMatrixIdEntered()));
-    connect(serverInput_, SIGNAL(editingFinished()), this, SLOT(onServerAddressEntered()));
+    connect(
+      this,
+      &LoginPage::loginOk,
+      this,
+      [this](const mtx::responses::Login &res) {
+          loggingIn_ = false;
+          emit loggingInChanged();
+
+          http::client()->set_user(res.user_id);
+          MainWindow::instance()->showChatPage();
+      },
+      Qt::QueuedConnection);
 }
 void
 LoginPage::showError(const QString &msg)
 {
-    auto rect  = QFontMetrics(font()).boundingRect(msg);
-    int width  = rect.width();
-    int height = rect.height();
-    error_label_->setFixedHeight((int)qCeil(width / 200.0) * height);
-    error_label_->setText(msg);
+    loggingIn_ = false;
+    emit loggingInChanged();
+
+    error_ = msg;
+    emit errorOccurred();
 }
 
 void
-LoginPage::showError(QLabel *label, const QString &msg)
+LoginPage::setHomeserver(QString hs)
 {
-    auto rect  = QFontMetrics(font()).boundingRect(msg);
-    int width  = rect.width();
-    int height = rect.height();
-    label->setFixedHeight((int)qCeil(width / 200.0) * height);
-    label->setText(msg);
+    if (hs != homeserver_) {
+        homeserver_      = hs;
+        homeserverValid_ = false;
+        emit homeserverChanged();
+        http::client()->set_server(hs.toStdString());
+        checkHomeserverVersion();
+    }
 }
 
 void
 LoginPage::onMatrixIdEntered()
 {
-    error_label_->setText(QLatin1String(""));
+    clearErrors();
 
-    User user;
-
-    if (!matrixid_input_->isValid()) {
-        error_matrixid_label_->show();
-        showError(error_matrixid_label_,
-                  tr("You have entered an invalid Matrix ID  e.g @joe:matrix.org"));
-        return;
-    } else {
-        error_matrixid_label_->setText(QLatin1String(""));
-        error_matrixid_label_->hide();
-    }
+    homeserverValid_ = false;
+    emit homeserverChanged();
 
+    User user;
     try {
-        user = parse<User>(matrixid_input_->text().toStdString());
+        user = parse<User>(mxid_.toStdString());
     } catch (const std::exception &) {
-        showError(error_matrixid_label_,
-                  tr("You have entered an invalid Matrix ID  e.g @joe:matrix.org"));
+        mxidError_ = tr("You have entered an invalid Matrix ID  e.g @joe:matrix.org");
+        emit mxidErrorChanged();
         return;
     }
 
-    QString homeServer = QString::fromStdString(user.hostname());
-    if (homeServer != inferredServerAddress_) {
-        serverInput_->hide();
-        serverLayout_->removeWidget(errorIcon_);
-        errorIcon_->hide();
-        if (serverInput_->isVisible()) {
-            matrixidLayout_->removeWidget(spinner_);
-            serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight);
-            spinner_->start();
-        } else {
-            serverLayout_->removeWidget(spinner_);
-            matrixidLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight);
-            spinner_->start();
-        }
+    if (user.hostname().empty() || user.localpart().empty()) {
+        mxidError_ = tr("You have entered an invalid Matrix ID  e.g @joe:matrix.org");
+        emit mxidErrorChanged();
+        return;
+    } else {
+        nhlog::net()->debug("hostname: {}", user.hostname());
+    }
 
-        inferredServerAddress_ = homeServer;
-        serverInput_->setText(homeServer);
+    if (user.hostname() != inferredServerAddress_.toStdString()) {
+        homeserverNeeded_ = false;
+        lookingUpHs_      = true;
+        emit lookingUpHsChanged();
 
         http::client()->set_server(user.hostname());
         http::client()->verify_certificates(
           !UserSettings::instance()->disableCertificateValidation());
+        homeserver_ = QString::fromStdString(user.hostname());
+        emit homeserverChanged();
 
         http::client()->well_known(
           [this](const mtx::responses::WellKnown &res, mtx::http::RequestErr err) {
@@ -286,6 +128,7 @@ LoginPage::onMatrixIdEntered()
 
               nhlog::net()->info("Autodiscovery: Discovered '" + res.homeserver.base_url + "'");
               http::client()->set_server(res.homeserver.base_url);
+              emit homeserverChanged();
               checkHomeserverVersion();
           });
     }
@@ -294,6 +137,16 @@ LoginPage::onMatrixIdEntered()
 void
 LoginPage::checkHomeserverVersion()
 {
+    clearErrors();
+
+    try {
+        User user = parse<User>(mxid_.toStdString());
+    } catch (const std::exception &) {
+        mxidError_ = tr("You have entered an invalid Matrix ID  e.g @joe:matrix.org");
+        emit mxidErrorChanged();
+        return;
+    }
+
     http::client()->versions([this](const mtx::responses::Versions &, mtx::http::RequestErr err) {
         if (err) {
             if (err->status_code == 404) {
@@ -332,93 +185,64 @@ LoginPage::checkHomeserverVersion()
     });
 }
 
-void
-LoginPage::onServerAddressEntered()
-{
-    error_label_->setText(QLatin1String(""));
-    http::client()->verify_certificates(!UserSettings::instance()->disableCertificateValidation());
-    http::client()->set_server(serverInput_->text().toStdString());
-    checkHomeserverVersion();
-
-    serverLayout_->removeWidget(errorIcon_);
-    errorIcon_->hide();
-    serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight);
-    spinner_->start();
-}
-
 void
 LoginPage::versionError(const QString &error)
 {
-    showError(error_label_, error);
-    serverInput_->show();
-
-    spinner_->stop();
-    serverLayout_->removeWidget(spinner_);
-    serverLayout_->addWidget(errorIcon_, 0, Qt::AlignVCenter | Qt::AlignRight);
-    errorIcon_->show();
-    matrixidLayout_->removeWidget(spinner_);
+    showError(error);
+
+    homeserverNeeded_ = true;
+    lookingUpHs_      = false;
+    homeserverValid_  = false;
+    emit lookingUpHsChanged();
+    emit versionLookedUp();
 }
 
 void
-LoginPage::versionOk(bool passwordSupported_, bool ssoSupported_)
+LoginPage::versionOk(bool passwordSupported, bool ssoSupported)
 {
-    passwordSupported = passwordSupported_;
-    ssoSupported      = ssoSupported_;
-
-    serverLayout_->removeWidget(spinner_);
-    matrixidLayout_->removeWidget(spinner_);
-    spinner_->stop();
-
-    password_input_->setVisible(passwordSupported);
-    password_input_->setEnabled(passwordSupported);
-    sso_login_button_->setVisible(ssoSupported);
-    login_button_->setVisible(passwordSupported);
-
-    if (serverInput_->isVisible())
-        serverInput_->hide();
+    passwordSupported_ = passwordSupported;
+    ssoSupported_      = ssoSupported;
+
+    lookingUpHs_     = false;
+    homeserverValid_ = true;
+    emit homeserverChanged();
+    emit lookingUpHsChanged();
+    emit versionLookedUp();
 }
 
 void
-LoginPage::onLoginButtonClicked(LoginMethod loginMethod)
+LoginPage::onLoginButtonClicked(LoginMethod loginMethod,
+                                QString userid,
+                                QString password,
+                                QString deviceName)
 {
-    error_label_->setText(QLatin1String(""));
-    User user;
+    clearErrors();
 
-    if (!matrixid_input_->isValid()) {
-        error_matrixid_label_->show();
-        showError(error_matrixid_label_,
-                  tr("You have entered an invalid Matrix ID  e.g @joe:matrix.org"));
-        return;
-    } else {
-        error_matrixid_label_->setText(QLatin1String(""));
-        error_matrixid_label_->hide();
-    }
+    User user;
 
     try {
-        user = parse<User>(matrixid_input_->text().toStdString());
+        user = parse<User>(userid.toStdString());
     } catch (const std::exception &) {
-        showError(error_matrixid_label_,
-                  tr("You have entered an invalid Matrix ID  e.g @joe:matrix.org"));
+        mxidError_ = tr("You have entered an invalid Matrix ID  e.g @joe:matrix.org");
+        emit mxidErrorChanged();
         return;
     }
 
     if (loginMethod == LoginMethod::Password) {
-        if (password_input_->text().isEmpty())
-            return showError(error_label_, tr("Empty password"));
+        if (password.isEmpty())
+            return showError(tr("Empty password"));
 
         http::client()->login(
           user.localpart(),
-          password_input_->text().toStdString(),
-          deviceName_->text().trimmed().isEmpty() ? initialDeviceName()
-                                                  : deviceName_->text().toStdString(),
+          password.toStdString(),
+          deviceName.trimmed().isEmpty() ? initialDeviceName_() : deviceName.toStdString(),
           [this](const mtx::responses::Login &res, mtx::http::RequestErr err) {
               if (err) {
                   auto error = err->matrix_error.error;
                   if (error.empty())
                       error = err->parse_error;
 
-                  showErrorMessage(error_label_, QString::fromStdString(error));
-                  emit errorOccurred();
+                  showError(QString::fromStdString(error));
                   return;
               }
 
@@ -432,34 +256,33 @@ LoginPage::onLoginButtonClicked(LoginMethod loginMethod)
           });
     } else {
         auto sso = new SSOHandler();
-        connect(sso, &SSOHandler::ssoSuccess, this, [this, sso](std::string token) {
-            mtx::requests::Login req{};
-            req.token     = token;
-            req.type      = mtx::user_interactive::auth_types::token;
-            req.device_id = deviceName_->text().trimmed().isEmpty()
-                              ? initialDeviceName()
-                              : deviceName_->text().toStdString();
-            http::client()->login(
-              req, [this](const mtx::responses::Login &res, mtx::http::RequestErr err) {
-                  if (err) {
-                      showErrorMessage(error_label_,
-                                       QString::fromStdString(err->matrix_error.error));
-                      emit errorOccurred();
-                      return;
-                  }
-
-                  if (res.well_known) {
-                      http::client()->set_server(res.well_known->homeserver.base_url);
-                      nhlog::net()->info("Login requested to user server: " +
-                                         res.well_known->homeserver.base_url);
-                  }
-
-                  emit loginOk(res);
-              });
-            sso->deleteLater();
-        });
+        connect(
+          sso, &SSOHandler::ssoSuccess, this, [this, sso, userid, deviceName](std::string token) {
+              mtx::requests::Login req{};
+              req.token = token;
+              req.type  = mtx::user_interactive::auth_types::token;
+              req.device_id =
+                deviceName.trimmed().isEmpty() ? initialDeviceName_() : deviceName.toStdString();
+              http::client()->login(
+                req, [this](const mtx::responses::Login &res, mtx::http::RequestErr err) {
+                    if (err) {
+                        showError(QString::fromStdString(err->matrix_error.error));
+                        emit errorOccurred();
+                        return;
+                    }
+
+                    if (res.well_known) {
+                        http::client()->set_server(res.well_known->homeserver.base_url);
+                        nhlog::net()->info("Login requested to user server: " +
+                                           res.well_known->homeserver.base_url);
+                    }
+
+                    emit loginOk(res);
+                });
+              sso->deleteLater();
+          });
         connect(sso, &SSOHandler::ssoFailed, this, [this, sso]() {
-            showErrorMessage(error_label_, tr("SSO login failed"));
+            showError(tr("SSO login failed"));
             emit errorOccurred();
             sso->deleteLater();
         });
@@ -468,37 +291,6 @@ LoginPage::onLoginButtonClicked(LoginMethod loginMethod)
           QString::fromStdString(http::client()->login_sso_redirect(sso->url())));
     }
 
-    emit loggingIn();
-}
-
-void
-LoginPage::reset()
-{
-    matrixid_input_->clear();
-    password_input_->clear();
-    password_input_->show();
-    serverInput_->clear();
-
-    spinner_->stop();
-    errorIcon_->hide();
-    serverLayout_->removeWidget(spinner_);
-    serverLayout_->removeWidget(errorIcon_);
-    matrixidLayout_->removeWidget(spinner_);
-
-    inferredServerAddress_.clear();
-}
-
-void
-LoginPage::onBackButtonClicked()
-{
-    emit backButtonClicked();
-}
-
-void
-LoginPage::paintEvent(QPaintEvent *)
-{
-    QStyleOption opt;
-    opt.initFrom(this);
-    QPainter p(this);
-    style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
+    loggingIn_ = true;
+    emit loggingInChanged();
 }
diff --git a/src/LoginPage.h b/src/LoginPage.h
index fbfd87103dd6babdcb3e372f467a0b92444dc128..a613bb48decb70b7b3795f6a336bd75bcbf1a510 100644
--- a/src/LoginPage.h
+++ b/src/LoginPage.h
@@ -6,16 +6,7 @@
 
 #pragma once
 
-#include <QWidget>
-
-class FlatButton;
-class LoadingIndicator;
-class OverlayModal;
-class RaisedButton;
-class TextField;
-class QLabel;
-class QVBoxLayout;
-class QHBoxLayout;
+#include <QObject>
 
 namespace mtx {
 namespace responses {
@@ -23,24 +14,61 @@ struct Login;
 }
 }
 
-class LoginPage : public QWidget
+class LoginPage : public QObject
 {
     Q_OBJECT
 
+    Q_PROPERTY(QString mxid READ mxid WRITE setMxid NOTIFY matrixIdChanged)
+    Q_PROPERTY(QString homeserver READ homeserver WRITE setHomeserver NOTIFY homeserverChanged)
+
+    Q_PROPERTY(QString mxidError READ mxidError NOTIFY mxidErrorChanged)
+    Q_PROPERTY(QString error READ error NOTIFY errorOccurred)
+    Q_PROPERTY(bool lookingUpHs READ lookingUpHs NOTIFY lookingUpHsChanged)
+    Q_PROPERTY(bool homeserverValid READ homeserverValid NOTIFY lookingUpHsChanged)
+    Q_PROPERTY(bool loggingIn READ loggingIn NOTIFY loggingInChanged)
+    Q_PROPERTY(bool passwordSupported READ passwordSupported NOTIFY versionLookedUp)
+    Q_PROPERTY(bool ssoSupported READ ssoSupported NOTIFY versionLookedUp)
+    Q_PROPERTY(bool homeserverNeeded READ homeserverNeeded NOTIFY versionLookedUp)
+
 public:
     enum class LoginMethod
     {
         Password,
         SSO,
     };
+    Q_ENUM(LoginMethod)
+
+    LoginPage(QObject *parent = nullptr);
+
+    Q_INVOKABLE QString initialDeviceName() const
+    {
+        return QString::fromStdString(initialDeviceName_());
+    }
 
-    LoginPage(QWidget *parent = nullptr);
+    bool lookingUpHs() const { return lookingUpHs_; }
+    bool loggingIn() const { return loggingIn_; }
+    bool passwordSupported() const { return passwordSupported_; }
+    bool ssoSupported() const { return ssoSupported_; }
+    bool homeserverNeeded() const { return homeserverNeeded_; }
+    bool homeserverValid() const { return homeserverValid_; }
 
-    void reset();
+    QString homeserver() { return homeserver_; }
+    QString mxid() { return mxid_; }
 
+    QString error() { return error_; }
+    QString mxidError() { return mxidError_; }
+
+    void setHomeserver(QString hs);
+    void setMxid(QString id)
+    {
+        if (id != mxid_) {
+            mxid_ = id;
+            emit matrixIdChanged();
+            onMatrixIdEntered();
+        }
+    }
 signals:
-    void backButtonClicked();
-    void loggingIn();
+    void loggingInChanged();
     void errorOccurred();
 
     //! Used to trigger the corresponding slot outside of the main thread.
@@ -48,28 +76,26 @@ signals:
     void versionOkCb(bool passwordSupported, bool ssoSupported);
 
     void loginOk(const mtx::responses::Login &res);
-    void showErrorMessage(QLabel *label, const QString &msg);
 
-protected:
-    void paintEvent(QPaintEvent *event) override;
+    void onServerAddressEntered();
+
+    void matrixIdChanged();
+    void homeserverChanged();
+
+    void mxidErrorChanged();
+    void lookingUpHsChanged();
+    void versionLookedUp();
+    void versionLookupFinished();
 
 public slots:
     // Displays errors produced during the login.
     void showError(const QString &msg);
-    void showError(QLabel *label, const QString &msg);
-
-private slots:
-    // Callback for the back button.
-    void onBackButtonClicked();
 
     // Callback for the login button.
-    void onLoginButtonClicked(LoginMethod loginMethod);
-
-    // Callback for probing the server found in the mxid
-    void onMatrixIdEntered();
-
-    // Callback for probing the manually entered server
-    void onServerAddressEntered();
+    void onLoginButtonClicked(LoginMethod loginMethod,
+                              QString userid,
+                              QString password,
+                              QString deviceName);
 
     // Callback for errors produced during server probing
     void versionError(const QString &error_message);
@@ -78,7 +104,8 @@ private slots:
 
 private:
     void checkHomeserverVersion();
-    std::string initialDeviceName()
+    void onMatrixIdEntered();
+    std::string initialDeviceName_() const
     {
 #if defined(Q_OS_MAC)
         return "Nheko on macOS";
@@ -92,34 +119,27 @@ private:
         return "Nheko";
 #endif
     }
+    void clearErrors()
+    {
+        error_.clear();
+        mxidError_.clear();
+        emit errorOccurred();
+        emit mxidErrorChanged();
+    }
 
-    QVBoxLayout *top_layout_;
-
-    QHBoxLayout *top_bar_layout_;
-    QHBoxLayout *logo_layout_;
-    QHBoxLayout *button_layout_;
-
-    QLabel *logo_;
-    QLabel *error_label_;
-    QLabel *error_matrixid_label_;
-
-    QHBoxLayout *serverLayout_;
-    QHBoxLayout *matrixidLayout_;
-    LoadingIndicator *spinner_;
-    QLabel *errorIcon_;
     QString inferredServerAddress_;
 
-    FlatButton *back_button_;
-    RaisedButton *login_button_, *sso_login_button_;
+    QString mxid_;
+    QString homeserver_;
+
+    QString mxidError_;
+    QString error_;
 
-    QWidget *form_widget_;
-    QHBoxLayout *form_wrapper_;
-    QVBoxLayout *form_layout_;
+    bool passwordSupported_ = true;
+    bool ssoSupported_      = false;
 
-    TextField *matrixid_input_;
-    TextField *password_input_;
-    TextField *deviceName_;
-    TextField *serverInput_;
-    bool passwordSupported = true;
-    bool ssoSupported      = false;
+    bool lookingUpHs_      = false;
+    bool loggingIn_        = false;
+    bool homeserverNeeded_ = false;
+    bool homeserverValid_  = false;
 };
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 03a99b0f1e5e839a5550c003a42bb45e9d11958d..3efdc23f74d4ba6928cb85ac15ca5919a8085aa5 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -87,7 +87,6 @@ MainWindow::MainWindow(QWindow *parent)
     setSource(QUrl(QStringLiteral("qrc:///qml/Root.qml")));
     // modal_ = new OverlayModal(this);
 
-
     // QFont font;
     // font.setStyleStrategy(QFont::PreferAntialias);
     // setFont(font);
@@ -95,31 +94,19 @@ MainWindow::MainWindow(QWindow *parent)
     trayIcon_ = new TrayIcon(QStringLiteral(":/logos/nheko.svg"), this);
 
     // welcome_page_  = new WelcomePage(this);
-    // login_page_    = new LoginPage(this);
     // register_page_ = new RegisterPage(this);
 
     //// Initialize sliding widget manager.
 
-    // connect(welcome_page_, SIGNAL(userLogin()), this, SLOT(showLoginPage()));
     // connect(welcome_page_, SIGNAL(userRegister()), this, SLOT(showRegisterPage()));
 
-    // connect(login_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
-    // connect(login_page_, &LoginPage::loggingIn, this, &MainWindow::showOverlayProgressBar);
-    // connect(register_page_, &RegisterPage::registering, this,
-    // &MainWindow::showOverlayProgressBar); connect(login_page_, &LoginPage::errorOccurred, this,
-    // [this]() { removeOverlayProgressBar(); }); connect(
-    //   register_page_, &RegisterPage::errorOccurred, this, [this]() { removeOverlayProgressBar();
-    //   });
-    // connect(register_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
-
-    connect(chat_page_, &ChatPage::closing, this, &MainWindow::showWelcomePage);
+    connect(chat_page_, &ChatPage::closing, this, [this] { switchToLoginPage(""); });
     // connect(
     //   chat_page_, &ChatPage::showOverlayProgressBar, this, &MainWindow::showOverlayProgressBar);
     connect(chat_page_, &ChatPage::unreadMessages, this, &MainWindow::setWindowTitle);
     connect(chat_page_, SIGNAL(unreadMessages(int)), trayIcon_, SLOT(setUnreadCount(int)));
     connect(chat_page_, &ChatPage::showLoginPage, this, [this](const QString &msg) {
-        login_page_->showError(msg);
-        showLoginPage();
+        switchToLoginPage(msg);
     });
 
     connect(userSettings_.get(), &UserSettings::trayChanged, trayIcon_, &TrayIcon::setVisible);
@@ -210,6 +197,7 @@ MainWindow::registerQmlTypes()
     qmlRegisterType<MxcAnimatedImage>("im.nheko", 1, 0, "MxcAnimatedImage");
     qmlRegisterType<MxcMediaProxy>("im.nheko", 1, 0, "MxcMedia");
     qmlRegisterType<RoomDirectoryModel>("im.nheko", 1, 0, "RoomDirectoryModel");
+    qmlRegisterType<LoginPage>("im.nheko", 1, 0, "Login");
     qmlRegisterUncreatableType<DeviceVerificationFlow>(
       "im.nheko",
       1,
@@ -375,9 +363,7 @@ MainWindow::removeOverlayProgressBar()
     QTimer *timer = new QTimer(this);
     timer->setSingleShot(true);
 
-    connect(timer, &QTimer::timeout, this, [this, timer]() {
-        timer->deleteLater();
-    });
+    connect(timer, &QTimer::timeout, this, [this, timer]() { timer->deleteLater(); });
 
     // FIXME:  Snackbar doesn't work if it's initialized in the constructor.
     // QTimer::singleShot(0, this, [this]() {
@@ -404,7 +390,6 @@ MainWindow::showChatPage()
 
     showOverlayProgressBar();
 
-    // login_page_->reset();
     chat_page_->bootstrap(userid, homeserver, token);
     connect(cache::client(), &Cache::databaseReady, this, &MainWindow::secretsChanged);
     connect(cache::client(), &Cache::secretChanged, this, &MainWindow::secretsChanged);
@@ -463,8 +448,7 @@ MainWindow::hasActiveUser()
 
 void
 MainWindow::showOverlayProgressBar()
-{
-}
+{}
 
 void
 MainWindow::openCreateRoomDialog(
@@ -485,8 +469,7 @@ MainWindow::showTransparentOverlayModal(QWidget *, QFlags<Qt::AlignmentFlag>)
 
 void
 MainWindow::showSolidOverlayModal(QWidget *, QFlags<Qt::AlignmentFlag>)
-{
-}
+{}
 
 bool
 MainWindow::hasActiveDialogs() const
@@ -503,8 +486,7 @@ MainWindow::pageSupportsTray() const
 
 void
 MainWindow::hideOverlay()
-{
-}
+{}
 
 inline void
 MainWindow::showDialog(QWidget *dialog)
@@ -520,11 +502,6 @@ MainWindow::showWelcomePage()
     removeOverlayProgressBar();
 }
 
-void
-MainWindow::showLoginPage()
-{
-}
-
 void
 MainWindow::showRegisterPage()
 {}
diff --git a/src/MainWindow.h b/src/MainWindow.h
index ea919f4d05074a520c1a08ae81f6ba2b1f1a9d74..5a3518930148fca380f463edf7c4897be1c53f28 100644
--- a/src/MainWindow.h
+++ b/src/MainWindow.h
@@ -20,7 +20,6 @@
 
 class ChatPage;
 class RegisterPage;
-class LoginPage;
 class WelcomePage;
 
 class LoadingIndicator;
@@ -65,6 +64,9 @@ public:
 
     MxcImageProvider *imageProvider() { return imgProvider; }
 
+    //! Show the chat page and start communicating with the given access token.
+    void showChatPage();
+
 protected:
     void closeEvent(QCloseEvent *event);
     bool event(QEvent *event) override;
@@ -76,15 +78,9 @@ private slots:
     //! Show the welcome page in the main window.
     void showWelcomePage();
 
-    //! Show the login page in the main window.
-    void showLoginPage();
-
     //! Show the register page in the main window.
     void showRegisterPage();
 
-    //! Show the chat page and start communicating with the given access token.
-    void showChatPage();
-
     void showOverlayProgressBar();
     void removeOverlayProgressBar();
 
@@ -96,6 +92,7 @@ signals:
 
     void switchToChatPage();
     void switchToWelcomePage();
+    void switchToLoginPage(QString error);
 
 private:
     void showDialog(QWidget *dialog);
@@ -112,8 +109,6 @@ private:
 
     //! The initial welcome screen.
     WelcomePage *welcome_page_;
-    //! The login screen.
-    LoginPage *login_page_;
     //! The register page.
     RegisterPage *register_page_;
     //! The main chat area.
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 9419f224177c77feaa31f79613f3dbf4a67dc3fb..13ab5dbbbb4c37ef40429378d53afbc20170a4c8 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -110,7 +110,7 @@ public slots:
     RoomlistModel *rooms() { return rooms_; }
 
 private:
-    bool isInitialSync_   = true;
+    bool isInitialSync_ = true;
 
     RoomlistModel *rooms_          = nullptr;
     CommunitiesModel *communities_ = nullptr;