diff --git a/src/contents/ui/Notifier.qml b/src/contents/ui/Notifier.qml
--- a/src/contents/ui/Notifier.qml
+++ b/src/contents/ui/Notifier.qml
@@ -18,6 +18,7 @@
   }
 
   property var nameProvider: Kazv.UserNameProvider {}
+  property var roomNameProvider: Kazv.RoomNameProvider {}
 
   property var conn: Connections {
     target: matrixSdk
@@ -35,7 +36,7 @@
             roomId,
           });
         notification.eventId = matrixSdk.shouldPlaySound(event) ? 'message' : 'messageWithoutSound';
-        notification.title = Helpers.roomNameOrHeroes(room, l10n);
+        notification.title = roomNameProvider.getName(room);
         const message = event.content.body;
         notification.text = message
           ? l10n.get('notification-message', { user: senderName, message })
diff --git a/src/contents/ui/RoomListViewItemDelegate.qml b/src/contents/ui/RoomListViewItemDelegate.qml
--- a/src/contents/ui/RoomListViewItemDelegate.qml
+++ b/src/contents/ui/RoomListViewItemDelegate.qml
@@ -22,7 +22,10 @@
   property var item
   property var iconSize: 1
   property var isFavourite: item.tagIds.includes('m.favourite')
-  property var roomDisplayName: Helpers.roomNameOrHeroes(item, l10n)
+  property var roomNameProvider: Kazv.RoomNameProvider {
+    room: upper.item
+  }
+  property var roomDisplayName: roomNameProvider.name
   property var roomTimeline: item.timeline()
   property var lastUnreadMessage: getLastUnreadMessage(roomTimeline, roomTimeline.count)
   property var lastUnreadMessageReaders: lastUnreadMessage ? lastUnreadMessage.readers() : null
@@ -63,6 +66,7 @@
     }
 
     Label {
+      objectName: 'roomDisplayNameLabel'
       text: roomDisplayName
       Layout.fillWidth: true
       elide: Text.ElideRight
diff --git a/src/contents/ui/RoomNameProvider.qml b/src/contents/ui/RoomNameProvider.qml
new file mode 100644
--- /dev/null
+++ b/src/contents/ui/RoomNameProvider.qml
@@ -0,0 +1,29 @@
+/*
+ * This file is part of kazv.
+ * SPDX-FileCopyrightText: 2024 tusooa <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15
+
+import 'matrix-helpers.js' as Helpers
+
+QtObject {
+  property var room
+  property var name: getName(room)
+
+  function getName(r) {
+    if (!r) {
+      return '';
+    }
+
+    return Helpers.roomNameOrHeroes(
+      r,
+      r.heroEvents,
+      sdkVars.userGivenNicknameMap.map,
+      l10n,
+    );
+  }
+}
diff --git a/src/contents/ui/RoomPage.qml b/src/contents/ui/RoomPage.qml
--- a/src/contents/ui/RoomPage.qml
+++ b/src/contents/ui/RoomPage.qml
@@ -19,13 +19,16 @@
   id: roomPage
   property string roomId: ''
   property var room: sdkVars.roomList.room(roomId)
+  property var roomNameProvider: Kazv.RoomNameProvider {
+    room: roomPage.room
+  }
   property var roomTimeline: room.timeline()
   property var lastReceiptableEventId: getLastReceiptableEventId(roomTimeline, roomTimeline.count)
 
   signal mentionUserRequested(string userId)
   signal replaceDraftRequested(string newDraft)
 
-  title: room.name || roomId
+  title: roomNameProvider.name
 
   property var isInvite: room.membership === MK.MatrixRoom.Invite
   property var isJoin: room.membership === MK.MatrixRoom.Join
diff --git a/src/contents/ui/UserPage.qml b/src/contents/ui/UserPage.qml
--- a/src/contents/ui/UserPage.qml
+++ b/src/contents/ui/UserPage.qml
@@ -24,7 +24,10 @@
     user: userPage.user
   }
   property var room
-  property var roomName: Helpers.roomNameOrHeroes(room, l10n)
+  property var roomNameProvider: Kazv.RoomNameProvider {
+    room: userPage.room
+  }
+  property var roomName: roomNameProvider.name
   property var editingPowerLevel: false
   property var submittingPowerLevel: false
   property var powerLevelsLoaded: false
diff --git a/src/contents/ui/room-settings/RoomInvitePage.qml b/src/contents/ui/room-settings/RoomInvitePage.qml
--- a/src/contents/ui/room-settings/RoomInvitePage.qml
+++ b/src/contents/ui/room-settings/RoomInvitePage.qml
@@ -17,7 +17,10 @@
   id: roomInvitePage
 
   property var room
-  property var roomDisplayName: Helpers.roomNameOrHeroes(room, l10n)
+  property var roomNameProvider: Kazv.RoomNameProvider {
+    room: roomInvitePage.room
+  }
+  property var roomDisplayName: roomNameProvider.name
   property var inviting: false
 
   title: l10n.get('room-invite-page-title', { room: roomDisplayName })
diff --git a/src/contents/ui/room-settings/RoomMemberListPage.qml b/src/contents/ui/room-settings/RoomMemberListPage.qml
--- a/src/contents/ui/room-settings/RoomMemberListPage.qml
+++ b/src/contents/ui/room-settings/RoomMemberListPage.qml
@@ -19,7 +19,10 @@
 
   property var room
   required property var members
-  property var roomDisplayName: Helpers.roomNameOrHeroes(room, l10n)
+  property var roomNameProvider: Kazv.RoomNameProvider {
+    room: roomSettingsPage.room
+  }
+  property var roomDisplayName: roomNameProvider.name
   title: l10n.get('room-member-list-page-title', { room: roomDisplayName })
 
   Kazv.RoomMemberListView {
diff --git a/src/contents/ui/room-settings/RoomSettingsPage.qml b/src/contents/ui/room-settings/RoomSettingsPage.qml
--- a/src/contents/ui/room-settings/RoomSettingsPage.qml
+++ b/src/contents/ui/room-settings/RoomSettingsPage.qml
@@ -19,7 +19,10 @@
   id: roomSettingsPage
 
   property var room
-  property var roomDisplayName: Helpers.roomNameOrHeroes(room, l10n)
+  property var roomNameProvider: Kazv.RoomNameProvider {
+    room: roomSettingsPage.room
+  }
+  property var roomDisplayName: roomNameProvider.name
   property var customTagIds: room.tagIds.filter(k => k.startsWith('u.'))
 
   title: l10n.get('room-settings-page-title', { room: roomDisplayName })
diff --git a/src/contents/ui/room-settings/RoomTagHandler.qml b/src/contents/ui/room-settings/RoomTagHandler.qml
--- a/src/contents/ui/room-settings/RoomTagHandler.qml
+++ b/src/contents/ui/room-settings/RoomTagHandler.qml
@@ -15,8 +15,12 @@
 import '..' as Kazv
 
 QtObject {
+  id: roomTagHandler
   property var room
-  property var roomDisplayName: Helpers.roomNameOrHeroes(room, l10n)
+  property var roomNameProvider: Kazv.RoomNameProvider {
+    room: roomTagHandler.room
+  }
+  property var roomDisplayName: roomNameProvider.name
   property var tagIds: room.tagIds
   property var available: true
   property var tagIdInProgress: ''
diff --git a/src/js/matrix-helpers.js b/src/js/matrix-helpers.js
--- a/src/js/matrix-helpers.js
+++ b/src/js/matrix-helpers.js
@@ -4,7 +4,7 @@
   return room.name || room.heroNames[0] || room.roomId;
 }
 
-function roomNameOrHeroes(room, l10n)
+function roomNameOrHeroes(room, heroes, userNameOverrides, l10n)
 {
   if (room.name) {
     return l10n.get(
@@ -13,13 +13,19 @@
         name: room.name,
       }
     );
-  } else if (room.heroNames.length) {
+  } else if (room.heroEvents && room.heroEvents.length) {
+    const heroNames = room.heroEvents.map(ev => {
+      const displayName = ev.content && ev.content.displayname || '';
+      const userId = ev.state_key;
+      return userDisplayName(displayName, userId, userNameOverrides, l10n);
+    });
+
     return l10n.get(
       'room-list-view-room-item-title-heroes',
       {
-        hero: room.heroNames[0],
-        secondHero: room.heroNames[1],
-        otherNum: room.heroNames.length - 1,
+        hero: heroNames[0],
+        secondHero: heroNames[1],
+        otherNum: heroNames.length - 1,
       }
     );
   } else {
diff --git a/src/matrix-room.hpp b/src/matrix-room.hpp
--- a/src/matrix-room.hpp
+++ b/src/matrix-room.hpp
@@ -51,6 +51,7 @@
     LAGER_QT_READER(QString, roomId);
     LAGER_QT_READER(QString, name);
     LAGER_QT_READER(QStringList, heroNames);
+    LAGER_QT_READER(QVariant, heroEvents);
     LAGER_QT_READER(QString, avatarMxcUri);
     LAGER_QT_READER(QString, roomOrHeroAvatarMxcUri);
     LAGER_QT_READER(QString, localDraft);
diff --git a/src/matrix-room.cpp b/src/matrix-room.cpp
--- a/src/matrix-room.cpp
+++ b/src/matrix-room.cpp
@@ -22,7 +22,7 @@
 #include "matrix-promise.hpp"
 #include "matrix-event.hpp"
 #include "kazv-markdown.hpp"
-
+#include "qt-json.hpp"
 #include "qfunctionutils.hpp"
 
 #include "helper.hpp"
@@ -42,6 +42,17 @@
     , LAGER_QT(roomId)(m_room.roomId().xform(strToQt))
     , LAGER_QT(name)(m_room.nameOpt()[lager::lenses::or_default].xform(strToQt))
     , LAGER_QT(heroNames)(m_room.heroDisplayNames().xform(strListToQt))
+    , LAGER_QT(heroEvents)(m_room.heroMemberEvents()
+        .map([](const auto &events) {
+            auto res = QList<QVariant>{};
+            res.reserve(events.size());
+            std::transform(events.begin(), events.end(),
+                std::back_inserter(res),
+                [](const auto &event) -> QVariant {
+                    return event.originalJson().get().template get<QJsonObject>();
+                });
+            return QVariant(res);
+        }))
     , LAGER_QT(avatarMxcUri)(m_room.avatarMxcUri().xform(strToQt))
     , LAGER_QT(roomOrHeroAvatarMxcUri)(lager::with(m_room.heroMemberEvents(), m_room.avatarMxcUri())
         .map([](const auto &heroes, const auto &roomAvatar) {
diff --git a/src/resources.qrc b/src/resources.qrc
--- a/src/resources.qrc
+++ b/src/resources.qrc
@@ -49,6 +49,7 @@
         <file alias="RoomMemberListView.qml">contents/ui/RoomMemberListView.qml</file>
         <file alias="RoomMemberListViewItemDelegate.qml">contents/ui/RoomMemberListViewItemDelegate.qml</file>
         <file alias="UserNameProvider.qml">contents/ui/UserNameProvider.qml</file>
+        <file alias="RoomNameProvider.qml">contents/ui/RoomNameProvider.qml</file>
 
         <file alias="AsyncHandler.qml">contents/ui/AsyncHandler.qml</file>
         <file alias="UploadFileHelper.qml">contents/ui/UploadFileHelper.qml</file>
diff --git a/src/tests/quick-tests/tst_RoomListViewItemDelegate.qml b/src/tests/quick-tests/tst_RoomListViewItemDelegate.qml
--- a/src/tests/quick-tests/tst_RoomListViewItemDelegate.qml
+++ b/src/tests/quick-tests/tst_RoomListViewItemDelegate.qml
@@ -121,6 +121,13 @@
   property var matrixSdk: TestHelpers.MatrixSdkMock {
     property var userId: '@foo:example.org'
   }
+  property var sdkVars: QtObject {
+    property var userGivenNicknameMap: QtObject {
+      property var map: ({
+        '@foo:example.com': 'something',
+      })
+    }
+  }
   property string testRoomId: 'room-test'
 
   signal switchToRoomRequested(string roomId)
@@ -153,6 +160,34 @@
         roomId: testRoomId
       })
     }
+
+    Kazv.RoomListViewItemDelegate {
+      id: delegateRoomWithName
+      item: QtObject {
+        property var roomId: testRoomId
+        property var name: 'some name'
+      }
+    }
+
+    Kazv.RoomListViewItemDelegate {
+      id: delegateRoomWithHeroes
+      item: QtObject {
+        property var roomId: testRoomId
+        property var heroEvents: [{
+          type: 'm.room.member',
+          state_key: '@foo:example.com',
+          content: {
+            displayname: 'foo',
+          },
+        }, {
+          type: 'm.room.member',
+          state_key: '@bar:example.com',
+          content: {
+            displayname: 'bar',
+          },
+        }]
+      }
+    }
   }
 
   TestCase {
@@ -204,5 +239,21 @@
       tryCompare(switchToRoomSpy, 'count', 1);
       verify(Helpers.deepEqual(switchToRoomSpy.signalArguments[0], [testRoomId]));
     }
+
+    function test_roomName() {
+      compare(
+        findChild(delegateRoomWithName, 'roomDisplayNameLabel').text,
+        l10n.get('room-list-view-room-item-title-name', { name: 'some name' }));
+
+      compare(
+        findChild(delegateRoomWithHeroes, 'roomDisplayNameLabel').text,
+        l10n.get('room-list-view-room-item-title-heroes', {
+          hero: l10n.get(
+            'user-name-overrided',
+            { overridedName: 'something', globalName: 'foo' }),
+          secondHero: 'bar',
+          otherNum: 1,
+        }));
+    }
   }
 }
diff --git a/src/tests/quick-tests/tst_RoomNameProvider.qml b/src/tests/quick-tests/tst_RoomNameProvider.qml
new file mode 100644
--- /dev/null
+++ b/src/tests/quick-tests/tst_RoomNameProvider.qml
@@ -0,0 +1,164 @@
+/*
+ * This file is part of kazv.
+ * SPDX-FileCopyrightText: 2023 tusooa <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import QtTest 1.0
+
+import '../../contents/ui' as Kazv
+import './test-helpers' as TestHelpers
+import 'test-helpers.js' as Helpers
+
+TestCase {
+  id: roomNameProviderTest
+  name: 'RoomNameProviderTest'
+  when: true
+
+  property var sdkVars: QtObject {
+    property var userGivenNicknameMap: QtObject {
+      property var map: ({})
+    }
+  }
+  property var l10n: Helpers.fluentMock
+
+  property var providerFunctional: Kazv.RoomNameProvider {}
+  property var providerReactive: Kazv.RoomNameProvider {}
+  property var roomComp: Component {
+    QtObject {
+      property var name
+      property var heroEvents
+      property var roomId
+    }
+  }
+  property var heroEvents: [{
+    type: 'm.room.member',
+    state_key: '@foo:example.com',
+    content: {
+      displayname: 'foo',
+    },
+  }, {
+    type: 'm.room.member',
+    state_key: '@bar:example.com',
+    content: {
+      displayname: 'bar',
+    },
+  }]
+
+  function init() {
+    sdkVars.userGivenNicknameMap.map = {
+      '@foo:example.com': 'something',
+    };
+  }
+
+  function test_functional() {
+    compare(
+      providerFunctional.getName({ name: 'some name' }),
+      l10n.get(
+        'room-list-view-room-item-title-name',
+        { name: 'some name' }));
+
+    compare(providerFunctional.getName({
+      heroEvents
+    }), l10n.get('room-list-view-room-item-title-heroes', {
+      hero: l10n.get(
+        'user-name-overrided',
+        { overridedName: 'something', globalName: 'foo' }),
+      secondHero: 'bar',
+      otherNum: 1,
+    }));
+
+    compare(
+      providerFunctional.getName({ roomId: '!room:example.com' }),
+      l10n.get(
+        'room-list-view-room-item-title-id',
+        { roomId: '!room:example.com' }));
+  }
+
+  function test_reactiveAgainstRoom() {
+    providerReactive.room = roomComp.createObject(roomNameProviderTest, {
+      name: 'some name',
+      heroEvents,
+      roomId: '!room:example.com',
+    });
+    compare(
+      providerReactive.name,
+      l10n.get(
+        'room-list-view-room-item-title-name',
+        { name: 'some name' }));
+    providerReactive.room.name = 'other name';
+    compare(
+      providerReactive.name,
+      l10n.get(
+        'room-list-view-room-item-title-name',
+        { name: 'other name' }));
+    providerReactive.room.name = undefined;
+    compare(
+      providerReactive.name,
+      l10n.get('room-list-view-room-item-title-heroes', {
+        hero: l10n.get(
+          'user-name-overrided',
+          { overridedName: 'something', globalName: 'foo' }),
+        secondHero: 'bar',
+        otherNum: 1,
+      }));
+
+    providerReactive.room.heroEvents = [heroEvents[0]];
+    compare(
+      providerReactive.name,
+      l10n.get('room-list-view-room-item-title-heroes', {
+        hero: l10n.get(
+          'user-name-overrided',
+          { overridedName: 'something', globalName: 'foo' }),
+        secondHero: undefined,
+        otherNum: 0,
+      }));
+
+    providerReactive.room.heroEvents = [];
+    compare(
+      providerReactive.name,
+      l10n.get('room-list-view-room-item-title-id', {
+        roomId: '!room:example.com',
+      }));
+
+    providerReactive.room = roomComp.createObject(roomNameProviderTest, {
+      name: 'some name 2',
+      heroEvents,
+      roomId: '!room:example.com',
+    });
+    compare(
+      providerReactive.name,
+      l10n.get(
+        'room-list-view-room-item-title-name',
+        { name: 'some name 2' }));
+  }
+
+  function test_reactiveAgainstOverrides() {
+    providerReactive.room = roomComp.createObject(roomNameProviderTest, {
+      heroEvents,
+      roomId: '!room:example.com',
+    });
+
+    compare(
+      providerReactive.name,
+      l10n.get('room-list-view-room-item-title-heroes', {
+        hero: l10n.get(
+          'user-name-overrided',
+          { overridedName: 'something', globalName: 'foo' }),
+        secondHero: 'bar',
+        otherNum: 1,
+      }));
+    sdkVars.userGivenNicknameMap.map = {
+    };
+
+    compare(
+      providerReactive.name,
+      l10n.get('room-list-view-room-item-title-heroes', {
+        hero: 'foo',
+        secondHero: 'bar',
+        otherNum: 1,
+      }));
+  }
+}