diff --git a/include/timeline/TimelineItem.h b/include/timeline/TimelineItem.h
index b72c3591234b52d7c264201f6e0a45c784e9508b..9997ec1dbef38070fd22022c8fe25c77f8c28b64 100644
--- a/include/timeline/TimelineItem.h
+++ b/include/timeline/TimelineItem.h
@@ -18,6 +18,7 @@
 #pragma once
 
 #include <QAbstractTextDocumentLayout>
+#include <QApplication>
 #include <QDateTime>
 #include <QHBoxLayout>
 #include <QLabel>
@@ -78,6 +79,42 @@ private slots:
         void adjustHeight(const QSizeF &size) { setFixedHeight(size.height()); }
 };
 
+class UserProfileFilter : public QObject
+{
+        Q_OBJECT
+
+public:
+        explicit UserProfileFilter(const QString &user_id, QLabel *parent)
+          : QObject(parent)
+          , user_id_{user_id}
+        {}
+
+signals:
+        void hoverOff();
+        void hoverOn();
+
+protected:
+        bool eventFilter(QObject *obj, QEvent *event)
+        {
+                if (event->type() == QEvent::MouseButtonRelease) {
+                        // QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
+                        // TODO: Open user profile
+                        return true;
+                } else if (event->type() == QEvent::HoverLeave) {
+                        emit hoverOff();
+                        return true;
+                } else if (event->type() == QEvent::HoverEnter) {
+                        emit hoverOn();
+                        return true;
+                }
+
+                return QObject::eventFilter(obj, event);
+        }
+
+private:
+        QString user_id_;
+};
+
 class TimelineItem : public QWidget
 {
         Q_OBJECT
@@ -182,7 +219,7 @@ private:
         void setupWidgetLayout(Widget *widget, const Event &event, bool withSender);
 
         void generateBody(const QString &body);
-        void generateBody(const QString &userid, const QString &body);
+        void generateBody(const QString &user_id, const QString &displayname, const QString &body);
         void generateTimestamp(const QDateTime &time);
 
         void setupAvatarLayout(const QString &userName);
@@ -237,7 +274,7 @@ TimelineItem::setupLocalWidgetLayout(Widget *widget, const QString &userid, bool
         widgetLayout_->addStretch(1);
 
         if (withSender) {
-                generateBody(displayName, "");
+                generateBody(userid, displayName, "");
                 setupAvatarLayout(displayName);
 
                 headerLayout_->addLayout(widgetLayout_);
@@ -283,7 +320,7 @@ TimelineItem::setupWidgetLayout(Widget *widget, const Event &event, bool withSen
         widgetLayout_->addStretch(1);
 
         if (withSender) {
-                generateBody(displayName, "");
+                generateBody(sender, displayName, "");
                 setupAvatarLayout(displayName);
 
                 headerLayout_->addLayout(widgetLayout_);
diff --git a/src/timeline/TimelineItem.cc b/src/timeline/TimelineItem.cc
index 47bc20ead06624fe84858b5ab5f645fdb3d2afa8..250373e47d1fcb7ed096f755953b07fe85f00a60 100644
--- a/src/timeline/TimelineItem.cc
+++ b/src/timeline/TimelineItem.cc
@@ -124,7 +124,7 @@ TimelineItem::TimelineItem(mtx::events::MessageType ty,
         generateTimestamp(timestamp);
 
         if (withSender) {
-                generateBody(displayName, body);
+                generateBody(userid, displayName, body);
                 setupAvatarLayout(displayName);
 
                 messageLayout_->addLayout(headerLayout_, 1);
@@ -292,7 +292,7 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Notice
         if (with_sender) {
                 auto displayName = Cache::displayName(room_id_, sender);
 
-                generateBody(displayName, body);
+                generateBody(sender, displayName, body);
                 setupAvatarLayout(displayName);
 
                 messageLayout_->addLayout(headerLayout_, 1);
@@ -339,7 +339,7 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Emote>
         emoteMsg.replace("\n", "<br/>");
 
         if (with_sender) {
-                generateBody(displayName, emoteMsg);
+                generateBody(sender, displayName, emoteMsg);
                 setupAvatarLayout(displayName);
 
                 messageLayout_->addLayout(headerLayout_, 1);
@@ -391,7 +391,7 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Text>
         body.replace("\n", "<br/>");
 
         if (with_sender) {
-                generateBody(displayName, body);
+                generateBody(sender, displayName, body);
                 setupAvatarLayout(displayName);
 
                 messageLayout_->addLayout(headerLayout_, 1);
@@ -435,14 +435,14 @@ TimelineItem::generateBody(const QString &body)
 
 // The username/timestamp is displayed along with the message body.
 void
-TimelineItem::generateBody(const QString &userid, const QString &body)
+TimelineItem::generateBody(const QString &user_id, const QString &displayname, const QString &body)
 {
-        auto sender = userid;
+        auto sender = displayname;
 
-        if (userid.startsWith("@")) {
+        if (displayname.startsWith("@")) {
                 // TODO: Fix this by using a UserId type.
-                if (userid.split(":")[0].split("@").size() > 1)
-                        sender = userid.split(":")[0].split("@")[1];
+                if (displayname.split(":")[0].split("@").size() > 1)
+                        sender = displayname.split(":")[0].split("@")[1];
         }
 
         QFontMetrics fm(usernameFont_);
@@ -450,6 +450,28 @@ TimelineItem::generateBody(const QString &userid, const QString &body)
         userName_ = new QLabel(this);
         userName_->setFont(usernameFont_);
         userName_->setText(fm.elidedText(sender, Qt::ElideRight, 500));
+        userName_->setToolTip(user_id);
+        userName_->setToolTipDuration(1500);
+        userName_->setAttribute(Qt::WA_Hover);
+        userName_->setAlignment(Qt::AlignLeft);
+        userName_->setFixedWidth(QFontMetrics(userName_->font()).width(userName_->text()));
+
+        auto filter = new UserProfileFilter(user_id, userName_);
+        userName_->installEventFilter(filter);
+
+        connect(filter, &UserProfileFilter::hoverOn, this, [this]() {
+                QFont f = userName_->font();
+                f.setUnderline(true);
+                userName_->setCursor(Qt::PointingHandCursor);
+                userName_->setFont(f);
+        });
+
+        connect(filter, &UserProfileFilter::hoverOff, this, [this]() {
+                QFont f = userName_->font();
+                f.setUnderline(false);
+                userName_->setCursor(Qt::ArrowCursor);
+                userName_->setFont(f);
+        });
 
         generateBody(body);
 }