diff --git a/CMakeLists.txt b/CMakeLists.txt
index d2d975f9059be7b5da02c34ddc0987336280d3c4..7dd4d2450cb244fe4723951fa0c68e63c4d8dd6e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -336,7 +336,6 @@ set(SRC_FILES
 	src/ui/NhekoCursorShape.cpp
 	src/ui/NhekoDropArea.cpp
 	src/ui/NhekoGlobalObject.cpp
-	src/ui/OverlayWidget.cpp
 	src/ui/RoomSettings.cpp
 	src/ui/TextField.cpp
 	src/ui/Theme.cpp
@@ -532,7 +531,6 @@ qt5_wrap_cpp(MOC_HEADERS
 	src/ui/NhekoCursorShape.h
 	src/ui/NhekoDropArea.h
 	src/ui/NhekoGlobalObject.h
-	src/ui/OverlayWidget.h
 	src/ui/RoomSettings.h
 	src/ui/TextField.h
 	src/ui/Theme.h
diff --git a/resources/langs/nheko_de.ts b/resources/langs/nheko_de.ts
index 29a043554cd9de205c6ea9613ab7a5f4d047529d..c499874b1fb77a27e2ee3c4bb3983fb7039e1075 100644
--- a/resources/langs/nheko_de.ts
+++ b/resources/langs/nheko_de.ts
@@ -319,7 +319,7 @@
     <message>
         <location line="+66"/>
         <source>Failed to kick %1 from %2: %3</source>
-        <translation>Kontte %1 nicht aus %2 entfernen: %3</translation>
+        <translation>Konnte %1 nicht aus %2 entfernen: %3</translation>
     </message>
 </context>
 <context>
diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml
index e3be440d12c1654eefcd8ca807292e2b7c3a7d95..88d3e7c6f1dd5d58ed5df4cf4eb5f0b22618a9bd 100644
--- a/resources/qml/Root.qml
+++ b/resources/qml/Root.qml
@@ -9,6 +9,7 @@ import "./dialogs"
 import "./emoji"
 import "./pages"
 import "./voip"
+import "./ui"
 import Qt.labs.platform 1.1 as Platform
 import QtQuick 2.15
 import QtQuick.Controls 2.15
@@ -402,6 +403,8 @@ Pane {
         }
     }
 
+    Snackbar { id: snackbar }
+
     Connections {
         function onSwitchToChatPage() {
             mainWindow.replace(null, chatPage);
@@ -409,6 +412,10 @@ Pane {
         function onSwitchToLoginPage(error) {
             mainWindow.replace(welcomePage, {}, loginPage, {"error": error}, StackView.PopTransition);
         }
+        function onShowNotification(msg) {
+            snackbar.showNotification(msg);
+            console.log("New snack: " + msg);
+        }
         target: MainWindow
     }
 
diff --git a/resources/qml/ui/Snackbar.qml b/resources/qml/ui/Snackbar.qml
new file mode 100644
index 0000000000000000000000000000000000000000..80c0d88860a0aa181216c807d6b7039665413929
--- /dev/null
+++ b/resources/qml/ui/Snackbar.qml
@@ -0,0 +1,98 @@
+// SPDX-FileCopyrightText: 2022 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import im.nheko 1.0
+
+Popup {
+    id: snackbar
+
+    property var messages: []
+    property string currentMessage: ""
+
+    function showNotification(msg) {
+        messages.push(msg);
+        currentMessage = messages[0];
+        if (!visible) {
+            open();
+            dismissTimer.start();
+        }
+    }
+
+    Timer {
+        id: dismissTimer
+        interval: 10000
+        onTriggered: snackbar.close()
+    }
+
+    onAboutToHide: {
+        messages.shift();
+    }
+    onClosed: {
+        if (messages.length > 0) {
+            currentMessage = messages[0];
+            open();
+            dismissTimer.restart();
+        }
+    }
+
+    parent: Overlay.overlay
+    opacity: 0
+    y: -100
+    x: (parent.width - width)/2
+    padding: Nheko.paddingLarge
+
+    contentItem: Label {
+        color: Nheko.colors.light
+        width: Math.max(Overlay.overlay? Overlay.overlay.width/2 : 0, 400)
+        text: snackbar.currentMessage
+        font.bold: true
+    }
+
+    background: Rectangle {
+        radius: Nheko.paddingLarge
+        color: Nheko.colors.dark
+        opacity: 0.8
+    }
+
+    enter: Transition {
+        NumberAnimation {
+            target: snackbar
+            property: "opacity"
+            from: 0.0
+            to: 1.0
+            duration: 200
+            easing.type: Easing.OutCubic
+        }
+        NumberAnimation {
+            target: snackbar
+            properties: "y"
+            from: -100
+            to: 100
+            duration: 1000
+            easing.type: Easing.OutCubic
+        }
+    }
+    exit: Transition {
+        NumberAnimation {
+            target: snackbar
+            property: "opacity"
+            from: 1.0
+            to: 0.0
+            duration: 300
+            easing.type: Easing.InCubic
+        }
+        NumberAnimation {
+            target: snackbar
+            properties: "y"
+            to: -100
+            from: 100
+            duration: 300
+            easing.type: Easing.InCubic
+        }
+    }
+}
+
+
diff --git a/resources/res.qrc b/resources/res.qrc
index 5b49d596b671a2f6a39baf9aead4bbbbe8284db4..2fba5f4cc0c5caa20d8533b5241c4c1703eec0ff 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -157,6 +157,7 @@
         <file>qml/ui/NhekoSlider.qml</file>
         <file>qml/ui/Ripple.qml</file>
         <file>qml/ui/Spinner.qml</file>
+        <file>qml/ui/Snackbar.qml</file>
         <file>qml/ui/animations/BlinkAnimation.qml</file>
         <file>qml/ui/media/MediaControls.qml</file>
         <file>qml/voip/ActiveCallBar.qml</file>
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 6458707580602178848c3e4b5f2304dc8c5ca4c9..83504d866a4bc3e11c58a022a8f14e3893a73af7 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -84,9 +84,8 @@ MainWindow::MainWindow(QWindow *parent)
     connect(chat_page_, &ChatPage::closing, this, [this] { switchToLoginPage(""); });
     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) {
-        switchToLoginPage(msg);
-    });
+    connect(chat_page_, &ChatPage::showLoginPage, this, &MainWindow::switchToLoginPage);
+    connect(chat_page_, &ChatPage::showNotification, this, &MainWindow::showNotification);
 
     connect(userSettings_.get(), &UserSettings::trayChanged, trayIcon_, &TrayIcon::setVisible);
     connect(trayIcon_,
diff --git a/src/MainWindow.h b/src/MainWindow.h
index 80ade988d00f5799e8f2f314e6d87f5f89b6d3d1..7bc94328bc535aaaeb655ef2b298eb2cd8a134fa 100644
--- a/src/MainWindow.h
+++ b/src/MainWindow.h
@@ -70,6 +70,8 @@ signals:
     void reload();
     void secretsChanged();
 
+    void showNotification(QString msg);
+
     void switchToChatPage();
     void switchToWelcomePage();
     void switchToLoginPage(QString error);
diff --git a/src/ui/OverlayWidget.cpp b/src/ui/OverlayWidget.cpp
deleted file mode 100644
index b755a44cfe456392613dbb136fb6fb354a2aea7e..0000000000000000000000000000000000000000
--- a/src/ui/OverlayWidget.cpp
+++ /dev/null
@@ -1,79 +0,0 @@
-// SPDX-FileCopyrightText: 2021 Nheko Contributors
-// SPDX-FileCopyrightText: 2022 Nheko Contributors
-//
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#include "OverlayWidget.h"
-
-#include <QPainter>
-#include <QStyleOption>
-
-OverlayWidget::OverlayWidget(QWidget *parent)
-  : QWidget(parent)
-{
-    if (parent) {
-        parent->installEventFilter(this);
-        setGeometry(overlayGeometry());
-        raise();
-    }
-}
-
-bool
-OverlayWidget::event(QEvent *event)
-{
-    if (!parent())
-        return QWidget::event(event);
-
-    switch (event->type()) {
-    case QEvent::ParentChange: {
-        parent()->installEventFilter(this);
-        setGeometry(overlayGeometry());
-        break;
-    }
-    case QEvent::ParentAboutToChange: {
-        parent()->removeEventFilter(this);
-        break;
-    }
-    default:
-        break;
-    }
-
-    return QWidget::event(event);
-}
-
-bool
-OverlayWidget::eventFilter(QObject *obj, QEvent *event)
-{
-    switch (event->type()) {
-    case QEvent::Move:
-    case QEvent::Resize:
-        setGeometry(overlayGeometry());
-        break;
-    default:
-        break;
-    }
-
-    return QWidget::eventFilter(obj, event);
-}
-
-QRect
-OverlayWidget::overlayGeometry() const
-{
-    QWidget *widget = parentWidget();
-
-    if (!widget)
-        return QRect();
-
-    return widget->rect();
-}
-
-void
-OverlayWidget::paintEvent(QPaintEvent *event)
-{
-    Q_UNUSED(event);
-
-    QStyleOption opt;
-    opt.initFrom(this);
-    QPainter p(this);
-    style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
-}
diff --git a/src/ui/OverlayWidget.h b/src/ui/OverlayWidget.h
deleted file mode 100644
index 19ad0cc6a9dbedb1bf1f796fadfeaf67479b5d7a..0000000000000000000000000000000000000000
--- a/src/ui/OverlayWidget.h
+++ /dev/null
@@ -1,26 +0,0 @@
-// SPDX-FileCopyrightText: 2021 Nheko Contributors
-// SPDX-FileCopyrightText: 2022 Nheko Contributors
-//
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#pragma once
-
-#include <QEvent>
-#include <QWidget>
-
-class QPainter;
-
-class OverlayWidget : public QWidget
-{
-    Q_OBJECT
-
-public:
-    explicit OverlayWidget(QWidget *parent = nullptr);
-
-protected:
-    bool event(QEvent *event) override;
-    bool eventFilter(QObject *obj, QEvent *event) override;
-
-    QRect overlayGeometry() const;
-    void paintEvent(QPaintEvent *event) override;
-};