diff --git a/CMakeLists.txt b/CMakeLists.txt
index fb45483eb20dc4d1cb00e78d9fc3b041af39403b..d64b033af1792958a09a7d475360ab4d1c7ab9ad 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -316,7 +316,6 @@ set(SRC_FILES
 	src/MatrixClient.cpp
 	src/MxcImageProvider.cpp
 	src/Olm.cpp
-	src/QuickSwitcher.cpp
 	src/RegisterPage.cpp
 	src/RoomInfoListItem.cpp
 	src/RoomList.cpp
@@ -532,7 +531,6 @@ qt5_wrap_cpp(MOC_HEADERS
 	src/LoginPage.h
 	src/MainWindow.h
 	src/MxcImageProvider.h
-	src/QuickSwitcher.h
 	src/RegisterPage.h
 	src/RoomInfoListItem.h
 	src/RoomList.h
diff --git a/resources/qml/Completer.qml b/resources/qml/Completer.qml
index e1b5c12990ef7211b5779efa338bbffebd451ca9..6170045c2dae82026126325fd46b8625fc96b243 100644
--- a/resources/qml/Completer.qml
+++ b/resources/qml/Completer.qml
@@ -15,9 +15,16 @@ Popup {
     property string completerName
     property var completer
     property bool bottomToTop: true
+    property bool fullWidth: false
+    property bool centerRowContent: true
+    property int avatarHeight: 24
+    property int avatarWidth: 24
+    property int rowMargin: 0
+    property int rowSpacing: 5
     property alias count: listView.count
 
     signal completionClicked(string completion)
+    signal completionSelected(string id)
 
     function up() {
         if (bottomToTop)
@@ -54,9 +61,19 @@ Popup {
             return null;
     }
 
+    function finishCompletion() {
+        if(popup.completerName == "room") {
+            popup.completionSelected(listView.itemAtIndex(currentIndex).modelData.roomid)
+        }
+    }
+
     onCompleterNameChanged: {
         if (completerName) {
-            completer = TimelineManager.timeline.input.completerFor(completerName);
+            if (completerName == "user") {
+                completer = TimelineManager.completerFor(completerName, TimelineManager.timeline.roomId());
+            } else {
+                completer = TimelineManager.completerFor(completerName);
+            }
             completer.setSearchString("");
         } else {
             completer = undefined;
@@ -75,14 +92,17 @@ Popup {
         id: listView
 
         anchors.fill: parent
-        implicitWidth: contentItem.childrenRect.width
+        implicitWidth: fullWidth ? parent.width : contentItem.childrenRect.width
         model: completer
         verticalLayoutDirection: popup.bottomToTop ? ListView.BottomToTop : ListView.TopToBottom
+        spacing: rowSpacing
+        pixelAligned: true
 
         delegate: Rectangle {
             color: model.index == popup.currentIndex ? colors.highlight : colors.base
-            height: chooser.childrenRect.height + 4
-            implicitWidth: chooser.childrenRect.width + 4
+            height: chooser.childrenRect.height + 2 * popup.rowMargin
+            implicitWidth: fullWidth ? popup.width : chooser.childrenRect.width + 4
+            property variant modelData: model
 
             MouseArea {
                 id: mouseArea
@@ -90,7 +110,12 @@ Popup {
                 anchors.fill: parent
                 hoverEnabled: true
                 onPositionChanged: popup.currentIndex = model.index
-                onClicked: popup.completionClicked(completer.completionAt(model.index))
+                onClicked: {
+                    popup.completionClicked(completer.completionAt(model.index))
+                    if(popup.completerName == "room") {
+                        popup.completionSelected(model.roomid)
+                    }
+                }
 
                 Ripple {
                     rippleTarget: mouseArea
@@ -103,7 +128,8 @@ Popup {
                 id: chooser
 
                 roleValue: popup.completerName
-                anchors.centerIn: parent
+                anchors.fill: parent
+                anchors.margins: popup.rowMargin
 
                 DelegateChoice {
                     roleValue: "user"
@@ -112,10 +138,11 @@ Popup {
                         id: del
 
                         anchors.centerIn: parent
+                        spacing: rowSpacing
 
                         Avatar {
-                            height: 24
-                            width: 24
+                            height: popup.avatarHeight
+                            width: popup.avatarWidth
                             displayName: model.displayName
                             url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
                             onClicked: popup.completionClicked(completer.completionAt(model.index))
@@ -142,6 +169,7 @@ Popup {
                         id: del
 
                         anchors.centerIn: parent
+                        spacing: rowSpacing
 
                         Label {
                             text: model.unicode
@@ -161,14 +189,44 @@ Popup {
                 DelegateChoice {
                     roleValue: "room"
 
+                    RowLayout {
+                        id: del
+
+                        anchors.centerIn: centerRowContent ? parent : undefined
+                        spacing: rowSpacing
+
+                        Avatar {
+                            height: popup.avatarHeight
+                            width: popup.avatarWidth
+                            url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
+                            onClicked: {
+                                popup.completionClicked(completer.completionAt(model.index))
+                                popup.completionSelected(model.roomid)
+                            }
+                        }
+
+                        Label {
+                            text: model.roomName
+                            font.pixelSize: popup.avatarHeight * 0.5
+                            color: model.index == popup.currentIndex ? colors.highlightedText : colors.text
+                        }
+
+                    }
+
+                }
+
+                DelegateChoice {
+                    roleValue: "roomAliases"
+
                     RowLayout {
                         id: del
 
                         anchors.centerIn: parent
+                        spacing: rowSpacing
 
                         Avatar {
-                            height: 24
-                            width: 24
+                            height: popup.avatarHeight
+                            width: popup.avatarWidth
                             url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
                             onClicked: popup.completionClicked(completer.completionAt(model.index))
                         }
diff --git a/resources/qml/MatrixTextField.qml b/resources/qml/MatrixTextField.qml
new file mode 100644
index 0000000000000000000000000000000000000000..9e8d286b17d73825f351c94fa2d2a2a6ed58964d
--- /dev/null
+++ b/resources/qml/MatrixTextField.qml
@@ -0,0 +1,56 @@
+import QtQuick 2.13
+import QtQuick.Layouts 1.13
+import QtQuick.Controls 2.13
+
+TextField {
+    id: input
+    palette: colors
+
+    background: Rectangle {
+        color: colors.base
+    }
+
+    Rectangle {
+        id: blueBar
+
+        anchors.top: parent.bottom
+        anchors.horizontalCenter: parent.horizontalCenter
+
+        color: colors.highlight
+        height: 1
+        width: parent.width
+    
+        Rectangle {
+            id: blackBar
+
+            anchors.verticalCenter: blueBar.verticalCenter
+            anchors.horizontalCenter: parent.horizontalCenter
+
+            height: parent.height+1
+            width: 0
+            color: colors.text
+        
+            states: State {
+                name: "focused"; when: input.activeFocus == true
+                PropertyChanges {
+                    target: blackBar
+                    width: blueBar.width
+                }
+            }
+        
+            transitions: Transition {
+                from: ""
+                to: "focused"
+                reversible: true
+
+                NumberAnimation { 
+                    target: blackBar
+                    properties: "width"
+                    duration: 500
+                    easing.type: Easing.InOutQuad
+                    alwaysRunToEnd: true 
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml
index 7b651bc0aaba62f5a2a63b563b060e91dad4b127..faf0d4421a326ffb497fff8b34487a111e4fd126 100644
--- a/resources/qml/MessageInput.qml
+++ b/resources/qml/MessageInput.qml
@@ -161,7 +161,7 @@ Rectangle {
                         messageInput.openCompleter(cursorPosition, "emoji");
                         popup.open();
                     } else if (event.key == Qt.Key_NumberSign) {
-                        messageInput.openCompleter(cursorPosition, "room");
+                        messageInput.openCompleter(cursorPosition, "roomAliases");
                         popup.open();
                     } else if (event.key == Qt.Key_Escape && popup.opened) {
                         completerTriggeredAt = -1;
diff --git a/resources/qml/QuickSwitcher.qml b/resources/qml/QuickSwitcher.qml
new file mode 100644
index 0000000000000000000000000000000000000000..29d9a9b2c1b6d5befaba9d3fb21546a8470d7fd7
--- /dev/null
+++ b/resources/qml/QuickSwitcher.qml
@@ -0,0 +1,97 @@
+import QtQuick 2.9
+import QtQuick.Controls 2.3
+import im.nheko 1.0
+
+Popup {
+    id: quickSwitcher
+
+    property int textHeight: 32
+    property int textMargin: 8
+
+    x: parent.width / 2 - width / 2
+    y: parent.height / 4 - height / 2
+    width: parent.width / 2
+    modal: true
+    closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
+    parent: Overlay.overlay
+    palette: colors
+
+    Overlay.modal: Rectangle {
+        color: "#aa1E1E1E"
+    }
+
+    MatrixTextField {
+        id: roomTextInput
+
+        anchors.fill: parent
+        font.pixelSize: quickSwitcher.textHeight * 0.6
+        padding: textMargin
+        color: colors.text
+
+        onTextEdited: {
+            completerPopup.completer.setSearchString(text)
+        }
+
+        Keys.onPressed: {
+            if (event.key == Qt.Key_Up && completerPopup.opened) {
+                event.accepted = true;
+                completerPopup.up();
+            } else if (event.key == Qt.Key_Down && completerPopup.opened) {
+                event.accepted = true;
+                completerPopup.down();
+            } else if (event.matches(StandardKey.InsertParagraphSeparator)) {
+                completerPopup.finishCompletion()
+                event.accepted = true;
+            }
+        }
+    }
+
+    Completer {
+        id: completerPopup
+
+        x: roomTextInput.x
+        y: roomTextInput.y + roomTextInput.height + textMargin
+        width: parent.width
+        completerName: "room"
+        bottomToTop: false
+        fullWidth: true
+        avatarHeight: textHeight
+        avatarWidth: textHeight
+        centerRowContent: false
+        rowMargin: 8
+        rowSpacing: 6
+
+        closePolicy: Popup.NoAutoClose
+    }
+
+    onOpened: {
+        completerPopup.open()
+        delay(200, function() {
+            roomTextInput.forceActiveFocus()
+        })
+    }
+
+    onClosed: {
+        completerPopup.close()
+    }
+
+    Connections {
+        onCompletionSelected: {
+            TimelineManager.setHistoryView(id)
+            TimelineManager.highlightRoom(id)
+            quickSwitcher.close()
+        }
+        target: completerPopup
+    }
+
+    Timer {
+        id: timer
+    }
+
+    function delay(delayTime, cb) {
+        timer.interval = delayTime;
+        timer.repeat = false;
+        timer.triggered.connect(cb);
+        timer.start();
+    }
+}
\ No newline at end of file
diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index 0a8233293d2042e4db97cb726bd6b7252d2424d9..20251bc0405cd2eea59a9263b08590a3f3b69e37 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -72,6 +72,22 @@ Page {
 
     }
 
+    Component {
+        id: quickSwitcherComponent
+
+        QuickSwitcher {
+        }
+    }
+
+    Shortcut {
+        sequence: "Ctrl+K"
+        onActivated: {
+            var quickSwitch = quickSwitcherComponent.createObject(timelineRoot);
+            TimelineManager.focusTimeline()
+            quickSwitch.open();
+        }
+    }
+
     Menu {
         id: messageContextMenu
 
diff --git a/resources/res.qrc b/resources/res.qrc
index e629a871cddac1bd69bd50a95d4686f45d54092e..328f65cae6f22438abff27ff48b3f01e9dbbfc3d 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -129,6 +129,7 @@
         <file>qml/EncryptionIndicator.qml</file>
         <file>qml/ImageButton.qml</file>
         <file>qml/MatrixText.qml</file>
+        <file>qml/MatrixTextField.qml</file>
         <file>qml/ToggleButton.qml</file>
         <file>qml/MessageInput.qml</file>
         <file>qml/MessageView.qml</file>
@@ -140,6 +141,7 @@
         <file>qml/StatusIndicator.qml</file>
         <file>qml/TimelineRow.qml</file>
         <file>qml/TopBar.qml</file>
+        <file>qml/QuickSwitcher.qml</file>
         <file>qml/TypingIndicator.qml</file>
         <file>qml/RoomSettings.qml</file>
         <file>qml/emoji/EmojiButton.qml</file>
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index d7ea43010ca5c8f3eeafb331a35e529ff7466245..7c018afff11a6fcbc9ebb10a88994a34bb5abe75 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -23,7 +23,6 @@
 #include "MainWindow.h"
 #include "MatrixClient.h"
 #include "Olm.h"
-#include "QuickSwitcher.h"
 #include "RoomList.h"
 #include "SideBarActions.h"
 #include "Splitter.h"
@@ -589,18 +588,6 @@ ChatPage::loadStateFromCache()
         emit trySyncCb();
 }
 
-void
-ChatPage::showQuickSwitcher()
-{
-        auto dialog = new QuickSwitcher(this);
-
-        connect(dialog, &QuickSwitcher::roomSelected, room_list_, &RoomList::highlightSelectedRoom);
-        connect(
-          dialog, &QuickSwitcher::closing, this, []() { MainWindow::instance()->hideOverlay(); });
-
-        MainWindow::instance()->showTransparentOverlayModal(dialog);
-}
-
 void
 ChatPage::removeRoom(const QString &room_id)
 {
@@ -1456,3 +1443,9 @@ ChatPage::handleMatrixUri(const QUrl &uri)
 {
         handleMatrixUri(uri.toString(QUrl::ComponentFormattingOption::FullyEncoded).toUtf8());
 }
+
+void
+ChatPage::highlightRoom(const QString &room_id)
+{
+        room_list_->highlightSelectedRoom(room_id);
+}
\ No newline at end of file
diff --git a/src/ChatPage.h b/src/ChatPage.h
index f2078f45456773c5322449787dd47dd5e056e689..17a4827f110d5cf336173f692b1b9c6c52c48ae5 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -31,7 +31,6 @@
 #include "notifications/Manager.h"
 
 class OverlayModal;
-class QuickSwitcher;
 class RoomList;
 class SideBarActions;
 class Splitter;
@@ -72,7 +71,6 @@ public:
 
         // Initialize all the components of the UI.
         void bootstrap(QString userid, QString homeserver, QString token);
-        void showQuickSwitcher();
         QString currentRoom() const { return current_room_; }
 
         static ChatPage *instance() { return instance_; }
@@ -104,6 +102,7 @@ public slots:
         void startChat(QString userid);
         void leaveRoom(const QString &room_id);
         void createRoom(const mtx::requests::CreateRoom &req);
+        void highlightRoom(const QString &room_id);
         void joinRoom(const QString &room);
         void joinRoomVia(const std::string &room_id,
                          const std::vector<std::string> &via,
diff --git a/src/CompletionProxyModel.cpp b/src/CompletionProxyModel.cpp
index 44a149114acb0eb112a7b5de7e5f9e07a9ced57e..a6759978d0bc74ff219e700e44cd6774a48ea752 100644
--- a/src/CompletionProxyModel.cpp
+++ b/src/CompletionProxyModel.cpp
@@ -10,8 +10,11 @@
 #include "Logging.h"
 #include "Utils.h"
 
-CompletionProxyModel::CompletionProxyModel(QAbstractItemModel *model, QObject *parent)
+CompletionProxyModel::CompletionProxyModel(QAbstractItemModel *model,
+                                           int max_mistakes,
+                                           QObject *parent)
   : QAbstractProxyModel(parent)
+  , maxMistakes_(max_mistakes)
 {
         setSourceModel(model);
         QRegularExpression splitPoints("\\s+|-");
@@ -63,7 +66,7 @@ CompletionProxyModel::invalidate()
 {
         auto key = searchString.toUcs4();
         beginResetModel();
-        mapping = trie_.search(key, 7);
+        mapping = trie_.search(key, 7, maxMistakes_);
         endResetModel();
 
         std::string temp;
diff --git a/src/CompletionProxyModel.h b/src/CompletionProxyModel.h
index 1b8e18f720e61b4566d9e0610b289d100eb75914..214845b7ecc609da6edb486b91c185fa99aa8f5c 100644
--- a/src/CompletionProxyModel.h
+++ b/src/CompletionProxyModel.h
@@ -58,19 +58,19 @@ struct trie
         }
 
         std::vector<Value> search(const QVector<Key> &keys, //< TODO(Nico): replace this with a span
-                                  size_t limit,
-                                  size_t max_distance = 2) const
+                                  size_t result_count_limit,
+                                  size_t max_edit_distance = 2) const
         {
                 std::vector<Value> ret;
-                if (!limit)
+                if (!result_count_limit)
                         return ret;
 
                 if (keys.isEmpty())
-                        return valuesAndSubvalues(limit);
+                        return valuesAndSubvalues(result_count_limit);
 
-                auto append = [&ret, limit](std::vector<Value> &&in) {
+                auto append = [&ret, result_count_limit](std::vector<Value> &&in) {
                         for (auto &&v : in) {
-                                if (ret.size() >= limit)
+                                if (ret.size() >= result_count_limit)
                                         return;
 
                                 if (std::find(ret.begin(), ret.end(), v) == ret.end()) {
@@ -80,11 +80,12 @@ struct trie
                 };
 
                 if (auto e = this->next.find(keys[0]); e != this->next.end()) {
-                        append(e->second.search(keys.mid(1), limit, max_distance));
+                        append(
+                          e->second.search(keys.mid(1), result_count_limit, max_edit_distance));
                 }
 
-                if (max_distance && ret.size() < limit) {
-                        max_distance -= 1;
+                if (max_edit_distance && ret.size() < result_count_limit) {
+                        max_edit_distance -= 1;
 
                         // swap chars case
                         if (keys.size() >= 2) {
@@ -99,27 +100,31 @@ struct trie
                                 }
 
                                 if (t) {
-                                        append(t->search(
-                                          keys.mid(2), (limit - ret.size()) * 2, max_distance));
+                                        append(t->search(keys.mid(2),
+                                                         (result_count_limit - ret.size()) * 2,
+                                                         max_edit_distance));
                                 }
                         }
 
                         // delete character case
-                        append(this->search(keys.mid(1), (limit - ret.size()) * 2, max_distance));
+                        append(this->search(
+                          keys.mid(1), (result_count_limit - ret.size()) * 2, max_edit_distance));
 
                         // substitute and insert cases
                         for (const auto &[k, t] : this->next) {
-                                if (k == keys[0] || ret.size() >= limit)
+                                if (k == keys[0] || ret.size() >= result_count_limit)
                                         break;
 
                                 // substitute
-                                append(t.search(keys.mid(1), limit - ret.size(), max_distance));
+                                append(t.search(
+                                  keys.mid(1), result_count_limit - ret.size(), max_edit_distance));
 
-                                if (ret.size() >= limit)
+                                if (ret.size() >= result_count_limit)
                                         break;
 
                                 // insert
-                                append(t.search(keys, limit - ret.size(), max_distance));
+                                append(t.search(
+                                  keys, result_count_limit - ret.size(), max_edit_distance));
                         }
                 }
 
@@ -132,7 +137,9 @@ class CompletionProxyModel : public QAbstractProxyModel
         Q_OBJECT
 
 public:
-        CompletionProxyModel(QAbstractItemModel *model, QObject *parent = nullptr);
+        CompletionProxyModel(QAbstractItemModel *model,
+                             int max_mistakes = 2,
+                             QObject *parent  = nullptr);
 
         void invalidate();
 
@@ -160,4 +167,5 @@ private:
         QString searchString;
         trie<uint, int> trie_;
         std::vector<int> mapping;
+        int maxMistakes_;
 };
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index c405f8531625da6d19b10d820b284102efc343af..92f43e03dbaef1e5845170fe0389b55b7f2e8e6c 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -135,12 +135,6 @@ MainWindow::MainWindow(QWidget *parent)
         QShortcut *quitShortcut = new QShortcut(QKeySequence::Quit, this);
         connect(quitShortcut, &QShortcut::activated, this, QApplication::quit);
 
-        QShortcut *quickSwitchShortcut = new QShortcut(QKeySequence("Ctrl+K"), this);
-        connect(quickSwitchShortcut, &QShortcut::activated, this, [this]() {
-                if (chat_page_->isVisible() && !hasActiveDialogs())
-                        chat_page_->showQuickSwitcher();
-        });
-
         trayIcon_->setVisible(userSettings_->tray());
 
         if (hasActiveUser()) {
diff --git a/src/QuickSwitcher.cpp b/src/QuickSwitcher.cpp
deleted file mode 100644
index ad2be23d6b908ff8fe721400b877be567e91f417..0000000000000000000000000000000000000000
--- a/src/QuickSwitcher.cpp
+++ /dev/null
@@ -1,129 +0,0 @@
-// SPDX-FileCopyrightText: 2017 Konstantinos Sideris <siderisk@auth.gr>
-// SPDX-FileCopyrightText: 2021 Nheko Contributors
-//
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#include <QCompleter>
-#include <QPainter>
-#include <QStringListModel>
-#include <QStyleOption>
-#include <QTimer>
-#include <QtConcurrent>
-
-#include "Cache.h"
-#include "QuickSwitcher.h"
-#include "popups/SuggestionsPopup.h"
-
-Q_DECLARE_METATYPE(std::vector<RoomSearchResult>)
-
-RoomSearchInput::RoomSearchInput(QWidget *parent)
-  : TextField(parent)
-{}
-
-void
-RoomSearchInput::keyPressEvent(QKeyEvent *event)
-{
-        switch (event->key()) {
-        case Qt::Key_Tab:
-        case Qt::Key_Down: {
-                emit selectNextCompletion();
-                event->accept();
-                break;
-        }
-        case Qt::Key_Backtab:
-        case Qt::Key_Up: {
-                emit selectPreviousCompletion();
-                event->accept();
-                break;
-        }
-        default:
-                TextField::keyPressEvent(event);
-        }
-}
-
-void
-RoomSearchInput::hideEvent(QHideEvent *event)
-{
-        emit hiding();
-        TextField::hideEvent(event);
-}
-
-QuickSwitcher::QuickSwitcher(QWidget *parent)
-  : QWidget(parent)
-{
-        qRegisterMetaType<std::vector<RoomSearchResult>>();
-        setMaximumWidth(450);
-
-        QFont font;
-        font.setPointSizeF(font.pointSizeF() * 1.5);
-
-        roomSearch_ = new RoomSearchInput(this);
-        roomSearch_->setFont(font);
-        roomSearch_->setPlaceholderText(tr("Search for a room..."));
-
-        topLayout_ = new QVBoxLayout(this);
-        topLayout_->addWidget(roomSearch_);
-
-        connect(this,
-                &QuickSwitcher::queryResults,
-                this,
-                [this](const std::vector<RoomSearchResult> &rooms) {
-                        auto pos = mapToGlobal(roomSearch_->geometry().bottomLeft());
-
-                        popup_.setFixedWidth(width());
-                        popup_.addRooms(rooms);
-                        popup_.move(pos.x() - topLayout_->margin(), pos.y() + topLayout_->margin());
-                        popup_.show();
-                });
-
-        connect(roomSearch_, &QLineEdit::textEdited, this, [this](const QString &query) {
-                if (query.isEmpty()) {
-                        popup_.hide();
-                        return;
-                }
-
-                QtConcurrent::run([this, query = query.toLower()]() {
-                        try {
-                                emit queryResults(cache::searchRooms(query.toStdString()));
-                        } catch (const lmdb::error &e) {
-                                qWarning() << "room search failed:" << e.what();
-                        }
-                });
-        });
-
-        connect(roomSearch_,
-                &RoomSearchInput::selectNextCompletion,
-                &popup_,
-                &SuggestionsPopup::selectNextSuggestion);
-        connect(roomSearch_,
-                &RoomSearchInput::selectPreviousCompletion,
-                &popup_,
-                &SuggestionsPopup::selectPreviousSuggestion);
-        connect(&popup_, &SuggestionsPopup::itemSelected, this, [this](const QString &room_id) {
-                reset();
-                emit roomSelected(room_id);
-        });
-        connect(roomSearch_, &RoomSearchInput::hiding, this, [this]() { popup_.hide(); });
-        connect(roomSearch_, &QLineEdit::returnPressed, this, [this]() {
-                reset();
-                popup_.selectHoveredSuggestion();
-        });
-}
-
-void
-QuickSwitcher::paintEvent(QPaintEvent *)
-{
-        QStyleOption opt;
-        opt.init(this);
-        QPainter p(this);
-        style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
-}
-
-void
-QuickSwitcher::keyPressEvent(QKeyEvent *event)
-{
-        if (event->key() == Qt::Key_Escape) {
-                event->accept();
-                reset();
-        }
-}
diff --git a/src/QuickSwitcher.h b/src/QuickSwitcher.h
deleted file mode 100644
index 41c5301671d3a1446ee968ac3249d441c8815b9f..0000000000000000000000000000000000000000
--- a/src/QuickSwitcher.h
+++ /dev/null
@@ -1,65 +0,0 @@
-// SPDX-FileCopyrightText: 2017 Konstantinos Sideris <siderisk@auth.gr>
-// SPDX-FileCopyrightText: 2021 Nheko Contributors
-//
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#pragma once
-
-#include <QAbstractItemView>
-#include <QKeyEvent>
-#include <QVBoxLayout>
-#include <QWidget>
-
-#include "popups/SuggestionsPopup.h"
-#include "ui/TextField.h"
-
-class RoomSearchInput : public TextField
-{
-        Q_OBJECT
-public:
-        explicit RoomSearchInput(QWidget *parent = nullptr);
-
-signals:
-        void selectNextCompletion();
-        void selectPreviousCompletion();
-        void hiding();
-
-protected:
-        void keyPressEvent(QKeyEvent *event) override;
-        void hideEvent(QHideEvent *event) override;
-        bool focusNextPrevChild(bool) override { return false; };
-};
-
-class QuickSwitcher : public QWidget
-{
-        Q_OBJECT
-
-public:
-        QuickSwitcher(QWidget *parent = nullptr);
-
-signals:
-        void closing();
-        void roomSelected(const QString &roomid);
-        void queryResults(const std::vector<RoomSearchResult> &rooms);
-
-protected:
-        void keyPressEvent(QKeyEvent *event) override;
-        void showEvent(QShowEvent *) override { roomSearch_->setFocus(); }
-        void paintEvent(QPaintEvent *event) override;
-
-private:
-        void reset()
-        {
-                emit closing();
-                roomSearch_->clear();
-        }
-
-        // Current highlighted selection from the completer.
-        int selection_ = -1;
-
-        QVBoxLayout *topLayout_;
-        RoomSearchInput *roomSearch_;
-
-        //! Autocomplete popup box with the room suggestions.
-        SuggestionsPopup popup_;
-};
diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp
index 490fccc388fd2354a1114de056340d78fd904cd0..d5a6a1dd804cac02b568a6bb5b11ece3ffbca720 100644
--- a/src/timeline/InputBar.cpp
+++ b/src/timeline/InputBar.cpp
@@ -192,28 +192,6 @@ InputBar::nextText()
         return text();
 }
 
-QObject *
-InputBar::completerFor(QString completerName)
-{
-        if (completerName == "user") {
-                auto userModel = new UsersModel(room->roomId().toStdString());
-                auto proxy     = new CompletionProxyModel(userModel);
-                userModel->setParent(proxy);
-                return proxy;
-        } else if (completerName == "emoji") {
-                auto emojiModel = new emoji::EmojiModel();
-                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;
-}
-
 void
 InputBar::send()
 {
diff --git a/src/timeline/InputBar.h b/src/timeline/InputBar.h
index 5af46b0da445cdd7d671fe68071d37207fce04f6..acd9e22c9a9d8979e6364e831b81bd0f9a749ddc 100644
--- a/src/timeline/InputBar.h
+++ b/src/timeline/InputBar.h
@@ -55,8 +55,6 @@ public slots:
         bool uploading() const { return uploading_; }
         void message(QString body, MarkdownOverride useMarkdown = MarkdownOverride::NOT_SPECIFIED);
 
-        QObject *completerFor(QString completerName);
-
 private slots:
         void startTyping();
         void stopTyping();
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index 744278558f77e6b638d1940209b15893e0683d47..3ed1c21c9a1d0ed6f48c3bc3fbd8dc5cd58e48ce 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -15,13 +15,16 @@
 #include "BlurhashProvider.h"
 #include "ChatPage.h"
 #include "ColorImageProvider.h"
+#include "CompletionProxyModel.h"
 #include "DelegateChooser.h"
 #include "DeviceVerificationFlow.h"
 #include "Logging.h"
 #include "MainWindow.h"
 #include "MatrixClient.h"
 #include "MxcImageProvider.h"
+#include "RoomsModel.h"
 #include "UserSettingsPage.h"
+#include "UsersModel.h"
 #include "dialogs/ImageOverlay.h"
 #include "emoji/EmojiModel.h"
 #include "emoji/Provider.h"
@@ -334,6 +337,12 @@ TimelineViewManager::setHistoryView(const QString &room_id)
         }
 }
 
+void
+TimelineViewManager::highlightRoom(const QString &room_id)
+{
+        ChatPage::instance()->highlightRoom(room_id);
+}
+
 QString
 TimelineViewManager::escapeEmoji(QString str) const
 {
@@ -556,3 +565,36 @@ TimelineViewManager::focusMessageInput()
 {
         emit focusInput();
 }
+
+QObject *
+TimelineViewManager::completerFor(QString completerName, QString roomId)
+{
+        if (completerName == "user") {
+                auto userModel = new UsersModel(roomId.toStdString());
+                auto proxy     = new CompletionProxyModel(userModel);
+                userModel->setParent(proxy);
+                return proxy;
+        } else if (completerName == "emoji") {
+                auto emojiModel = new emoji::EmojiModel();
+                auto proxy      = new CompletionProxyModel(emojiModel);
+                emojiModel->setParent(proxy);
+                return proxy;
+        } else if (completerName == "room") {
+                auto roomModel = new RoomsModel(false);
+                auto proxy     = new CompletionProxyModel(roomModel, 4);
+                roomModel->setParent(proxy);
+                return proxy;
+        } else if (completerName == "roomAliases") {
+                auto roomModel = new RoomsModel(true);
+                auto proxy     = new CompletionProxyModel(roomModel);
+                roomModel->setParent(proxy);
+                return proxy;
+        }
+        return nullptr;
+}
+
+void
+TimelineViewManager::focusTimeline()
+{
+        getWidget()->setFocus();
+}
\ No newline at end of file
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 097fccfc76d1ad090719696abcf0ce6540623794..e3ed499103539b58820628e447200dfa88ce6bea 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -104,6 +104,8 @@ public slots:
         }
 
         void setHistoryView(const QString &room_id);
+        void highlightRoom(const QString &room_id);
+        void focusTimeline();
         TimelineModel *getHistoryView(const QString &room_id)
         {
                 auto room = models.find(room_id);
@@ -142,6 +144,7 @@ public slots:
         }
 
         void backToRooms() { emit showRoomList(); }
+        QObject *completerFor(QString completerName, QString roomId = "");
 
 private:
 #ifdef USE_QUICK_VIEW