diff --git a/src/api/common/notify.ts b/src/api/common/notify.ts
index e7ec37d4e4..4b3e6a5d54 100644
--- a/src/api/common/notify.ts
+++ b/src/api/common/notify.ts
@@ -27,4 +27,12 @@ export default (
 	// Publish notification event
 	event(notifiee, 'notification',
 		await serialize(notification));
+
+	// 3秒経っても(今回作成した)通知が既読にならなかったら「未読の通知がありますよ」イベントを発行する
+	setTimeout(async () => {
+		const fresh = await Notification.findOne({ _id: notification._id }, { is_read: true });
+		if (!fresh.is_read) {
+			event(notifiee, 'unread_notification', await serialize(notification));
+		}
+	}, 3000);
 });
diff --git a/src/web/app/desktop/tags/ui.tag b/src/web/app/desktop/tags/ui.tag
index f7e1bf7463..908257620e 100644
--- a/src/web/app/desktop/tags/ui.tag
+++ b/src/web/app/desktop/tags/ui.tag
@@ -214,7 +214,9 @@
 </mk-ui-header-post-button>
 
 <mk-ui-header-notifications>
-	<button class="header" data-active={ isOpen } onclick={ toggle } title="%i18n:desktop.tags.mk-ui-header-notifications.title%"><i class="fa fa-bell-o"></i></button>
+	<button data-active={ isOpen } onclick={ toggle } title="%i18n:desktop.tags.mk-ui-header-notifications.title%">
+		<i class="fa fa-bell-o icon"></i><i class="fa fa-circle badge" if={ hasUnreadNotifications }></i>
+	</button>
 	<div class="notifications" if={ isOpen }>
 		<mk-notifications/>
 	</div>
@@ -223,7 +225,7 @@
 			display block
 			float left
 
-			> .header
+			> button
 				display block
 				margin 0
 				padding 0
@@ -243,10 +245,16 @@
 				&:active
 					color darken(#9eaba8, 30%)
 
-				> i
+				> .icon
 					font-size 1.2em
 					line-height 48px
 
+				> .badge
+					margin-left -5px
+					vertical-align super
+					font-size 10px
+					color $theme-color
+
 			> .notifications
 				display block
 				position absolute
@@ -290,8 +298,53 @@
 	<script>
 		import contains from '../../common/scripts/contains';
 
+		this.mixin('i');
+		this.mixin('api');
+
+		if (this.SIGNIN) {
+			this.mixin('stream');
+			this.connection = this.stream.getConnection();
+			this.connectionId = this.stream.use();
+		}
+
 		this.isOpen = false;
 
+		this.on('mount', () => {
+			if (this.SIGNIN) {
+				this.connection.on('read_all_notifications', this.onReadAllNotifications);
+				this.connection.on('unread_notification', this.onUnreadNotification);
+
+				// Fetch count of unread notifications
+				this.api('notifications/get_unread_count').then(res => {
+					if (res.count > 0) {
+						this.update({
+							hasUnreadNotifications: true
+						});
+					}
+				});
+			}
+		});
+
+		this.on('unmount', () => {
+			if (this.SIGNIN) {
+				this.connection.off('read_all_notifications', this.onReadAllNotifications);
+				this.connection.off('unread_notification', this.onUnreadNotification);
+				this.stream.dispose(this.connectionId);
+			}
+		});
+
+		this.onReadAllNotifications = () => {
+			this.update({
+				hasUnreadNotifications: false
+			});
+		};
+
+		this.onUnreadNotification = () => {
+			this.update({
+				hasUnreadNotifications: true
+			});
+		};
+
 		this.toggle = () => {
 			this.isOpen ? this.close() : this.open();
 		};
@@ -424,9 +477,11 @@
 		this.mixin('i');
 		this.mixin('api');
 
-		this.mixin('stream');
-		this.connection = this.stream.getConnection();
-		this.connectionId = this.stream.use();
+		if (this.SIGNIN) {
+			this.mixin('stream');
+			this.connection = this.stream.getConnection();
+			this.connectionId = this.stream.use();
+		}
 
 		this.page = this.opts.page;