diff --git a/CHANGELOG.md b/CHANGELOG.md
index 063ebf5251abbe8abb4abeb84a6a13a0db4fb0a4..405c92bb3a9c2e01d1d727c1aa893f134458115d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,7 @@ You should also include the user name that made the change.
 - Client: Improve control panel @syuilo
 - Client: Show warning in control panel when there is an unresolved abuse report @syuilo
 - Client: Add instance-cloud widget @syuilo
+- Client: Add rss-marquee widget @syuilo
 - Make possible to delete an account by admin @syuilo
 - Improve player detection in URL preview @mei23
 - Add Badge Image to Push Notification #8012 @tamaina
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 743640725860a4cea0b7f9c4c010f9b41dffacc9..1f52c2c2590786d7a00167ccea2b5f49a7789df6 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1246,6 +1246,7 @@ _widgets:
   trends: "トレンド"
   clock: "時計"
   rss: "RSSリーダー"
+  rssMarquee: "RSSリーダー(マーキー)"
   activity: "アクティビティ"
   photos: "フォト"
   digitalClock: "デジタル時計"
diff --git a/packages/client/package.json b/packages/client/package.json
index 56467fc84206bd093f1c0f8b44a98676154bed68..54ee7dee53c9a692414cfc0648c1b4159a69e9dc 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -76,6 +76,7 @@
 		"vanilla-tilt": "1.7.2",
 		"vite": "3.0.0-beta.5",
 		"vue": "3.2.37",
+		"vue-marquee-text-component": "2.0.1",
 		"vue-prism-editor": "2.0.0-alpha.2",
 		"vuedraggable": "4.0.1",
 		"websocket": "1.0.34",
diff --git a/packages/client/src/widgets/index.ts b/packages/client/src/widgets/index.ts
index feda16c91dd133eed6151a5d94f0e350d30bdb0c..ed65f52913e9c2843f484547375df55fed186a37 100644
--- a/packages/client/src/widgets/index.ts
+++ b/packages/client/src/widgets/index.ts
@@ -6,6 +6,7 @@ export default function(app: App) {
 	app.component('MkwTimeline', defineAsyncComponent(() => import('./timeline.vue')));
 	app.component('MkwCalendar', defineAsyncComponent(() => import('./calendar.vue')));
 	app.component('MkwRss', defineAsyncComponent(() => import('./rss.vue')));
+	app.component('MkwRssMarquee', defineAsyncComponent(() => import('./rss-marquee.vue')));
 	app.component('MkwTrends', defineAsyncComponent(() => import('./trends.vue')));
 	app.component('MkwClock', defineAsyncComponent(() => import('./clock.vue')));
 	app.component('MkwActivity', defineAsyncComponent(() => import('./activity.vue')));
@@ -29,13 +30,14 @@ export const widgets = [
 	'timeline',
 	'calendar',
 	'rss',
+	'rssMarquee',
 	'trends',
 	'clock',
 	'activity',
 	'photos',
 	'digitalClock',
 	'federation',
-	'instance-cloud',
+	'instanceCloud',
 	'postForm',
 	'slideshow',
 	'serverMetric',
diff --git a/packages/client/src/widgets/rss-marquee.vue b/packages/client/src/widgets/rss-marquee.vue
new file mode 100644
index 0000000000000000000000000000000000000000..e23f916d3a58568af64c01f91638a80fe369eab2
--- /dev/null
+++ b/packages/client/src/widgets/rss-marquee.vue
@@ -0,0 +1,115 @@
+<template>
+<MkContainer :naked="widgetProps.transparent" :show-header="widgetProps.showHeader" class="mkw-rss-marquee">
+	<template #header><i class="fas fa-rss-square"></i>RSS</template>
+	<template #func><button class="_button" @click="configure"><i class="fas fa-cog"></i></button></template>
+
+	<div class="ekmkgxbk">
+		<MkLoading v-if="fetching"/>
+		<div v-else class="feed">
+			<MarqueeText :duration="widgetProps.speed" :reverse="widgetProps.reverse">
+				<a v-for="item in items" class="item" :href="item.link" rel="nofollow noopener" target="_blank" :title="item.title">{{ item.title }}</a>
+			</MarqueeText>
+		</div>
+	</div>
+</MkContainer>
+</template>
+
+<script lang="ts" setup>
+import { onMounted, onUnmounted, ref, watch } from 'vue';
+import MarqueeText from 'vue-marquee-text-component';
+import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
+import { GetFormResultType } from '@/scripts/form';
+import * as os from '@/os';
+import MkContainer from '@/components/ui/container.vue';
+import { useInterval } from '@/scripts/use-interval';
+
+const name = 'rssMarquee';
+
+const widgetPropsDef = {
+	url: {
+		type: 'string' as const,
+		default: 'http://feeds.afpbb.com/rss/afpbb/afpbbnews',
+	},
+	showHeader: {
+		type: 'boolean' as const,
+		default: false,
+	},
+	transparent: {
+		type: 'boolean' as const,
+		default: false,
+	},
+	speed: {
+		type: 'radio' as const,
+		default: 70,
+		options: [{
+			value: 170, label: 'very slow',
+		}, {
+			value: 100, label: 'slow',
+		}, {
+			value: 70, label: 'medium',
+		}, {
+			value: 40, label: 'fast',
+		}, {
+			value: 20, label: 'very fast',
+		}],
+	},
+	reverse: {
+		type: 'boolean' as const,
+		default: false,
+	},
+};
+
+type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
+
+// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
+//const props = defineProps<WidgetComponentProps<WidgetProps>>();
+//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
+const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
+const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
+
+const { widgetProps, configure } = useWidgetPropsManager(name,
+	widgetPropsDef,
+	props,
+	emit,
+);
+
+const items = ref([]);
+const fetching = ref(true);
+
+const tick = () => {
+	fetch(`https://api.rss2json.com/v1/api.json?rss_url=${widgetProps.url}`, {}).then(res => {
+		res.json().then(feed => {
+			items.value = feed.items;
+			fetching.value = false;
+		});
+	});
+};
+
+watch(() => widgetProps.url, tick);
+
+useInterval(tick, 60000, {
+	immediate: true,
+	afterMounted: true,
+});
+
+defineExpose<WidgetComponentExpose>({
+	name,
+	configure,
+	id: props.widget ? props.widget.id : null,
+});
+</script>
+
+<style lang="scss" scoped>
+.ekmkgxbk {
+	> .feed {
+		padding: 0;
+		font-size: 0.9em;
+
+		::v-deep(.item) {
+			display: inline-block;
+			color: var(--fg);
+			margin: 12px 3em 12px 0;
+		}
+	}
+}
+</style>
diff --git a/packages/client/src/widgets/rss.vue b/packages/client/src/widgets/rss.vue
index e5da291a8dd15719f2820f0f24a606ec0c315eee..ea896478a3cbfc1b7618f20cb0cfe5fb87918c0f 100644
--- a/packages/client/src/widgets/rss.vue
+++ b/packages/client/src/widgets/rss.vue
@@ -6,7 +6,7 @@
 	<div class="ekmkgxbj">
 		<MkLoading v-if="fetching"/>
 		<div v-else class="feed">
-			<a v-for="item in items" :href="item.link" rel="nofollow noopener" target="_blank" :title="item.title">{{ item.title }}</a>
+			<a v-for="item in items" class="item" :href="item.link" rel="nofollow noopener" target="_blank" :title="item.title">{{ item.title }}</a>
 		</div>
 	</div>
 </MkContainer>
@@ -23,14 +23,14 @@ import { useInterval } from '@/scripts/use-interval';
 const name = 'rss';
 
 const widgetPropsDef = {
-	showHeader: {
-		type: 'boolean' as const,
-		default: true,
-	},
 	url: {
 		type: 'string' as const,
 		default: 'http://feeds.afpbb.com/rss/afpbb/afpbbnews',
 	},
+	showHeader: {
+		type: 'boolean' as const,
+		default: true,
+	},
 };
 
 type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
@@ -79,7 +79,7 @@ defineExpose<WidgetComponentExpose>({
 		padding: 0;
 		font-size: 0.9em;
 
-		> a {
+		> .item {
 			display: block;
 			padding: 8px 16px;
 			color: var(--fg);
diff --git a/packages/client/yarn.lock b/packages/client/yarn.lock
index 7bbbe08d377c218e402d664a0b91b5c4644fb54a..58df1576f6852dd9ec6875a6146ddeb12c33ff1e 100644
--- a/packages/client/yarn.lock
+++ b/packages/client/yarn.lock
@@ -1221,6 +1221,11 @@ content-disposition@0.5.4:
   dependencies:
     safe-buffer "5.2.1"
 
+core-js@^3.18.0:
+  version "3.23.3"
+  resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.23.3.tgz#3b977612b15da6da0c9cc4aec487e8d24f371112"
+  integrity sha512-oAKwkj9xcWNBAvGbT//WiCdOMpb9XQG92/Fe3ABFM/R16BsHgePG00mFOgKf7IsCtfj8tA1kHtf/VwErhriz5Q==
+
 core-util-is@1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@@ -4252,12 +4257,20 @@ vue-eslint-parser@^9.0.1:
     lodash "^4.17.21"
     semver "^7.3.6"
 
+vue-marquee-text-component@2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/vue-marquee-text-component/-/vue-marquee-text-component-2.0.1.tgz#62691df195f755471fa9bdc9b1969f836a922b9a"
+  integrity sha512-dbeRwDY5neOJcWZrDFU2tJMhPSsxN25ZpNYeZdt0jkseg1MbyGKzrfEH9nrCFZRkEfqhxG+ukyzwVwR9US5sTQ==
+  dependencies:
+    core-js "^3.18.0"
+    vue "^3.2.19"
+
 vue-prism-editor@2.0.0-alpha.2:
   version "2.0.0-alpha.2"
   resolved "https://registry.yarnpkg.com/vue-prism-editor/-/vue-prism-editor-2.0.0-alpha.2.tgz#aa53a88efaaed628027cbb282c2b1d37fc7c5c69"
   integrity sha512-Gu42ba9nosrE+gJpnAEuEkDMqG9zSUysIR8SdXUw8MQKDjBnnNR9lHC18uOr/ICz7yrA/5c7jHJr9lpElODC7w==
 
-vue@3.2.37:
+vue@3.2.37, vue@^3.2.19:
   version "3.2.37"
   resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.37.tgz#da220ccb618d78579d25b06c7c21498ca4e5452e"
   integrity sha512-bOKEZxrm8Eh+fveCqS1/NkG/n6aMidsI6hahas7pa0w/l7jkbssJVsRhVDs07IdDq7h9KHswZOgItnwJAgtVtQ==