diff --git a/CMakeLists.txt b/CMakeLists.txt
index 99ae1d62b1cf6b637e65e8bd6c03685d502d4900..6415b91288602659e84c97a04bb89bee2fe8012a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -171,6 +171,7 @@ set(SRC_FILES
     src/ui/FlatButton.cc
     src/ui/OverlayModal.cc
     src/ui/ScrollBar.cc
+    src/ui/SnackBar.cc
     src/ui/RaisedButton.cc
     src/ui/Ripple.cc
     src/ui/RippleOverlay.cc
@@ -249,6 +250,7 @@ qt5_wrap_cpp(MOC_HEADERS
     include/ui/FlatButton.h
     include/ui/OverlayWidget.h
     include/ui/ScrollBar.h
+    include/ui/SnackBar.h
     include/ui/RaisedButton.h
     include/ui/Ripple.h
     include/ui/RippleOverlay.h
@@ -292,9 +294,6 @@ if (APPLE)
 endif()
 
 if (BUILD_TESTS)
-    #
-    # Build tests.
-    #
     enable_testing()
 
     find_package(GTest REQUIRED)
@@ -312,26 +311,23 @@ if (BUILD_TESTS)
     add_test(MatrixEvents events_test)
     add_test(MatrixEventCollection event_collection_test)
     add_test(MatrixMessageEvents message_events)
-else()
-    #
-    # Build the executable.
-    #
+endif()
+
 if(APPVEYOR_BUILD)
     set (NHEKO_LIBS matrix_events Qt5::Widgets Qt5::Network lmdb)
 else()
     set (NHEKO_LIBS matrix_events Qt5::Widgets Qt5::Network ${LMDB_LIBRARY})
 endif()
 
-    set (NHEKO_DEPS ${OS_BUNDLE} ${SRC_FILES} ${UI_HEADERS} ${MOC_HEADERS} ${QRC} ${LANG_QRC} ${QM_SRC})
-
-    if(APPLE)
-        add_executable (nheko ${NHEKO_DEPS})
-        target_link_libraries (nheko ${NHEKO_LIBS} Qt5::MacExtras)
-    elseif(WIN32)
-        add_executable (nheko ${ICON_FILE} ${NHEKO_DEPS})
-        target_link_libraries (nheko ${NTDLIB} ${NHEKO_LIBS} Qt5::WinMain)
-    else()
-        add_executable (nheko ${NHEKO_DEPS})
-        target_link_libraries (nheko ${NHEKO_LIBS})
-    endif()
+set (NHEKO_DEPS ${OS_BUNDLE} ${SRC_FILES} ${UI_HEADERS} ${MOC_HEADERS} ${QRC} ${LANG_QRC} ${QM_SRC})
+
+if(APPLE)
+    add_executable (nheko ${NHEKO_DEPS})
+    target_link_libraries (nheko ${NHEKO_LIBS} Qt5::MacExtras)
+elseif(WIN32)
+    add_executable (nheko ${ICON_FILE} ${NHEKO_DEPS})
+    target_link_libraries (nheko ${NTDLIB} ${NHEKO_LIBS} Qt5::WinMain)
+else()
+    add_executable (nheko ${NHEKO_DEPS})
+    target_link_libraries (nheko ${NHEKO_LIBS})
 endif()
diff --git a/include/ui/SnackBar.h b/include/ui/SnackBar.h
new file mode 100644
index 0000000000000000000000000000000000000000..076b71056fbeb0786db8898ed06a7e75e893c91c
--- /dev/null
+++ b/include/ui/SnackBar.h
@@ -0,0 +1,80 @@
+#pragma once
+
+#include <QCoreApplication>
+#include <QPaintEvent>
+#include <QStateMachine>
+#include <QTimer>
+
+#include "OverlayWidget.h"
+
+enum class SnackBarPosition {
+        Bottom,
+        Top,
+};
+
+class SnackBar : public OverlayWidget
+{
+        Q_OBJECT
+
+public:
+        explicit SnackBar(QWidget *parent);
+        ~SnackBar();
+
+        inline void setBackgroundColor(const QColor &color);
+        inline void setTextColor(const QColor &color);
+        inline void setPosition(SnackBarPosition pos);
+
+public slots:
+        void showMessage(const QString &msg);
+
+protected:
+        void paintEvent(QPaintEvent *event) override;
+        void mousePressEvent(QMouseEvent *event) override;
+
+private slots:
+        void onTimeout();
+        void hideMessage();
+
+private:
+        void stopTimers();
+        void start();
+
+        QColor bgColor_;
+        QColor textColor_;
+
+        qreal bgOpacity_;
+        qreal offset_;
+
+        QList<QString> messages_;
+
+        QTimer *showTimer_;
+        QTimer *hideTimer_;
+
+        int duration_;
+        int boxWidth_;
+        int boxHeight_;
+        int boxPadding_;
+
+        SnackBarPosition position_;
+};
+
+inline void
+SnackBar::setPosition(SnackBarPosition pos)
+{
+        position_ = pos;
+        update();
+}
+
+inline void
+SnackBar::setBackgroundColor(const QColor &color)
+{
+        bgColor_ = color;
+        update();
+}
+
+inline void
+SnackBar::setTextColor(const QColor &color)
+{
+        textColor_ = color;
+        update();
+}
diff --git a/src/ui/SnackBar.cc b/src/ui/SnackBar.cc
new file mode 100644
index 0000000000000000000000000000000000000000..673c2f934307c32caf96c2f002b91794e47e8277
--- /dev/null
+++ b/src/ui/SnackBar.cc
@@ -0,0 +1,143 @@
+#include <QDebug>
+#include <QPainter>
+
+#include "SnackBar.h"
+
+constexpr int STARTING_OFFSET = 1;
+
+SnackBar::SnackBar(QWidget *parent)
+  : OverlayWidget(parent)
+{
+        bgOpacity_  = 0.9;
+        duration_   = 6000;
+        boxWidth_   = 400;
+        boxHeight_  = 40;
+        boxPadding_ = 10;
+        textColor_  = QColor("white");
+        bgColor_    = QColor("#333");
+        offset_     = STARTING_OFFSET;
+        position_   = SnackBarPosition::Top;
+
+        QFont font("Open Sans", 14, QFont::Medium);
+        setFont(font);
+
+        showTimer_ = new QTimer();
+        hideTimer_ = new QTimer();
+        hideTimer_->setSingleShot(true);
+
+        connect(showTimer_, SIGNAL(timeout()), this, SLOT(onTimeout()));
+        connect(hideTimer_, SIGNAL(timeout()), this, SLOT(hideMessage()));
+}
+
+SnackBar::~SnackBar()
+{
+        stopTimers();
+
+        delete showTimer_;
+        delete hideTimer_;
+}
+
+void
+SnackBar::start()
+{
+        show();
+        raise();
+
+        showTimer_->start(10);
+}
+
+void
+SnackBar::hideMessage()
+{
+        stopTimers();
+        hide();
+
+        // Moving on to the next message.
+        messages_.removeFirst();
+
+        // Reseting the starting position of the widget.
+        offset_ = STARTING_OFFSET;
+
+        if (!messages_.isEmpty())
+                start();
+}
+
+void
+SnackBar::stopTimers()
+{
+        showTimer_->stop();
+        hideTimer_->stop();
+}
+
+void
+SnackBar::showMessage(const QString &msg)
+{
+        messages_.push_back(msg);
+
+        // There is already an active message.
+        if (isVisible())
+                return;
+
+        start();
+}
+
+void
+SnackBar::onTimeout()
+{
+        offset_ -= 0.5;
+
+        if (offset_ <= 0.0) {
+                showTimer_->stop();
+                hideTimer_->start(duration_);
+        }
+
+        update();
+}
+
+void
+SnackBar::mousePressEvent(QMouseEvent *)
+{
+        hideMessage();
+}
+
+void
+SnackBar::paintEvent(QPaintEvent *event)
+{
+        Q_UNUSED(event)
+
+        if (messages_.isEmpty())
+                return;
+
+        auto message_ = messages_.first();
+
+        QPainter p(this);
+        p.setRenderHint(QPainter::Antialiasing);
+
+        QBrush brush;
+        brush.setStyle(Qt::SolidPattern);
+        brush.setColor(bgColor_);
+        p.setBrush(brush);
+        p.setOpacity(bgOpacity_);
+
+        QRect r(0, 0, boxWidth_, boxHeight_);
+
+        p.setPen(Qt::white);
+        QRect br = p.boundingRect(r, Qt::AlignHCenter | Qt::AlignTop | Qt::TextWordWrap, message_);
+
+        p.setPen(Qt::NoPen);
+        r = br.united(r).adjusted(-boxPadding_, -boxPadding_, boxPadding_, boxPadding_);
+
+        const qreal s = 1 - offset_;
+
+        if (position_ == SnackBarPosition::Bottom)
+                p.translate((width() - (r.width() - 2 * boxPadding_)) / 2,
+                            height() - boxPadding_ - s * (r.height()));
+        else
+                p.translate((width() - (r.width() - 2 * boxPadding_)) / 2,
+                            s * (r.height()) - 2 * boxPadding_);
+
+        br.moveCenter(r.center());
+        p.drawRoundedRect(r.adjusted(0, 0, 0, 3), 3, 3);
+        p.setPen(textColor_);
+        p.drawText(br, Qt::AlignHCenter | Qt::AlignTop | Qt::TextWordWrap, message_);
+}