diff --git a/src/client/components/mfm.ts b/src/client/components/mfm.ts
index 7a8ee8b19f..7e4fec2848 100644
--- a/src/client/components/mfm.ts
+++ b/src/client/components/mfm.ts
@@ -125,6 +125,12 @@ export default defineComponent({
 					}, genEl(token.children));
 				}
 
+				case 'twitch': {
+					return h('span', {
+						style: this.$store.state.device.animatedMfm ? 'display: inline-block; animation: anime-twitch 0.5s ease infinite;' : 'display: inline-block;'
+					}, genEl(token.children));
+				}
+
 				case 'url': {
 					return [h(MkUrl, {
 						key: Math.random(),
diff --git a/src/client/style.scss b/src/client/style.scss
index cbcd497e22..be9e373291 100644
--- a/src/client/style.scss
+++ b/src/client/style.scss
@@ -499,6 +499,21 @@ hr {
 	100% { transform: translateY(0); }
 }
 
+// const gen = () => `translate(${Math.floor(Math.random() * 24) - 12}px, ${Math.floor(Math.random() * 24) - 12}px)`
+@keyframes anime-twitch {
+	0% { transform: translate(0px, -9px); }
+	10% { transform: translate(5px, -11px); }
+	20% { transform: translate(-12px, 2px); }
+	30% { transform: translate(10px, 4px); }
+	40% { transform: translate(0px, 9px); }
+	50% { transform: translate(-6px, 2px); }
+	60% { transform: translate(-7px, 7px); }
+	70% { transform: translate(14px, 7px); }
+	80% { transform: translate(-5px, 10px); }
+	90% { transform: translate(3px, -3px); }
+	100% { transform: translate(10px, 8px); }
+}
+
 @keyframes anime-tada {
 	from {
 		transform: scale3d(1, 1, 1);
diff --git a/src/mfm/language.ts b/src/mfm/language.ts
index 59b09daf38..f334301d12 100644
--- a/src/mfm/language.ts
+++ b/src/mfm/language.ts
@@ -76,6 +76,7 @@ export const mfmLanguage = P.createLanguage({
 		r.spin,
 		r.jump,
 		r.flip,
+		r.twitch,
 		r.inlineCode,
 		r.mathInline,
 		r.mention,
@@ -122,6 +123,7 @@ export const mfmLanguage = P.createLanguage({
 	},
 	jump: r => P.regexp(/<jump>(.+?)<\/jump>/, 1).map(x => createTree('jump', r.inline.atLeast(1).tryParse(x), {})),
 	flip: r => P.regexp(/<flip>(.+?)<\/flip>/, 1).map(x => createTree('flip', r.inline.atLeast(1).tryParse(x), {})),
+	twitch: r => P.regexp(/<twitch>(.+?)<\/twitch>/, 1).map(x => createTree('twitch', r.inline.atLeast(1).tryParse(x), {})),
 	center: r => r.startOfLine.then(P.regexp(/<center>([\s\S]+?)<\/center>/, 1).map(x => createTree('center', r.inline.atLeast(1).tryParse(x), {}))),
 	inlineCode: () => P.regexp(/`([^ยด\n]+?)`/, 1).map(x => createLeaf('inlineCode', { code: x })),
 	mathBlock: r => r.startOfLine.then(P.regexp(/\\\[([\s\S]+?)\\\]/, 1).map(x => createLeaf('mathBlock', { formula: x.trim() }))),
diff --git a/src/mfm/to-html.ts b/src/mfm/to-html.ts
index 5b21384608..d58bf386e8 100644
--- a/src/mfm/to-html.ts
+++ b/src/mfm/to-html.ts
@@ -67,6 +67,12 @@ export function toHtml(tokens: MfmForest | null, mentionedRemoteUsers: IMentione
 			return el;
 		},
 
+		twitch(token) {
+			const el = doc.createElement('i');
+			appendChildren(token.children, el);
+			return el;
+		},
+
 		flip(token) {
 			const el = doc.createElement('span');
 			appendChildren(token.children, el);
diff --git a/src/mfm/to-string.ts b/src/mfm/to-string.ts
index 107929164c..a6e2a9916c 100644
--- a/src/mfm/to-string.ts
+++ b/src/mfm/to-string.ts
@@ -48,6 +48,10 @@ export function toString(tokens: MfmForest | null, opts?: RestoreOptions): strin
 			return `<jump>${appendChildren(token.children, opts)}</jump>`;
 		},
 
+		twitch(token, opts) {
+			return `<twitch>${appendChildren(token.children, opts)}</twitch>`;
+		},
+
 		flip(token, opts) {
 			return `<flip>${appendChildren(token.children, opts)}</flip>`;
 		},