diff --git a/locales/en-US.yml b/locales/en-US.yml
index 1bdf57fae..3f3ab32b2 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -612,6 +612,7 @@ regexpError: "Regular Expression error"
 regexpErrorDescription: "An error occurred in the regular expression on line {line} of your {tab} word mutes:"
 instanceMute: "Instance Mutes"
 userSaysSomething: "{name} said something"
+userSaysSomethingReason: "{name} said {reason}"
 makeActive: "Activate"
 display: "Display"
 copy: "Copy"
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 26971184c..6af0d7fc6 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -612,6 +612,7 @@ regexpError: "正規表現エラー"
 regexpErrorDescription: "{tab}ワードミュートの{line}行目の正規表現にエラーが発生しました:"
 instanceMute: "インスタンスミュート"
 userSaysSomething: "{name}が何かを言いました"
+userSaysSomethingReason: "{name}前記{reason}"
 makeActive: "アクティブにする"
 display: "表示"
 copy: "コピー"
diff --git a/packages/backend/src/misc/check-word-mute.ts b/packages/backend/src/misc/check-word-mute.ts
index ffdf3caf8..53193d851 100644
--- a/packages/backend/src/misc/check-word-mute.ts
+++ b/packages/backend/src/misc/check-word-mute.ts
@@ -5,46 +5,74 @@ import type { User } from "@/models/entities/user.js";
 type NoteLike = {
 	userId: Note["userId"];
 	text: Note["text"];
+	cw?: Note["cw"];
 };
 
 type UserLike = {
 	id: User["id"];
 };
 
-export async function checkWordMute(
+export type Muted = {
+	muted: boolean;
+	matched: string[];
+};
+
+const NotMuted = { muted: false, matched: [] };
+
+function escapeRegExp(x: string) {
+	return x.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
+}
+
+export async function getWordMute(
 	note: NoteLike,
 	me: UserLike | null | undefined,
 	mutedWords: Array<string | string[]>,
-): Promise<boolean> {
+): Promise<Muted> {
 	// 自分自身
-	if (me && note.userId === me.id) return false;
+	if (me && note.userId === me.id) {
+		return NotMuted;
+	}
 
 	if (mutedWords.length > 0) {
 		const text = ((note.cw ?? "") + "\n" + (note.text ?? "")).trim();
 
-		if (text === "") return false;
+		if (text === "") {
+			return NotMuted;
+		}
 
-		const matched = mutedWords.some((filter) => {
-			if (Array.isArray(filter)) {
-				return filter.every((keyword) => text.includes(keyword));
-			} else {
-				// represents RegExp
-				const regexp = filter.match(/^\/(.+)\/(.*)$/);
+		for (const mutePattern of mutedWords) {
+			let mute: RE2;
+			let matched: string[];
+			if (Array.isArray(mutePattern)) {
+				matched = mutePattern.filter((keyword) => keyword !== "");
 
-				// This should never happen due to input sanitisation.
-				if (!regexp) return false;
-
-				try {
-					return new RE2(regexp[1], regexp[2]).test(text);
-				} catch (err) {
-					// This should never happen due to input sanitisation.
-					return false;
+				if (matched.length === 0) {
+					continue;
 				}
+				mute = new RE2(
+					`\\b${matched.map(escapeRegExp).join("\\b.*\\b")}\\b`,
+					"g",
+				);
+			} else {
+				const regexp = mutePattern.match(/^\/(.+)\/(.*)$/);
+				// This should never happen due to input sanitisation.
+				if (!regexp) {
+					console.warn(`Found invalid regex in word mutes: ${mutePattern}`);
+					continue;
+				}
+				mute = new RE2(regexp[1], regexp[2]);
+				matched = [mutePattern];
 			}
-		});
 
-		if (matched) return true;
+			try {
+				if (mute.test(text)) {
+					return { muted: true, matched };
+				}
+			} catch (err) {
+				// This should never happen due to input sanitisation.
+			}
+		}
 	}
 
-	return false;
+	return NotMuted;
 }
diff --git a/packages/backend/src/server/api/stream/channels/global-timeline.ts b/packages/backend/src/server/api/stream/channels/global-timeline.ts
index bea201088..a99b3cbc1 100644
--- a/packages/backend/src/server/api/stream/channels/global-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts
@@ -1,6 +1,6 @@
 import Channel from "../channel.js";
 import { fetchMeta } from "@/misc/fetch-meta.js";
-import { checkWordMute } from "@/misc/check-word-mute.js";
+import { getWordMute } from "@/misc/check-word-mute.js";
 import { isInstanceMuted } from "@/misc/is-instance-muted.js";
 import { isUserRelated } from "@/misc/is-user-related.js";
 import type { Packed } from "@/misc/schema.js";
@@ -60,10 +60,10 @@ export default class extends Channel {
 		// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
 		// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
 		// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
-		// そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる
+		// そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordMuteを呼んでいる
 		if (
 			this.userProfile &&
-			(await checkWordMute(note, this.user, this.userProfile.mutedWords))
+			(await getWordMute(note, this.user, this.userProfile.mutedWords)).muted
 		)
 			return;
 
diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts
index 47d773638..c51a0fc2b 100644
--- a/packages/backend/src/server/api/stream/channels/home-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts
@@ -1,5 +1,5 @@
 import Channel from "../channel.js";
-import { checkWordMute } from "@/misc/check-word-mute.js";
+import { getWordMute } from "@/misc/check-word-mute.js";
 import { isUserRelated } from "@/misc/is-user-related.js";
 import { isInstanceMuted } from "@/misc/is-instance-muted.js";
 import type { Packed } from "@/misc/schema.js";
@@ -58,10 +58,10 @@ export default class extends Channel {
 		// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
 		// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
 		// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
-		// そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる
+		// そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordMuteを呼んでいる
 		if (
 			this.userProfile &&
-			(await checkWordMute(note, this.user, this.userProfile.mutedWords))
+			(await getWordMute(note, this.user, this.userProfile.mutedWords)).muted
 		)
 			return;
 
diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
index 398127c40..3e4a8a4e1 100644
--- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
@@ -1,6 +1,6 @@
 import Channel from "../channel.js";
 import { fetchMeta } from "@/misc/fetch-meta.js";
-import { checkWordMute } from "@/misc/check-word-mute.js";
+import { getWordMute } from "@/misc/check-word-mute.js";
 import { isUserRelated } from "@/misc/is-user-related.js";
 import { isInstanceMuted } from "@/misc/is-instance-muted.js";
 import type { Packed } from "@/misc/schema.js";
@@ -75,10 +75,10 @@ export default class extends Channel {
 		// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
 		// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
 		// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
-		// そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる
+		// そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordMuteを呼んでいる
 		if (
 			this.userProfile &&
-			(await checkWordMute(note, this.user, this.userProfile.mutedWords))
+			(await getWordMute(note, this.user, this.userProfile.mutedWords)).muted
 		)
 			return;
 
diff --git a/packages/backend/src/server/api/stream/channels/local-timeline.ts b/packages/backend/src/server/api/stream/channels/local-timeline.ts
index 6f8075b7a..56689aae9 100644
--- a/packages/backend/src/server/api/stream/channels/local-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts
@@ -1,6 +1,6 @@
 import Channel from "../channel.js";
 import { fetchMeta } from "@/misc/fetch-meta.js";
-import { checkWordMute } from "@/misc/check-word-mute.js";
+import { getWordMute } from "@/misc/check-word-mute.js";
 import { isUserRelated } from "@/misc/is-user-related.js";
 import type { Packed } from "@/misc/schema.js";
 
@@ -52,10 +52,10 @@ export default class extends Channel {
 		// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
 		// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
 		// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
-		// そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる
+		// そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordMuteを呼んでいる
 		if (
 			this.userProfile &&
-			(await checkWordMute(note, this.user, this.userProfile.mutedWords))
+			(await getWordMute(note, this.user, this.userProfile.mutedWords)).muted
 		)
 			return;
 
diff --git a/packages/backend/src/server/api/stream/channels/recommended-timeline.ts b/packages/backend/src/server/api/stream/channels/recommended-timeline.ts
index a2a03fca1..28e57ef53 100644
--- a/packages/backend/src/server/api/stream/channels/recommended-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/recommended-timeline.ts
@@ -1,6 +1,6 @@
 import Channel from "../channel.js";
 import { fetchMeta } from "@/misc/fetch-meta.js";
-import { checkWordMute } from "@/misc/check-word-mute.js";
+import { getWordMute } from "@/misc/check-word-mute.js";
 import { isUserRelated } from "@/misc/is-user-related.js";
 import { isInstanceMuted } from "@/misc/is-instance-muted.js";
 import type { Packed } from "@/misc/schema.js";
@@ -73,10 +73,10 @@ export default class extends Channel {
 		// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
 		// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
 		// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
-		// そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる
+		// そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordMuteを呼んでいる
 		if (
 			this.userProfile &&
-			(await checkWordMute(note, this.user, this.userProfile.mutedWords))
+			(await getWordMute(note, this.user, this.userProfile.mutedWords)).muted
 		)
 			return;
 
diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts
index 6c7fd9ad5..5dd324d89 100644
--- a/packages/backend/src/services/note/create.ts
+++ b/packages/backend/src/services/note/create.ts
@@ -53,7 +53,7 @@ import { Poll } from "@/models/entities/poll.js";
 import { createNotification } from "../create-notification.js";
 import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js";
 import { checkHitAntenna } from "@/misc/check-hit-antenna.js";
-import { checkWordMute } from "@/misc/check-word-mute.js";
+import { getWordMute } from "@/misc/check-word-mute.js";
 import { addNoteToAntenna } from "../add-note-to-antenna.js";
 import { countSameRenotes } from "@/misc/count-same-renotes.js";
 import { deliverToRelays } from "../relay.js";
@@ -343,9 +343,9 @@ export default async (
 			)
 			.then((us) => {
 				for (const u of us) {
-					checkWordMute(note, { id: u.userId }, u.mutedWords).then(
+					getWordMute(note, { id: u.userId }, u.mutedWords).then(
 						(shouldMute) => {
-							if (shouldMute) {
+							if (shouldMute.muted) {
 								MutedNotes.insert({
 									id: genId(),
 									userId: u.userId,
diff --git a/packages/client/src/components/MkNote.vue b/packages/client/src/components/MkNote.vue
index f80aabbc9..a0c4edda7 100644
--- a/packages/client/src/components/MkNote.vue
+++ b/packages/client/src/components/MkNote.vue
@@ -1,6 +1,6 @@
 <template>
 <div
-	v-if="!muted"
+	v-if="!muted.muted"
 	v-show="!isDeleted"
 	ref="el"
 	v-hotkey="keymap"
@@ -96,13 +96,16 @@
 		</div>
 	</article>
 </div>
-<div v-else class="muted" @click="muted = false">
-	<I18n :src="i18n.ts.userSaysSomething" tag="small">
+<div v-else class="muted" @click="muted.muted = false">
+	<I18n :src="i18n.ts.userSaysSomethingReason" tag="small">
 		<template #name>
 			<MkA v-user-preview="appearNote.userId" class="name" :to="userPage(appearNote.user)">
 				<MkUserName :user="appearNote.user"/>
 			</MkA>
 		</template>
+		<template #reason>
+			<b>{{ muted.matched.join(", ") }}</b>
+		</template>
 	</I18n>
 </div>
 </template>
@@ -126,7 +129,7 @@ import MkUrlPreview from '@/components/MkUrlPreview.vue';
 import MkVisibility from '@/components/MkVisibility.vue';
 import { pleaseLogin } from '@/scripts/please-login';
 import { focusPrev, focusNext } from '@/scripts/focus';
-import { checkWordMute } from '@/scripts/check-word-mute';
+import { getWordMute } from '@/scripts/check-word-mute';
 import { useRouter } from '@/router';
 import { userPage } from '@/filters/user';
 import * as os from '@/os';
@@ -184,7 +187,7 @@ const isLong = (appearNote.cw == null && appearNote.text != null && (
 ));
 const collapsed = ref(appearNote.cw == null && isLong);
 const isDeleted = ref(false);
-const muted = ref(checkWordMute(appearNote, $i, defaultStore.state.mutedWords));
+const muted = ref(getWordMute(appearNote, $i, defaultStore.state.mutedWords));
 const translation = ref(null);
 const translating = ref(false);
 const urls = appearNote.text ? extractUrlFromMfm(mfm.parse(appearNote.text)).slice(0, 5) : null;
diff --git a/packages/client/src/components/MkNoteDetailed.vue b/packages/client/src/components/MkNoteDetailed.vue
index 4eed184e2..66ad47484 100644
--- a/packages/client/src/components/MkNoteDetailed.vue
+++ b/packages/client/src/components/MkNoteDetailed.vue
@@ -1,6 +1,6 @@
 <template>
 <div
-	v-if="!muted"
+	v-if="!muted.muted"
 	v-show="!isDeleted"
 	ref="el"
 	v-hotkey="keymap"
@@ -102,13 +102,16 @@
 	</article>
 	<MkNoteSub v-for="note in directReplies" :key="note.id" :note="note" class="reply" :conversation="replies"/>
 </div>
-<div v-else class="_panel muted" @click="muted = false">
-	<I18n :src="i18n.ts.userSaysSomething" tag="small">
+<div v-else class="_panel muted" @click="muted.muted = false">
+	<I18n :src="i18n.ts.userSaysSomethingReason" tag="small">
 		<template #name>
 			<MkA v-user-preview="appearNote.userId" class="name" :to="userPage(appearNote.user)">
 				<MkUserName :user="appearNote.user"/>
 			</MkA>
 		</template>
+		<template #reason>
+			<b>{{ muted.matched.join(", ") }}</b>
+		</template>
 	</I18n>
 </div>
 </template>
@@ -130,7 +133,7 @@ import MkUrlPreview from '@/components/MkUrlPreview.vue';
 import MkInstanceTicker from '@/components/MkInstanceTicker.vue';
 import MkVisibility from '@/components/MkVisibility.vue';
 import { pleaseLogin } from '@/scripts/please-login';
-import { checkWordMute } from '@/scripts/check-word-mute';
+import { getWordMute } from '@/scripts/check-word-mute';
 import { userPage } from '@/filters/user';
 import { notePage } from '@/filters/note';
 import { useRouter } from '@/router';
@@ -186,7 +189,7 @@ let appearNote = $computed(() => isRenote ? note.renote as misskey.entities.Note
 const isMyRenote = $i && ($i.id === note.userId);
 const showContent = ref(false);
 const isDeleted = ref(false);
-const muted = ref(checkWordMute(appearNote, $i, defaultStore.state.mutedWords));
+const muted = ref(getWordMute(appearNote, $i, defaultStore.state.mutedWords));
 const translation = ref(null);
 const translating = ref(false);
 const urls = appearNote.text ? extractUrlFromMfm(mfm.parse(appearNote.text)).slice(0, 5) : null;
diff --git a/packages/client/src/scripts/check-word-mute.ts b/packages/client/src/scripts/check-word-mute.ts
index 7053c658b..a189a6d3a 100644
--- a/packages/client/src/scripts/check-word-mute.ts
+++ b/packages/client/src/scripts/check-word-mute.ts
@@ -1,41 +1,64 @@
-export function checkWordMute(
+export type Muted = {
+	muted: boolean;
+	matched: string[];
+};
+
+const NotMuted = { muted: false, matched: [] };
+
+function escapeRegExp(x: string) {
+	return x.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
+}
+
+export function getWordMute(
 	note: Record<string, any>,
 	me: Record<string, any> | null | undefined,
 	mutedWords: Array<string | string[]>,
-): boolean {
+): Muted {
 	// 自分自身
-	if (me && note.userId === me.id) return false;
+	if (me && note.userId === me.id) {
+		return NotMuted;
+	}
 
 	if (mutedWords.length > 0) {
 		const text = ((note.cw ?? "") + "\n" + (note.text ?? "")).trim();
 
-		if (text === "") return false;
+		if (text === "") {
+			return NotMuted;
+		}
 
-		const matched = mutedWords.some((filter) => {
-			if (Array.isArray(filter)) {
-				// Clean up
-				const filteredFilter = filter.filter((keyword) => keyword !== "");
-				if (filteredFilter.length === 0) return false;
+		for (const mutePattern of mutedWords) {
+			let mute: RegExp;
+			let matched: string[];
+			if (Array.isArray(mutePattern)) {
+				matched = mutePattern.filter((keyword) => keyword !== "");
 
-				return filteredFilter.every((keyword) => text.includes(keyword));
-			} else {
-				// represents RegExp
-				const regexp = filter.match(/^\/(.+)\/(.*)$/);
-
-				// This should never happen due to input sanitisation.
-				if (!regexp) return false;
-
-				try {
-					return new RegExp(regexp[1], regexp[2]).test(text);
-				} catch (err) {
-					// This should never happen due to input sanitisation.
-					return false;
+				if (matched.length === 0) {
+					continue;
 				}
+				mute = new RegExp(
+					`\\b${matched.map(escapeRegExp).join("\\b.*\\b")}\\b`,
+					"g",
+				);
+			} else {
+				const regexp = mutePattern.match(/^\/(.+)\/(.*)$/);
+				// This should never happen due to input sanitisation.
+				if (!regexp) {
+					console.warn(`Found invalid regex in word mutes: ${mutePattern}`);
+					continue;
+				}
+				mute = new RegExp(regexp[1], regexp[2]);
+				matched = [mutePattern];
 			}
-		});
 
-		if (matched) return true;
+			try {
+				if (mute.test(text)) {
+					return { muted: true, matched };
+				}
+			} catch (err) {
+				// This should never happen due to input sanitisation.
+			}
+		}
 	}
 
-	return false;
+	return NotMuted;
 }