diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp
index 6a90ab6ec4224481b297214ed300d0745e1cdeaf..87940948be76dea0643071de68bf5003411b4464 100644
--- a/src/timeline/RoomlistModel.cpp
+++ b/src/timeline/RoomlistModel.cpp
@@ -33,6 +33,27 @@ RoomlistModel::RoomlistModel(TimelineViewManager *parent)
                 &RoomlistModel::totalUnreadMessageCountUpdated,
                 ChatPage::instance(),
                 &ChatPage::unreadMessages);
+
+        connect(
+          this,
+          &RoomlistModel::fetchedPreview,
+          this,
+          [this](QString roomid, RoomInfo info) {
+                  if (this->previewedRooms.contains(roomid)) {
+                          this->previewedRooms.insert(roomid, std::move(info));
+                          auto idx = this->roomidToIndex(roomid);
+                          emit dataChanged(index(idx),
+                                           index(idx),
+                                           {
+                                             Roles::RoomName,
+                                             Roles::AvatarUrl,
+                                             Roles::IsSpace,
+                                             Roles::IsPreviewFetched,
+                                             Qt::DisplayRole,
+                                           });
+                  }
+          },
+          Qt::QueuedConnection);
 }
 
 QHash<int, QByteArray>
@@ -61,6 +82,16 @@ RoomlistModel::data(const QModelIndex &index, int role) const
         if (index.row() >= 0 && static_cast<size_t>(index.row()) < roomids.size()) {
                 auto roomid = roomids.at(index.row());
 
+                if (role == Roles::ParentSpaces) {
+                        auto parents = cache::client()->getParentRoomIds(roomid.toStdString());
+                        QStringList list;
+                        for (const auto &t : parents)
+                                list.push_back(QString::fromStdString(t));
+                        return list;
+                } else if (role == Roles::RoomId) {
+                        return roomid;
+                }
+
                 if (models.contains(roomid)) {
                         auto room = models.value(roomid);
                         switch (role) {
@@ -68,8 +99,6 @@ RoomlistModel::data(const QModelIndex &index, int role) const
                                 return room->roomAvatarUrl();
                         case Roles::RoomName:
                                 return room->plainRoomName();
-                        case Roles::RoomId:
-                                return room->roomId();
                         case Roles::LastMessage:
                                 return room->lastMessage().body;
                         case Roles::Time:
@@ -88,6 +117,8 @@ RoomlistModel::data(const QModelIndex &index, int role) const
                                 return false;
                         case Roles::IsSpace:
                                 return room->isSpace();
+                        case Roles::IsPreview:
+                                return false;
                         case Roles::Tags: {
                                 auto info = cache::singleRoomInfo(roomid.toStdString());
                                 QStringList list;
@@ -95,14 +126,6 @@ RoomlistModel::data(const QModelIndex &index, int role) const
                                         list.push_back(QString::fromStdString(t));
                                 return list;
                         }
-                        case Roles::ParentSpaces: {
-                                auto parents =
-                                  cache::client()->getParentRoomIds(roomid.toStdString());
-                                QStringList list;
-                                for (const auto &t : parents)
-                                        list.push_back(QString::fromStdString(t));
-                                return list;
-                        }
                         default:
                                 return {};
                         }
@@ -113,8 +136,6 @@ RoomlistModel::data(const QModelIndex &index, int role) const
                                 return QString::fromStdString(room.avatar_url);
                         case Roles::RoomName:
                                 return QString::fromStdString(room.name);
-                        case Roles::RoomId:
-                                return roomid;
                         case Roles::LastMessage:
                                 return QString();
                         case Roles::Time:
@@ -130,21 +151,77 @@ RoomlistModel::data(const QModelIndex &index, int role) const
                                 return true;
                         case Roles::IsSpace:
                                 return false;
+                        case Roles::IsPreview:
+                                return false;
                         case Roles::Tags:
                                 return QStringList();
-                        case Roles::ParentSpaces: {
-                                auto parents =
-                                  cache::client()->getParentRoomIds(roomid.toStdString());
-                                QStringList list;
-                                for (const auto &t : parents)
-                                        list.push_back(QString::fromStdString(t));
-                                return list;
+                        default:
+                                return {};
                         }
+                } else if (previewedRooms.contains(roomid) &&
+                           previewedRooms.value(roomid).has_value()) {
+                        auto room = previewedRooms.value(roomid).value();
+                        switch (role) {
+                        case Roles::AvatarUrl:
+                                return QString::fromStdString(room.avatar_url);
+                        case Roles::RoomName:
+                                return QString::fromStdString(room.name);
+                        case Roles::LastMessage:
+                                return tr("Previewing this room");
+                        case Roles::Time:
+                                return QString();
+                        case Roles::Timestamp:
+                                return QVariant(static_cast<quint64>(0));
+                        case Roles::HasUnreadMessages:
+                        case Roles::HasLoudNotification:
+                                return false;
+                        case Roles::NotificationCount:
+                                return 0;
+                        case Roles::IsInvite:
+                                return false;
+                        case Roles::IsSpace:
+                                return room.is_space;
+                        case Roles::IsPreview:
+                                return true;
+                        case Roles::IsPreviewFetched:
+                                return true;
+                        case Roles::Tags:
+                                return QStringList();
                         default:
                                 return {};
                         }
                 } else {
-                        return {};
+                        if (role == Roles::IsPreview)
+                                return true;
+                        else if (role == Roles::IsPreviewFetched)
+                                return false;
+
+                        fetchPreview(roomid);
+                        switch (role) {
+                        case Roles::AvatarUrl:
+                                return QString();
+                        case Roles::RoomName:
+                                return tr("No preview available");
+                        case Roles::LastMessage:
+                                return QString();
+                        case Roles::Time:
+                                return QString();
+                        case Roles::Timestamp:
+                                return QVariant(static_cast<quint64>(0));
+                        case Roles::HasUnreadMessages:
+                        case Roles::HasLoudNotification:
+                                return false;
+                        case Roles::NotificationCount:
+                                return 0;
+                        case Roles::IsInvite:
+                                return false;
+                        case Roles::IsSpace:
+                                return false;
+                        case Roles::Tags:
+                                return QStringList();
+                        default:
+                                return {};
+                        }
                 }
         } else {
                 return {};
@@ -248,25 +325,111 @@ RoomlistModel::addRoom(const QString &room_id, bool suppressInsertNotification)
 
                 newRoom->updateLastMessage();
 
-                bool wasInvite = invites.contains(room_id);
-                if (!suppressInsertNotification && !wasInvite)
-                        beginInsertRows(QModelIndex(), (int)roomids.size(), (int)roomids.size());
+                std::vector<QString> previewsToAdd;
+                if (newRoom->isSpace()) {
+                        auto childs = cache::client()->getChildRoomIds(room_id.toStdString());
+                        for (const auto &c : childs) {
+                                auto id = QString::fromStdString(c);
+                                if (!(models.contains(id) || invites.contains(id) ||
+                                      previewedRooms.contains(id))) {
+                                        previewsToAdd.push_back(std::move(id));
+                                }
+                        }
+                }
 
-                models.insert(room_id, std::move(newRoom));
+                bool wasInvite  = invites.contains(room_id);
+                bool wasPreview = previewedRooms.contains(room_id);
+                if (!suppressInsertNotification &&
+                    ((!wasInvite && !wasPreview) || !previewedRooms.empty()))
+                        // if the old room was already in the list, don't add it. Also add all
+                        // previews at the same time.
+                        beginInsertRows(QModelIndex(),
+                                        (int)roomids.size(),
+                                        (int)(roomids.size() + previewsToAdd.size() -
+                                              ((wasInvite || wasPreview) ? 0 : 1)));
 
+                models.insert(room_id, std::move(newRoom));
                 if (wasInvite) {
                         auto idx = roomidToIndex(room_id);
                         invites.remove(room_id);
                         emit dataChanged(index(idx), index(idx));
+                } else if (wasPreview) {
+                        auto idx = roomidToIndex(room_id);
+                        previewedRooms.remove(room_id);
+                        emit dataChanged(index(idx), index(idx));
                 } else {
                         roomids.push_back(room_id);
                 }
 
+                for (auto p : previewsToAdd) {
+                        previewedRooms.insert(p, std::nullopt);
+                        roomids.push_back(std::move(p));
+                }
+
                 if (!suppressInsertNotification && !wasInvite)
                         endInsertRows();
         }
 }
 
+void
+RoomlistModel::fetchPreview(QString roomid_) const
+{
+        std::string roomid = roomid_.toStdString();
+        http::client()->get_state_event<mtx::events::state::Create>(
+          roomid,
+          "",
+          [this, roomid](const mtx::events::state::Create &c, mtx::http::RequestErr err) {
+                  bool is_space = false;
+                  if (!err) {
+                          is_space = c.type == mtx::events::state::room_type::space;
+                  }
+
+                  http::client()->get_state_event<mtx::events::state::Avatar>(
+                    roomid,
+                    "",
+                    [this, roomid, is_space](const mtx::events::state::Avatar &a,
+                                             mtx::http::RequestErr) {
+                            auto avatar_url = a.url;
+
+                            http::client()->get_state_event<mtx::events::state::Topic>(
+                              roomid,
+                              "",
+                              [this, roomid, avatar_url, is_space](
+                                const mtx::events::state::Topic &t, mtx::http::RequestErr) {
+                                      auto topic = t.topic;
+                                      http::client()->get_state_event<mtx::events::state::Name>(
+                                        roomid,
+                                        "",
+                                        [this, roomid, topic, avatar_url, is_space](
+                                          const mtx::events::state::Name &n,
+                                          mtx::http::RequestErr err) {
+                                                if (err) {
+                                                        nhlog::net()->warn(
+                                                          "Failed to fetch name event to "
+                                                          "create preview for {}",
+                                                          roomid);
+                                                }
+
+                                                // don't even add a preview, if we got not a single
+                                                // response
+                                                if (n.name.empty() && avatar_url.empty() &&
+                                                    topic.empty())
+                                                        return;
+
+                                                RoomInfo info{};
+                                                info.name       = n.name;
+                                                info.is_space   = is_space;
+                                                info.avatar_url = avatar_url;
+                                                info.topic      = topic;
+
+                                                const_cast<RoomlistModel *>(this)->fetchedPreview(
+                                                  QString::fromStdString(roomid), info);
+                                        });
+                              });
+                    });
+          });
+}
+
 void
 RoomlistModel::sync(const mtx::responses::Rooms &rooms)
 {
@@ -426,7 +589,9 @@ RoomlistModel::setCurrentRoom(QString roomid)
 namespace {
 enum NotificationImportance : short
 {
-        ImportanceDisabled = -1,
+        ImportanceDisabled = -3,
+        NoPreview          = -2,
+        Preview            = -1,
         AllEventsRead      = 0,
         NewMessage         = 1,
         NewMentions        = 2,
@@ -448,6 +613,11 @@ FilteredRoomlistModel::calculateImportance(const QModelIndex &idx) const
                         return CurrentSpace;
                 else
                         return SubSpace;
+        } else if (sourceModel()->data(idx, RoomlistModel::IsPreview).toBool()) {
+                if (sourceModel()->data(idx, RoomlistModel::IsPreviewFetched).toBool())
+                        return Preview;
+                else
+                        return NoPreview;
         } else if (sourceModel()->data(idx, RoomlistModel::IsInvite).toBool()) {
                 return Invite;
         } else if (!this->sortByImportance) {
@@ -460,6 +630,7 @@ FilteredRoomlistModel::calculateImportance(const QModelIndex &idx) const
                 return AllEventsRead;
         }
 }
+
 bool
 FilteredRoomlistModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
 {
@@ -531,6 +702,12 @@ bool
 FilteredRoomlistModel::filterAcceptsRow(int sourceRow, const QModelIndex &) const
 {
         if (filterType == FilterBy::Nothing) {
+                if (sourceModel()
+                      ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsPreview)
+                      .toBool()) {
+                        return false;
+                }
+
                 if (sourceModel()
                       ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsSpace)
                       .toBool()) {
@@ -560,6 +737,12 @@ FilteredRoomlistModel::filterAcceptsRow(int sourceRow, const QModelIndex &) cons
 
                 return true;
         } else if (filterType == FilterBy::Tag) {
+                if (sourceModel()
+                      ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsPreview)
+                      .toBool()) {
+                        return false;
+                }
+
                 if (sourceModel()
                       ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsSpace)
                       .toBool()) {
diff --git a/src/timeline/RoomlistModel.h b/src/timeline/RoomlistModel.h
index d6cbb462b570eda16c360a70658be8f2493ba2ca..2005c35e39aefa0eaa9cdb2a51cb0bb9438501b0 100644
--- a/src/timeline/RoomlistModel.h
+++ b/src/timeline/RoomlistModel.h
@@ -37,6 +37,8 @@ public:
                 NotificationCount,
                 IsInvite,
                 IsSpace,
+                IsPreview,
+                IsPreviewFetched,
                 Tags,
                 ParentSpaces,
         };
@@ -87,15 +89,18 @@ private slots:
 signals:
         void totalUnreadMessageCountUpdated(int unreadMessages);
         void currentRoomChanged();
+        void fetchedPreview(QString roomid, RoomInfo info);
 
 private:
         void addRoom(const QString &room_id, bool suppressInsertNotification = false);
+        void fetchPreview(QString roomid) const;
 
         TimelineViewManager *manager = nullptr;
         std::vector<QString> roomids;
         QHash<QString, RoomInfo> invites;
         QHash<QString, QSharedPointer<TimelineModel>> models;
         std::map<QString, bool> roomReadStatus;
+        QHash<QString, std::optional<RoomInfo>> previewedRooms;
 
         QSharedPointer<TimelineModel> currentRoom_;