diff --git a/CHANGELOG.md b/CHANGELOG.md
index 215555df7222324461451cafd4a21aa001635f2a..6550db98039c36bd4c1768d9160e16470064e9d5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,7 +14,9 @@
 - アカウント登録にメールアドレスの設定を必須にするオプション
 - クライアント: アニメーションを減らす設定をメニューのアニメーションにも適用するように
 - クライアント: MFM関数構文のサジェストを実装
+- クライアント: 未読の通知のみ表示する機能
 - ActivityPub: HTML -> MFMの変換を強化
+- API: i/notifications に unreadOnly オプションを追加
 - API: ap系のエンドポイントをログイン必須化+レートリミット追加
 - Misskeyのコマンドラインオプションを廃止
 	- 代わりに環境変数で設定することができます
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 0be87cf2c500cab37fa35d4b47cf346f26262563..eb72c7ca17b9bca0ed5e3951e2de18e5bd7657c4 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -792,6 +792,7 @@ unresolved: "未解決"
 itsOn: "オンになっています"
 itsOff: "オフになっています"
 emailRequiredForSignup: "アカウント登録にメールアドレスを必須にする"
+unread: "未読"
 
 _signup:
   almostThere: "ほとんど完了です"
diff --git a/src/client/components/notifications.vue b/src/client/components/notifications.vue
index e91f18a69300f40fe06f27ca7b536d8e45ba8560..8be1e191b99f8aea269abc0870a21fcec8cf5dfd 100644
--- a/src/client/components/notifications.vue
+++ b/src/client/components/notifications.vue
@@ -48,6 +48,11 @@ export default defineComponent({
 			required: false,
 			default: null,
 		},
+		unreadOnly: {
+			type: Boolean,
+			required: false,
+			default: false,
+		},
 	},
 
 	data() {
@@ -58,6 +63,7 @@ export default defineComponent({
 				limit: 10,
 				params: () => ({
 					includeTypes: this.allIncludeTypes || undefined,
+					unreadOnly: this.unreadOnly,
 				})
 			},
 		};
@@ -76,6 +82,11 @@ export default defineComponent({
 			},
 			deep: true
 		},
+		unreadOnly: {
+			handler() {
+				this.reload();
+			},
+		},
 		// TODO: vue/vuexのバグか仕様かは不明なものの、プロフィール更新するなどして $i が更新されると、
 		// mutingNotificationTypes に変化が無くてもこのハンドラーが呼び出され無駄なリロードが発生するのを直す
 		'$i.mutingNotificationTypes': {
diff --git a/src/client/pages/notifications.vue b/src/client/pages/notifications.vue
index 6cbcc9b8e5a846e2dc8d3aa8c904e3fcf48fc173..9728b87bf1a4b6397f97ed05e2e2bbe911d46d23 100644
--- a/src/client/pages/notifications.vue
+++ b/src/client/pages/notifications.vue
@@ -2,13 +2,13 @@
 <div>
 	<MkHeader :info="header"/>
 	<div class="clupoqwt" v-size="{ min: [800] }">
-		<XNotifications class="notifications" @before="before" @after="after" page/>
+		<XNotifications class="notifications" @before="before" @after="after" :unread-only="tab === 'unread'"/>
 	</div>
 </div>
 </template>
 
 <script lang="ts">
-import { defineComponent } from 'vue';
+import { computed, defineComponent } from 'vue';
 import Progress from '@client/scripts/loading';
 import XNotifications from '@client/components/notifications.vue';
 import * as os from '@client/os';
@@ -26,7 +26,8 @@ export default defineComponent({
 				icon: 'fas fa-bell',
 				bg: 'var(--bg)',
 			},
-			header: {
+			tab: 'all',
+			header: computed(() => ({
 				title: this.$ts.notifications,
 				icon: 'fas fa-bell',
 				bg: 'var(--bg)',
@@ -35,9 +36,18 @@ export default defineComponent({
 					icon: 'fas fa-check',
 					handler: () => {
 						os.apiWithDialog('notifications/mark-all-as-read');
-					}
-				}]
-			},
+					},
+				}],
+				tabs: [{
+					active: this.tab === 'all',
+					title: this.$ts.all,
+					onClick: () => { this.tab = 'all'; },
+				}, {
+					active: this.tab === 'unread',
+					title: this.$ts.unread,
+					onClick: () => { this.tab = 'unread'; },
+				},]
+			})),
 		};
 	},
 
diff --git a/src/server/api/endpoints/i/notifications.ts b/src/server/api/endpoints/i/notifications.ts
index 3c265a10c173bdd86c795f0ea59d0d4a0a00a1c3..0c5586054ff00d451e40e6d70993b9452cdd96ce 100644
--- a/src/server/api/endpoints/i/notifications.ts
+++ b/src/server/api/endpoints/i/notifications.ts
@@ -33,6 +33,11 @@ export const meta = {
 			default: false
 		},
 
+		unreadOnly: {
+			validator: $.optional.bool,
+			default: false
+		},
+
 		markAsRead: {
 			validator: $.optional.bool,
 			default: true
@@ -105,6 +110,10 @@ export default define(meta, async (ps, user) => {
 		query.andWhere(`notification.type NOT IN (:...excludeTypes)`, { excludeTypes: ps.excludeTypes });
 	}
 
+	if (ps.unreadOnly) {
+		query.andWhere(`notification.isRead = false`);
+	}
+
 	const notifications = await query.take(ps.limit!).getMany();
 
 	// Mark all as read