diff --git a/CMakeLists.txt b/CMakeLists.txt
index 828509471bc6f6b9902173f675d588f1a8c5e6ae..05e5438049f3d9dfef7aec8396ce34a8f6afe9c3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -326,6 +326,7 @@ set(SRC_FILES
 	src/UserInfoWidget.cpp
 	src/UserSettingsPage.cpp
 	src/UsersModel.cpp
+	src/RoomsModel.cpp
 	src/Utils.cpp
 	src/WebRTCSession.cpp
 	src/WelcomePage.cpp
@@ -537,6 +538,7 @@ qt5_wrap_cpp(MOC_HEADERS
 	src/UserInfoWidget.h
 	src/UserSettingsPage.h
 	src/UsersModel.h
+	src/RoomsModel.h
 	src/WebRTCSession.h
 	src/WelcomePage.h
 	src/popups/PopupItem.h
diff --git a/resources/qml/Completer.qml b/resources/qml/Completer.qml
index 27322172e2619e7471c36f33434729c1e85f4f49..f77f50e9bb07aac2a5fb755e16e64d5b464c69bc 100644
--- a/resources/qml/Completer.qml
+++ b/resources/qml/Completer.qml
@@ -154,6 +154,35 @@ Popup {
 
                 }
 
+                DelegateChoice {
+                    roleValue: "room"
+
+                    RowLayout {
+                        id: del
+
+                        anchors.centerIn: parent
+
+                        Avatar {
+                            height: 24
+                            width: 24
+                            url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
+                            onClicked: popup.completionClicked(completer.completionAt(model.index))
+                        }
+
+                        Label {
+                            text: model.roomName
+                            color: model.index == popup.currentIndex ? colors.highlightedText : colors.text
+                        }
+
+                        Label {
+                            text: "(" + model.roomAlias + ")"
+                            color: model.index == popup.currentIndex ? colors.highlightedText : colors.buttonText
+                        }
+
+                    }
+
+                }
+
             }
 
         }
diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml
index b5c966606299d78f99cde0c0316312e3ce592385..7ecaf81a5b2703cd4dde820cee9da1d4f8965e31 100644
--- a/resources/qml/MessageInput.qml
+++ b/resources/qml/MessageInput.qml
@@ -183,6 +183,9 @@ Rectangle {
                     } else if (event.key == Qt.Key_Colon) {
                         messageInput.openCompleter(cursorPosition, "emoji");
                         popup.open();
+                    } else if (event.key == Qt.Key_NumberSign) {
+                        messageInput.openCompleter(cursorPosition, "room");
+                        popup.open();
                     } else if (event.key == Qt.Key_Escape && popup.opened) {
                         completerTriggeredAt = -1;
                         popup.completerName = "";
diff --git a/src/RoomsModel.cpp b/src/RoomsModel.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4286f87b54e20a6a84322a580fbbc6b1dcb3e271
--- /dev/null
+++ b/src/RoomsModel.cpp
@@ -0,0 +1,69 @@
+#include "RoomsModel.h"
+
+#include <QUrl>
+
+#include "Cache_p.h"
+#include "CompletionModelRoles.h"
+
+RoomsModel::RoomsModel(bool showOnlyRoomWithAliases, QObject *parent)
+  : QAbstractListModel(parent)
+  , showOnlyRoomWithAliases_(showOnlyRoomWithAliases)
+{
+        std::vector<std::string> rooms_ = cache::joinedRooms();
+        roomInfos                       = cache::getRoomInfo(rooms_);
+
+        for (const auto &r : rooms_) {
+                auto roomAliasesList = cache::client()->getRoomAliases(r);
+
+                if (showOnlyRoomWithAliases_) {
+                        if (roomAliasesList && !roomAliasesList->alias.empty()) {
+                                roomids.push_back(QString::fromStdString(r));
+                                roomAliases.push_back(
+                                  QString::fromStdString(roomAliasesList->alias));
+                        }
+                } else {
+                        roomids.push_back(QString::fromStdString(r));
+                        roomAliases.push_back(
+                          roomAliasesList ? QString::fromStdString(roomAliasesList->alias) : "");
+                }
+        }
+}
+
+QHash<int, QByteArray>
+RoomsModel::roleNames() const
+{
+        return {{CompletionModel::CompletionRole, "completionRole"},
+                {CompletionModel::SearchRole, "searchRole"},
+                {CompletionModel::SearchRole2, "searchRole2"},
+                {Roles::RoomAlias, "roomAlias"},
+                {Roles::AvatarUrl, "avatarUrl"},
+                {Roles::RoomID, "roomid"},
+                {Roles::RoomName, "roomName"}};
+}
+
+QVariant
+RoomsModel::data(const QModelIndex &index, int role) const
+{
+        if (hasIndex(index.row(), index.column(), index.parent())) {
+                switch (role) {
+                case CompletionModel::CompletionRole: {
+                        QString percentEncoding = QUrl::toPercentEncoding(roomAliases[index.row()]);
+                        return QString("[%1](https://matrix.to/#/%2)")
+                          .arg(roomAliases[index.row()], percentEncoding);
+                }
+                case CompletionModel::SearchRole:
+                case Qt::DisplayRole:
+                case Roles::RoomAlias:
+                        return roomAliases[index.row()];
+                case CompletionModel::SearchRole2:
+                case Roles::RoomName:
+                        return QString::fromStdString(roomInfos.at(roomids[index.row()]).name);
+                case Roles::AvatarUrl:
+                        return QString::fromStdString(
+                          roomInfos.at(roomids[index.row()]).avatar_url);
+                case Roles::RoomID:
+                        return roomids[index.row()];
+                }
+        }
+        return {};
+}
diff --git a/src/RoomsModel.h b/src/RoomsModel.h
new file mode 100644
index 0000000000000000000000000000000000000000..0e006448d88bf5eef5d11a3e72fa44144d17bd4a
--- /dev/null
+++ b/src/RoomsModel.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#include "Cache.h"
+
+#include <QAbstractListModel>
+#include <QString>
+
+class RoomsModel : public QAbstractListModel
+{
+public:
+        enum Roles
+        {
+                AvatarUrl = Qt::UserRole,
+                RoomAlias,
+                RoomID,
+                RoomName,
+        };
+
+        RoomsModel(bool showOnlyRoomWithAliases = false, QObject *parent = nullptr);
+        QHash<int, QByteArray> roleNames() const override;
+        int rowCount(const QModelIndex &parent = QModelIndex()) const override
+        {
+                (void)parent;
+                return (int)roomids.size();
+        }
+        QVariant data(const QModelIndex &index, int role) const override;
+
+private:
+        std::vector<QString> roomids;
+        std::vector<QString> roomAliases;
+        std::map<QString, RoomInfo> roomInfos;
+        bool showOnlyRoomWithAliases_;
+};
diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp
index 08cbd15b0a3804ee914f1457dc977e3d46ee111e..5ef38ac7f1520641472a64810b37f092579bb06f 100644
--- a/src/timeline/InputBar.cpp
+++ b/src/timeline/InputBar.cpp
@@ -19,6 +19,7 @@
 #include "MainWindow.h"
 #include "MatrixClient.h"
 #include "Olm.h"
+#include "RoomsModel.h"
 #include "TimelineModel.h"
 #include "TimelineViewManager.h"
 #include "UserSettingsPage.h"
@@ -186,6 +187,11 @@ InputBar::completerFor(QString completerName)
                 auto proxy      = new CompletionProxyModel(emojiModel);
                 emojiModel->setParent(proxy);
                 return proxy;
+        } else if (completerName == "room") {
+                auto roomModel = new RoomsModel(true);
+                auto proxy     = new CompletionProxyModel(roomModel);
+                roomModel->setParent(proxy);
+                return proxy;
         }
         return nullptr;
 }