From 786b150ea75111b5f6102c256d5cfa42cb83d1fb Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Wed, 31 Aug 2022 00:24:33 +0900
Subject: refactor(client): align filename to component name

---
 .../client/src/components/MkUserSelectDialog.vue   | 190 +++++++++++++++++++++
 1 file changed, 190 insertions(+)
 create mode 100644 packages/client/src/components/MkUserSelectDialog.vue

(limited to 'packages/client/src/components/MkUserSelectDialog.vue')

diff --git a/packages/client/src/components/MkUserSelectDialog.vue b/packages/client/src/components/MkUserSelectDialog.vue
new file mode 100644
index 0000000000..4d8e427a72
--- /dev/null
+++ b/packages/client/src/components/MkUserSelectDialog.vue
@@ -0,0 +1,190 @@
+<template>
+<XModalWindow
+	ref="dialogEl"
+	:with-ok-button="true"
+	:ok-button-disabled="selected == null"
+	@click="cancel()"
+	@close="cancel()"
+	@ok="ok()"
+	@closed="$emit('closed')"
+>
+	<template #header>{{ i18n.ts.selectUser }}</template>
+	<div class="tbhwbxda">
+		<div class="form">
+			<FormSplit :min-width="170">
+				<MkInput v-model="username" :autofocus="true" @update:modelValue="search">
+					<template #label>{{ i18n.ts.username }}</template>
+					<template #prefix>@</template>
+				</MkInput>
+				<MkInput v-model="host" @update:modelValue="search">
+					<template #label>{{ i18n.ts.host }}</template>
+					<template #prefix>@</template>
+				</MkInput>
+			</FormSplit>
+		</div>
+		<div v-if="username != '' || host != ''" class="result" :class="{ hit: users.length > 0 }">
+			<div v-if="users.length > 0" class="users">
+				<div v-for="user in users" :key="user.id" class="user" :class="{ selected: selected && selected.id === user.id }" @click="selected = user" @dblclick="ok()">
+					<MkAvatar :user="user" class="avatar" :show-indicator="true"/>
+					<div class="body">
+						<MkUserName :user="user" class="name"/>
+						<MkAcct :user="user" class="acct"/>
+					</div>
+				</div>
+			</div>
+			<div v-else class="empty">
+				<span>{{ i18n.ts.noUsers }}</span>
+			</div>
+		</div>
+		<div v-if="username == '' && host == ''" class="recent">
+			<div class="users">
+				<div v-for="user in recentUsers" :key="user.id" class="user" :class="{ selected: selected && selected.id === user.id }" @click="selected = user" @dblclick="ok()">
+					<MkAvatar :user="user" class="avatar" :show-indicator="true"/>
+					<div class="body">
+						<MkUserName :user="user" class="name"/>
+						<MkAcct :user="user" class="acct"/>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+</XModalWindow>
+</template>
+
+<script lang="ts" setup>
+import { nextTick, onMounted } from 'vue';
+import * as misskey from 'misskey-js';
+import MkInput from '@/components/form/input.vue';
+import FormSplit from '@/components/form/split.vue';
+import XModalWindow from '@/components/ui/modal-window.vue';
+import * as os from '@/os';
+import { defaultStore } from '@/store';
+import { i18n } from '@/i18n';
+
+const emit = defineEmits<{
+	(ev: 'ok', selected: misskey.entities.UserDetailed): void;
+	(ev: 'cancel'): void;
+	(ev: 'closed'): void;
+}>();
+
+let username = $ref('');
+let host = $ref('');
+let users: misskey.entities.UserDetailed[] = $ref([]);
+let recentUsers: misskey.entities.UserDetailed[] = $ref([]);
+let selected: misskey.entities.UserDetailed | null = $ref(null);
+let dialogEl = $ref();
+
+const search = () => {
+	if (username === '' && host === '') {
+		users = [];
+		return;
+	}
+	os.api('users/search-by-username-and-host', {
+		username: username,
+		host: host,
+		limit: 10,
+		detail: false,
+	}).then(_users => {
+		users = _users;
+	});
+};
+
+const ok = () => {
+	if (selected == null) return;
+	emit('ok', selected);
+	dialogEl.close();
+
+	// 最近使ったユーザー更新
+	let recents = defaultStore.state.recentlyUsedUsers;
+	recents = recents.filter(x => x !== selected.id);
+	recents.unshift(selected.id);
+	defaultStore.set('recentlyUsedUsers', recents.splice(0, 16));
+};
+
+const cancel = () => {
+	emit('cancel');
+	dialogEl.close();
+};
+
+onMounted(() => {
+	os.api('users/show', {
+		userIds: defaultStore.state.recentlyUsedUsers,
+	}).then(users => {
+		recentUsers = users;
+	});
+});
+</script>
+
+<style lang="scss" scoped>
+.tbhwbxda {
+	> .form {
+		padding: 0 var(--root-margin);
+	}
+
+	> .result, > .recent {
+		display: flex;
+		flex-direction: column;
+		overflow: auto;
+		height: 100%;
+
+		&.result.hit {
+			padding: 0;
+		}
+
+		&.recent {
+			padding: 0;
+		}
+
+		> .users {
+			flex: 1;
+			overflow: auto;
+			padding: 8px 0;
+
+			> .user {
+				display: flex;
+				align-items: center;
+				padding: 8px var(--root-margin);
+				font-size: 14px;
+
+				&:hover {
+					background: var(--X7);
+				}
+
+				&.selected {
+					background: var(--accent);
+					color: #fff;
+				}
+
+				> * {
+					pointer-events: none;
+					user-select: none;
+				}
+
+				> .avatar {
+					width: 45px;
+					height: 45px;
+				}
+
+				> .body {
+					padding: 0 8px;
+					min-width: 0;
+
+					> .name {
+						display: block;
+						font-weight: bold;
+					}
+
+					> .acct {
+						opacity: 0.5;
+					}
+				}
+			}
+		}
+
+		> .empty {
+			opacity: 0.7;
+			text-align: center;
+		}
+	}
+}
+</style>
-- 
cgit v1.3.1-freya


From a9e13693a593ff1fb4b2ed1f2e1cb90a8ef7bd3b Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Tue, 6 Sep 2022 18:21:49 +0900
Subject: refactor(client): refactor file name and directory structure

---
 packages/client/src/components/MkAbuseReport.vue   |   2 +-
 .../client/src/components/MkAbuseReportWindow.vue  |   4 +-
 packages/client/src/components/MkButton.vue        | 226 +++++++++
 packages/client/src/components/MkChartTooltip.vue  |   2 +-
 packages/client/src/components/MkContainer.vue     | 264 ++++++++++
 packages/client/src/components/MkContextMenu.vue   |  85 ++++
 packages/client/src/components/MkCropperDialog.vue |   2 +-
 packages/client/src/components/MkDialog.vue        |   4 +-
 packages/client/src/components/MkDrive.vue         |   2 +-
 .../client/src/components/MkDriveSelectDialog.vue  |   2 +-
 packages/client/src/components/MkDriveWindow.vue   |   2 +-
 .../client/src/components/MkEmojiPickerDialog.vue  |   2 +-
 .../client/src/components/MkEmojiPickerWindow.vue  |   2 +-
 .../client/src/components/MkFileListForAdmin.vue   |   2 +-
 packages/client/src/components/MkFolder.vue        | 156 ++++++
 .../client/src/components/MkForgotPassword.vue     |   4 +-
 packages/client/src/components/MkFormDialog.vue    |   4 +-
 packages/client/src/components/MkImageViewer.vue   |   2 +-
 packages/client/src/components/MkInfo.vue          |  34 ++
 packages/client/src/components/MkLaunchPad.vue     |   2 +-
 packages/client/src/components/MkMediaCaption.vue  |   4 +-
 packages/client/src/components/MkMenu.child.vue    |  65 +++
 packages/client/src/components/MkMenu.vue          | 364 +++++++++++++
 packages/client/src/components/MkModal.vue         | 406 +++++++++++++++
 .../client/src/components/MkModalPageWindow.vue    |   2 +-
 packages/client/src/components/MkModalWindow.vue   | 146 ++++++
 packages/client/src/components/MkNotes.vue         |   2 +-
 .../src/components/MkNotificationSettingWindow.vue |   6 +-
 packages/client/src/components/MkNotifications.vue |   2 +-
 packages/client/src/components/MkPageWindow.vue    |   2 +-
 packages/client/src/components/MkPagination.vue    | 317 ++++++++++++
 packages/client/src/components/MkPollEditor.vue    |   2 +-
 packages/client/src/components/MkPopupMenu.vue     |  36 ++
 packages/client/src/components/MkPostForm.vue      |   2 +-
 .../client/src/components/MkPostFormDialog.vue     |   2 +-
 .../client/src/components/MkReactionTooltip.vue    |   2 +-
 .../src/components/MkReactionsViewer.details.vue   |   2 +-
 packages/client/src/components/MkSample.vue        |   2 +-
 packages/client/src/components/MkSignin.vue        |   4 +-
 packages/client/src/components/MkSigninDialog.vue  |   2 +-
 packages/client/src/components/MkSignup.vue        |   2 +-
 packages/client/src/components/MkSignupDialog.vue  |   2 +-
 packages/client/src/components/MkSuperMenu.vue     | 148 ++++++
 .../src/components/MkTokenGenerateWindow.vue       |   6 +-
 packages/client/src/components/MkTooltip.vue       | 101 ++++
 packages/client/src/components/MkUpdated.vue       |   4 +-
 packages/client/src/components/MkUserList.vue      |   2 +-
 .../client/src/components/MkUserSelectDialog.vue   |   2 +-
 packages/client/src/components/MkUsersTooltip.vue  |   2 +-
 .../client/src/components/MkVisibilityPicker.vue   |   2 +-
 packages/client/src/components/MkWaitingDialog.vue |   2 +-
 packages/client/src/components/MkWidgets.vue       |   2 +-
 packages/client/src/components/MkWindow.vue        | 563 +++++++++++++++++++++
 packages/client/src/components/form/input.vue      |   2 +-
 packages/client/src/components/form/range.vue      |   2 +-
 packages/client/src/components/form/select.vue     |   2 +-
 packages/client/src/components/form/suspense.vue   |   2 +-
 packages/client/src/components/form/textarea.vue   |   2 +-
 packages/client/src/components/global/MkAd.vue     |   2 +-
 packages/client/src/components/global/MkError.vue  |   2 +-
 .../client/src/components/page/page.button.vue     |   2 +-
 .../client/src/components/page/page.counter.vue    |   2 +-
 packages/client/src/components/page/page.post.vue  |   2 +-
 packages/client/src/components/ui/button.vue       | 226 ---------
 packages/client/src/components/ui/container.vue    | 264 ----------
 packages/client/src/components/ui/context-menu.vue |  85 ----
 packages/client/src/components/ui/folder.vue       | 156 ------
 packages/client/src/components/ui/info.vue         |  34 --
 packages/client/src/components/ui/menu.child.vue   |  65 ---
 packages/client/src/components/ui/menu.vue         | 364 -------------
 packages/client/src/components/ui/modal-window.vue | 146 ------
 packages/client/src/components/ui/modal.vue        | 406 ---------------
 packages/client/src/components/ui/pagination.vue   | 317 ------------
 packages/client/src/components/ui/popup-menu.vue   |  36 --
 packages/client/src/components/ui/super-menu.vue   | 148 ------
 packages/client/src/components/ui/tooltip.vue      | 101 ----
 packages/client/src/components/ui/window.vue       | 563 ---------------------
 packages/client/src/directives/tooltip.ts          |   2 +-
 packages/client/src/os.ts                          |   4 +-
 packages/client/src/pages/_error_.vue              |   2 +-
 packages/client/src/pages/about-misskey.vue        |   2 +-
 packages/client/src/pages/about.emojis.vue         |  12 +-
 packages/client/src/pages/about.federation.vue     |   4 +-
 packages/client/src/pages/admin-file.vue           |   4 +-
 packages/client/src/pages/admin/_header_.vue       |   2 +-
 packages/client/src/pages/admin/abuses.vue         |   2 +-
 packages/client/src/pages/admin/ads.vue            |   2 +-
 packages/client/src/pages/admin/announcements.vue  |   2 +-
 packages/client/src/pages/admin/bot-protection.vue |   2 +-
 packages/client/src/pages/admin/email-settings.vue |   2 +-
 .../client/src/pages/admin/emoji-edit-dialog.vue   |   4 +-
 packages/client/src/pages/admin/emojis.vue         |   4 +-
 packages/client/src/pages/admin/files.vue          |   2 +-
 packages/client/src/pages/admin/index.vue          |   4 +-
 packages/client/src/pages/admin/instance-block.vue |   2 +-
 .../src/pages/admin/integrations.discord.vue       |   4 +-
 .../client/src/pages/admin/integrations.github.vue |   4 +-
 .../src/pages/admin/integrations.twitter.vue       |   4 +-
 packages/client/src/pages/admin/metrics.vue        |   6 +-
 packages/client/src/pages/admin/proxy-account.vue  |   4 +-
 packages/client/src/pages/admin/queue.vue          |   2 +-
 packages/client/src/pages/admin/relays.vue         |   2 +-
 packages/client/src/pages/admin/security.vue       |   4 +-
 packages/client/src/pages/admin/settings.vue       |   2 +-
 packages/client/src/pages/admin/users.vue          |   2 +-
 packages/client/src/pages/announcements.vue        |   4 +-
 packages/client/src/pages/api-console.vue          |   2 +-
 packages/client/src/pages/auth.form.vue            |   2 +-
 packages/client/src/pages/channel-editor.vue       |   2 +-
 packages/client/src/pages/channel.vue              |   2 +-
 packages/client/src/pages/channels.vue             |   4 +-
 packages/client/src/pages/explore.users.vue        |   2 +-
 packages/client/src/pages/explore.vue              |   2 +-
 packages/client/src/pages/favorites.vue            |   2 +-
 packages/client/src/pages/follow-requests.vue      |   2 +-
 packages/client/src/pages/gallery/edit.vue         |   2 +-
 packages/client/src/pages/gallery/index.vue        |   6 +-
 packages/client/src/pages/gallery/post.vue         |   6 +-
 packages/client/src/pages/instance-info.vue        |   4 +-
 packages/client/src/pages/messaging/index.vue      |   2 +-
 .../client/src/pages/messaging/messaging-room.vue  |   2 +-
 packages/client/src/pages/miauth.vue               |   2 +-
 packages/client/src/pages/my-antennas/editor.vue   |   2 +-
 packages/client/src/pages/my-antennas/index.vue    |   4 +-
 packages/client/src/pages/my-clips/index.vue       |   4 +-
 packages/client/src/pages/my-lists/index.vue       |   4 +-
 packages/client/src/pages/my-lists/list.vue        |   2 +-
 packages/client/src/pages/note.vue                 |   2 +-
 .../client/src/pages/page-editor/page-editor.vue   |   2 +-
 packages/client/src/pages/page.vue                 |   6 +-
 packages/client/src/pages/pages.vue                |   4 +-
 packages/client/src/pages/registry.keys.vue        |   2 +-
 packages/client/src/pages/registry.value.vue       |   4 +-
 packages/client/src/pages/registry.vue             |   2 +-
 packages/client/src/pages/reset-password.vue       |   2 +-
 packages/client/src/pages/scratchpad.vue           |   4 +-
 packages/client/src/pages/settings/2fa.vue         |   4 +-
 packages/client/src/pages/settings/accounts.vue    |   2 +-
 packages/client/src/pages/settings/api.vue         |   2 +-
 packages/client/src/pages/settings/apps.vue        |   2 +-
 packages/client/src/pages/settings/custom-css.vue  |   2 +-
 .../client/src/pages/settings/delete-account.vue   |   4 +-
 .../client/src/pages/settings/import-export.vue    |   2 +-
 packages/client/src/pages/settings/index.vue       |   4 +-
 .../client/src/pages/settings/instance-mute.vue    |   4 +-
 packages/client/src/pages/settings/integration.vue |   2 +-
 packages/client/src/pages/settings/mute-block.vue  |   4 +-
 packages/client/src/pages/settings/navbar.vue      |   2 +-
 .../client/src/pages/settings/notifications.vue    |   2 +-
 .../client/src/pages/settings/plugin.install.vue   |   4 +-
 packages/client/src/pages/settings/plugin.vue      |   2 +-
 .../src/pages/settings/preferences-backups.vue     |   4 +-
 packages/client/src/pages/settings/profile.vue     |   2 +-
 packages/client/src/pages/settings/reaction.vue    |   2 +-
 packages/client/src/pages/settings/security.vue    |   4 +-
 packages/client/src/pages/settings/sounds.vue      |   2 +-
 .../src/pages/settings/statusbar.statusbar.vue     |   2 +-
 packages/client/src/pages/settings/statusbar.vue   |   2 +-
 .../client/src/pages/settings/theme.install.vue    |   2 +-
 .../client/src/pages/settings/theme.manage.vue     |   2 +-
 packages/client/src/pages/settings/theme.vue       |   2 +-
 .../client/src/pages/settings/webhook.edit.vue     |   2 +-
 packages/client/src/pages/settings/webhook.new.vue |   2 +-
 packages/client/src/pages/settings/webhook.vue     |   2 +-
 packages/client/src/pages/settings/word-mute.vue   |   4 +-
 packages/client/src/pages/share.vue                |   2 +-
 packages/client/src/pages/theme-editor.vue         |   2 +-
 packages/client/src/pages/timeline.tutorial.vue    |   2 +-
 packages/client/src/pages/user-info.vue            |   4 +-
 packages/client/src/pages/user/clips.vue           |   2 +-
 packages/client/src/pages/user/follow-list.vue     |   2 +-
 packages/client/src/pages/user/gallery.vue         |   2 +-
 packages/client/src/pages/user/home.vue            |   6 +-
 packages/client/src/pages/user/index.activity.vue  |   2 +-
 packages/client/src/pages/user/index.photos.vue    |   2 +-
 packages/client/src/pages/user/pages.vue           |   2 +-
 packages/client/src/pages/user/reactions.vue       |   2 +-
 packages/client/src/pages/welcome.entrance.a.vue   |   2 +-
 packages/client/src/pages/welcome.entrance.b.vue   |   2 +-
 packages/client/src/pages/welcome.entrance.c.vue   |   2 +-
 packages/client/src/pages/welcome.setup.vue        |   2 +-
 packages/client/src/ui/classic.header.vue          |   2 +-
 packages/client/src/ui/classic.sidebar.vue         |   2 +-
 packages/client/src/ui/deck.vue                    |   2 +-
 packages/client/src/ui/visitor/a.vue               |   4 +-
 packages/client/src/ui/visitor/b.vue               |   4 +-
 packages/client/src/ui/visitor/kanban.vue          |  12 +-
 packages/client/src/widgets/activity.vue           |   2 +-
 packages/client/src/widgets/aiscript.vue           |   2 +-
 packages/client/src/widgets/button.vue             |   8 +-
 packages/client/src/widgets/clock.vue              |   2 +-
 packages/client/src/widgets/federation.vue         |   2 +-
 packages/client/src/widgets/instance-cloud.vue     |   2 +-
 packages/client/src/widgets/memo.vue               |   2 +-
 packages/client/src/widgets/notifications.vue      |   2 +-
 packages/client/src/widgets/photos.vue             |   2 +-
 packages/client/src/widgets/rss-ticker.vue         |   2 +-
 packages/client/src/widgets/rss.vue                |   2 +-
 .../client/src/widgets/server-metric/index.vue     |   2 +-
 packages/client/src/widgets/timeline.vue           |   2 +-
 packages/client/src/widgets/trends.vue             |   2 +-
 201 files changed, 3150 insertions(+), 3150 deletions(-)
 create mode 100644 packages/client/src/components/MkButton.vue
 create mode 100644 packages/client/src/components/MkContainer.vue
 create mode 100644 packages/client/src/components/MkContextMenu.vue
 create mode 100644 packages/client/src/components/MkFolder.vue
 create mode 100644 packages/client/src/components/MkInfo.vue
 create mode 100644 packages/client/src/components/MkMenu.child.vue
 create mode 100644 packages/client/src/components/MkMenu.vue
 create mode 100644 packages/client/src/components/MkModal.vue
 create mode 100644 packages/client/src/components/MkModalWindow.vue
 create mode 100644 packages/client/src/components/MkPagination.vue
 create mode 100644 packages/client/src/components/MkPopupMenu.vue
 create mode 100644 packages/client/src/components/MkSuperMenu.vue
 create mode 100644 packages/client/src/components/MkTooltip.vue
 create mode 100644 packages/client/src/components/MkWindow.vue
 delete mode 100644 packages/client/src/components/ui/button.vue
 delete mode 100644 packages/client/src/components/ui/container.vue
 delete mode 100644 packages/client/src/components/ui/context-menu.vue
 delete mode 100644 packages/client/src/components/ui/folder.vue
 delete mode 100644 packages/client/src/components/ui/info.vue
 delete mode 100644 packages/client/src/components/ui/menu.child.vue
 delete mode 100644 packages/client/src/components/ui/menu.vue
 delete mode 100644 packages/client/src/components/ui/modal-window.vue
 delete mode 100644 packages/client/src/components/ui/modal.vue
 delete mode 100644 packages/client/src/components/ui/pagination.vue
 delete mode 100644 packages/client/src/components/ui/popup-menu.vue
 delete mode 100644 packages/client/src/components/ui/super-menu.vue
 delete mode 100644 packages/client/src/components/ui/tooltip.vue
 delete mode 100644 packages/client/src/components/ui/window.vue

(limited to 'packages/client/src/components/MkUserSelectDialog.vue')

diff --git a/packages/client/src/components/MkAbuseReport.vue b/packages/client/src/components/MkAbuseReport.vue
index 42bd1af3fb..9a3464b640 100644
--- a/packages/client/src/components/MkAbuseReport.vue
+++ b/packages/client/src/components/MkAbuseReport.vue
@@ -36,7 +36,7 @@
 </template>
 
 <script lang="ts" setup>
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import MkSwitch from '@/components/form/switch.vue';
 import MkKeyValue from '@/components/MkKeyValue.vue';
 import { acct, userPage } from '@/filters/user';
diff --git a/packages/client/src/components/MkAbuseReportWindow.vue b/packages/client/src/components/MkAbuseReportWindow.vue
index 6b8e36c4da..1862d0a0e4 100644
--- a/packages/client/src/components/MkAbuseReportWindow.vue
+++ b/packages/client/src/components/MkAbuseReportWindow.vue
@@ -25,9 +25,9 @@
 <script setup lang="ts">
 import { ref } from 'vue';
 import * as Misskey from 'misskey-js';
-import XWindow from '@/components/ui/window.vue';
+import XWindow from '@/components/MkWindow.vue';
 import MkTextarea from '@/components/form/textarea.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
 
diff --git a/packages/client/src/components/MkButton.vue b/packages/client/src/components/MkButton.vue
new file mode 100644
index 0000000000..a052f8f7a9
--- /dev/null
+++ b/packages/client/src/components/MkButton.vue
@@ -0,0 +1,226 @@
+<template>
+<button
+	v-if="!link" class="bghgjjyj _button"
+	:class="{ inline, primary, gradate, danger, rounded, full }"
+	:type="type"
+	@click="emit('click', $event)"
+	@mousedown="onMousedown"
+>
+	<div ref="ripples" class="ripples"></div>
+	<div class="content">
+		<slot></slot>
+	</div>
+</button>
+<MkA
+	v-else class="bghgjjyj _button"
+	:class="{ inline, primary, gradate, danger, rounded, full }"
+	:to="to"
+	@mousedown="onMousedown"
+>
+	<div ref="ripples" class="ripples"></div>
+	<div class="content">
+		<slot></slot>
+	</div>
+</MkA>
+</template>
+
+<script lang="ts" setup>
+import { nextTick, onMounted } from 'vue';
+
+const props = defineProps<{
+	type?: 'button' | 'submit' | 'reset';
+	primary?: boolean;
+	gradate?: boolean;
+	rounded?: boolean;
+	inline?: boolean;
+	link?: boolean;
+	to?: string;
+	autofocus?: boolean;
+	wait?: boolean;
+	danger?: boolean;
+	full?: boolean;
+}>();
+
+const emit = defineEmits<{
+	(ev: 'click', payload: MouseEvent): void;
+}>();
+
+let el = $ref<HTMLElement | null>(null);
+let ripples = $ref<HTMLElement | null>(null);
+
+onMounted(() => {
+	if (props.autofocus) {
+		nextTick(() => {
+			el!.focus();
+		});
+	}
+});
+
+function distance(p, q): number {
+	return Math.hypot(p.x - q.x, p.y - q.y);
+}
+
+function calcCircleScale(boxW, boxH, circleCenterX, circleCenterY): number {
+	const origin = { x: circleCenterX, y: circleCenterY };
+	const dist1 = distance({ x: 0, y: 0 }, origin);
+	const dist2 = distance({ x: boxW, y: 0 }, origin);
+	const dist3 = distance({ x: 0, y: boxH }, origin);
+	const dist4 = distance({ x: boxW, y: boxH }, origin);
+	return Math.max(dist1, dist2, dist3, dist4) * 2;
+}
+
+function onMousedown(evt: MouseEvent): void {
+	const target = evt.target! as HTMLElement;
+	const rect = target.getBoundingClientRect();
+
+	const ripple = document.createElement('div');
+	ripple.style.top = (evt.clientY - rect.top - 1).toString() + 'px';
+	ripple.style.left = (evt.clientX - rect.left - 1).toString() + 'px';
+
+	ripples!.appendChild(ripple);
+
+	const circleCenterX = evt.clientX - rect.left;
+	const circleCenterY = evt.clientY - rect.top;
+
+	const scale = calcCircleScale(target.clientWidth, target.clientHeight, circleCenterX, circleCenterY);
+
+	window.setTimeout(() => {
+		ripple.style.transform = 'scale(' + (scale / 2) + ')';
+	}, 1);
+	window.setTimeout(() => {
+		ripple.style.transition = 'all 1s ease';
+		ripple.style.opacity = '0';
+	}, 1000);
+	window.setTimeout(() => {
+		if (ripples) ripples.removeChild(ripple);
+	}, 2000);
+}
+</script>
+
+<style lang="scss" scoped>
+.bghgjjyj {
+	position: relative;
+	z-index: 1; // 他コンポーネントのbox-shadowに隠されないようにするため
+	display: block;
+	min-width: 100px;
+	width: max-content;
+	padding: 8px 16px;
+	text-align: center;
+	font-weight: normal;
+	font-size: 1em;
+	box-shadow: none;
+	text-decoration: none;
+	background: var(--buttonBg);
+	border-radius: 5px;
+	overflow: clip;
+	box-sizing: border-box;
+	transition: background 0.1s ease;
+
+	&:not(:disabled):hover {
+		background: var(--buttonHoverBg);
+	}
+
+	&:not(:disabled):active {
+		background: var(--buttonHoverBg);
+	}
+
+	&.full {
+		width: 100%;
+	}
+
+	&.rounded {
+		border-radius: 999px;
+	}
+
+	&.primary {
+		font-weight: bold;
+		color: var(--fgOnAccent) !important;
+		background: var(--accent);
+
+		&:not(:disabled):hover {
+			background: var(--X8);
+		}
+
+		&:not(:disabled):active {
+			background: var(--X8);
+		}
+	}
+
+	&.gradate {
+		font-weight: bold;
+		color: var(--fgOnAccent) !important;
+		background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
+
+		&:not(:disabled):hover {
+			background: linear-gradient(90deg, var(--X8), var(--X8));
+		}
+
+		&:not(:disabled):active {
+			background: linear-gradient(90deg, var(--X8), var(--X8));
+		}
+	}
+
+	&.danger {
+		color: #ff2a2a;
+
+		&.primary {
+			color: #fff;
+			background: #ff2a2a;
+
+			&:not(:disabled):hover {
+				background: #ff4242;
+			}
+
+			&:not(:disabled):active {
+				background: #d42e2e;
+			}
+		}
+	}
+
+	&:disabled {
+		opacity: 0.7;
+	}
+
+	&:focus-visible {
+		outline: solid 2px var(--focus);
+		outline-offset: 2px;
+	}
+
+	&.inline {
+		display: inline-block;
+		width: auto;
+		min-width: 100px;
+	}
+
+	> .ripples {
+		position: absolute;
+		z-index: 0;
+		top: 0;
+		left: 0;
+		width: 100%;
+		height: 100%;
+		border-radius: 6px;
+		overflow: hidden;
+
+		::v-deep(div) {
+			position: absolute;
+			width: 2px;
+			height: 2px;
+			border-radius: 100%;
+			background: rgba(0, 0, 0, 0.1);
+			opacity: 1;
+			transform: scale(1);
+			transition: all 0.5s cubic-bezier(0,.5,0,1);
+		}
+	}
+
+	&.primary > .ripples ::v-deep(div) {
+		background: rgba(0, 0, 0, 0.15);
+	}
+
+	> .content {
+		position: relative;
+		z-index: 1;
+	}
+}
+</style>
diff --git a/packages/client/src/components/MkChartTooltip.vue b/packages/client/src/components/MkChartTooltip.vue
index 9b57a1b3d5..a92dd36b61 100644
--- a/packages/client/src/components/MkChartTooltip.vue
+++ b/packages/client/src/components/MkChartTooltip.vue
@@ -14,7 +14,7 @@
 
 <script lang="ts" setup>
 import { } from 'vue';
-import MkTooltip from './ui/tooltip.vue';
+import MkTooltip from './MkTooltip.vue';
 
 const props = defineProps<{
 	showing: boolean;
diff --git a/packages/client/src/components/MkContainer.vue b/packages/client/src/components/MkContainer.vue
new file mode 100644
index 0000000000..4be59adc2a
--- /dev/null
+++ b/packages/client/src/components/MkContainer.vue
@@ -0,0 +1,264 @@
+<template>
+<div v-size="{ max: [380] }" class="ukygtjoj _panel" :class="{ naked, thin, hideHeader: !showHeader, scrollable, closed: !showBody }">
+	<header v-if="showHeader" ref="header">
+		<div class="title"><slot name="header"></slot></div>
+		<div class="sub">
+			<slot name="func"></slot>
+			<button v-if="foldable" class="_button" @click="() => showBody = !showBody">
+				<template v-if="showBody"><i class="fas fa-angle-up"></i></template>
+				<template v-else><i class="fas fa-angle-down"></i></template>
+			</button>
+		</div>
+	</header>
+	<transition
+		:name="$store.state.animation ? 'container-toggle' : ''"
+		@enter="enter"
+		@after-enter="afterEnter"
+		@leave="leave"
+		@after-leave="afterLeave"
+	>
+		<div v-show="showBody" ref="content" class="content" :class="{ omitted }">
+			<slot></slot>
+			<button v-if="omitted" class="fade _button" @click="() => { ignoreOmit = true; omitted = false; }">
+				<span>{{ $ts.showMore }}</span>
+			</button>
+		</div>
+	</transition>
+</div>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue';
+
+export default defineComponent({
+	props: {
+		showHeader: {
+			type: Boolean,
+			required: false,
+			default: true,
+		},
+		thin: {
+			type: Boolean,
+			required: false,
+			default: false,
+		},
+		naked: {
+			type: Boolean,
+			required: false,
+			default: false,
+		},
+		foldable: {
+			type: Boolean,
+			required: false,
+			default: false,
+		},
+		expanded: {
+			type: Boolean,
+			required: false,
+			default: true,
+		},
+		scrollable: {
+			type: Boolean,
+			required: false,
+			default: false,
+		},
+		maxHeight: {
+			type: Number,
+			required: false,
+			default: null,
+		},
+	},
+	data() {
+		return {
+			showBody: this.expanded,
+			omitted: null,
+			ignoreOmit: false,
+		};
+	},
+	mounted() {
+		this.$watch('showBody', showBody => {
+			const headerHeight = this.showHeader ? this.$refs.header.offsetHeight : 0;
+			this.$el.style.minHeight = `${headerHeight}px`;
+			if (showBody) {
+				this.$el.style.flexBasis = 'auto';
+			} else {
+				this.$el.style.flexBasis = `${headerHeight}px`;
+			}
+		}, {
+			immediate: true,
+		});
+
+		this.$el.style.setProperty('--maxHeight', this.maxHeight + 'px');
+
+		const calcOmit = () => {
+			if (this.omitted || this.ignoreOmit || this.maxHeight == null) return;
+			const height = this.$refs.content.offsetHeight;
+			this.omitted = height > this.maxHeight;
+		};
+
+		calcOmit();
+		new ResizeObserver((entries, observer) => {
+			calcOmit();
+		}).observe(this.$refs.content);
+	},
+	methods: {
+		toggleContent(show: boolean) {
+			if (!this.foldable) return;
+			this.showBody = show;
+		},
+
+		enter(el) {
+			const elementHeight = el.getBoundingClientRect().height;
+			el.style.height = 0;
+			el.offsetHeight; // reflow
+			el.style.height = elementHeight + 'px';
+		},
+		afterEnter(el) {
+			el.style.height = null;
+		},
+		leave(el) {
+			const elementHeight = el.getBoundingClientRect().height;
+			el.style.height = elementHeight + 'px';
+			el.offsetHeight; // reflow
+			el.style.height = 0;
+		},
+		afterLeave(el) {
+			el.style.height = null;
+		},
+	},
+});
+</script>
+
+<style lang="scss" scoped>
+.container-toggle-enter-active, .container-toggle-leave-active {
+	overflow-y: hidden;
+	transition: opacity 0.5s, height 0.5s !important;
+}
+.container-toggle-enter-from {
+	opacity: 0;
+}
+.container-toggle-leave-to {
+	opacity: 0;
+}
+
+.ukygtjoj {
+	position: relative;
+	overflow: clip;
+	contain: content;
+
+	&.naked {
+		background: transparent !important;
+		box-shadow: none !important;
+	}
+
+	&.scrollable {
+		display: flex;
+		flex-direction: column;
+
+		> .content {
+			overflow: auto;
+		}
+	}
+
+	> header {
+		position: sticky;
+		top: var(--stickyTop, 0px);
+		left: 0;
+		color: var(--panelHeaderFg);
+		background: var(--panelHeaderBg);
+		border-bottom: solid 0.5px var(--panelHeaderDivider);
+		z-index: 2;
+		line-height: 1.4em;
+
+		> .title {
+			margin: 0;
+			padding: 12px 16px;
+
+			> ::v-deep(i) {
+				margin-right: 6px;
+			}
+
+			&:empty {
+				display: none;
+			}
+		}
+
+		> .sub {
+			position: absolute;
+			z-index: 2;
+			top: 0;
+			right: 0;
+			height: 100%;
+
+			> ::v-deep(button) {
+				width: 42px;
+				height: 100%;
+			}
+		}
+	}
+
+	> .content {
+		--stickyTop: 0px;
+
+		&.omitted {
+			position: relative;
+			max-height: var(--maxHeight);
+			overflow: hidden;
+
+			> .fade {
+				display: block;
+				position: absolute;
+				z-index: 10;
+				bottom: 0;
+				left: 0;
+				width: 100%;
+				height: 64px;
+				background: linear-gradient(0deg, var(--panel), var(--X15));
+
+				> span {
+					display: inline-block;
+					background: var(--panel);
+					padding: 6px 10px;
+					font-size: 0.8em;
+					border-radius: 999px;
+					box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
+				}
+
+				&:hover {
+					> span {
+						background: var(--panelHighlight);
+					}
+				}
+			}
+		}
+	}
+
+	&.max-width_380px, &.thin {
+		> header {
+			> .title {
+				padding: 8px 10px;
+				font-size: 0.9em;
+			}
+		}
+
+		> .content {
+		}
+	}
+}
+
+._forceContainerFull_ .ukygtjoj {
+	> header {
+		> .title {
+			padding: 12px 16px !important;
+		}
+	}
+}
+
+._forceContainerFull_.ukygtjoj {
+	> header {
+		> .title {
+			padding: 12px 16px !important;
+		}
+	}
+}
+</style>
diff --git a/packages/client/src/components/MkContextMenu.vue b/packages/client/src/components/MkContextMenu.vue
new file mode 100644
index 0000000000..cfc9502b41
--- /dev/null
+++ b/packages/client/src/components/MkContextMenu.vue
@@ -0,0 +1,85 @@
+<template>
+<transition :name="$store.state.animation ? 'fade' : ''" appear>
+	<div ref="rootEl" class="nvlagfpb" :style="{ zIndex }" @contextmenu.prevent.stop="() => {}">
+		<MkMenu :items="items" :align="'left'" @close="$emit('closed')"/>
+	</div>
+</transition>
+</template>
+
+<script lang="ts" setup>
+import { onMounted, onBeforeUnmount } from 'vue';
+import MkMenu from './MkMenu.vue';
+import { MenuItem } from './types/menu.vue';
+import contains from '@/scripts/contains';
+import * as os from '@/os';
+
+const props = defineProps<{
+	items: MenuItem[];
+	ev: MouseEvent;
+}>();
+
+const emit = defineEmits<{
+	(ev: 'closed'): void;
+}>();
+
+let rootEl = $ref<HTMLDivElement>();
+
+let zIndex = $ref<number>(os.claimZIndex('high'));
+
+onMounted(() => {
+	let left = props.ev.pageX + 1; // 間違って右ダブルクリックした場合に意図せずアイテムがクリックされるのを防ぐため + 1
+	let top = props.ev.pageY + 1; // 間違って右ダブルクリックした場合に意図せずアイテムがクリックされるのを防ぐため + 1
+
+	const width = rootEl.offsetWidth;
+	const height = rootEl.offsetHeight;
+
+	if (left + width - window.pageXOffset > window.innerWidth) {
+		left = window.innerWidth - width + window.pageXOffset;
+	}
+
+	if (top + height - window.pageYOffset > window.innerHeight) {
+		top = window.innerHeight - height + window.pageYOffset;
+	}
+
+	if (top < 0) {
+		top = 0;
+	}
+
+	if (left < 0) {
+		left = 0;
+	}
+
+	rootEl.style.top = `${top}px`;
+	rootEl.style.left = `${left}px`;
+
+	for (const el of Array.from(document.querySelectorAll('body *'))) {
+		el.addEventListener('mousedown', onMousedown);
+	}
+});
+
+onBeforeUnmount(() => {
+	for (const el of Array.from(document.querySelectorAll('body *'))) {
+		el.removeEventListener('mousedown', onMousedown);
+	}
+});
+
+function onMousedown(evt: Event) {
+	if (!contains(rootEl, evt.target) && (rootEl !== evt.target)) emit('closed');
+}
+</script>
+
+<style lang="scss" scoped>
+.nvlagfpb {
+	position: absolute;
+}
+
+.fade-enter-active, .fade-leave-active {
+	transition: opacity 0.5s cubic-bezier(0.16, 1, 0.3, 1), transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
+	transform-origin: left top;
+}
+
+.fade-enter-from, .fade-leave-to {
+	opacity: 0;
+	transform: scale(0.9);
+}
+</style>
diff --git a/packages/client/src/components/MkCropperDialog.vue b/packages/client/src/components/MkCropperDialog.vue
index c320b21d72..9a09834bf7 100644
--- a/packages/client/src/components/MkCropperDialog.vue
+++ b/packages/client/src/components/MkCropperDialog.vue
@@ -30,7 +30,7 @@ import { nextTick, onMounted } from 'vue';
 import * as misskey from 'misskey-js';
 import Cropper from 'cropperjs';
 import tinycolor from 'tinycolor2';
-import XModalWindow from '@/components/ui/modal-window.vue';
+import XModalWindow from '@/components/MkModalWindow.vue';
 import * as os from '@/os';
 import { $i } from '@/account';
 import { defaultStore } from '@/store';
diff --git a/packages/client/src/components/MkDialog.vue b/packages/client/src/components/MkDialog.vue
index b090f3cb4e..155473cd75 100644
--- a/packages/client/src/components/MkDialog.vue
+++ b/packages/client/src/components/MkDialog.vue
@@ -40,8 +40,8 @@
 
 <script lang="ts" setup>
 import { onBeforeUnmount, onMounted, ref } from 'vue';
-import MkModal from '@/components/ui/modal.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkModal from '@/components/MkModal.vue';
+import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/form/input.vue';
 import MkSelect from '@/components/form/select.vue';
 import { i18n } from '@/i18n';
diff --git a/packages/client/src/components/MkDrive.vue b/packages/client/src/components/MkDrive.vue
index 9f0016adc2..002ca58d04 100644
--- a/packages/client/src/components/MkDrive.vue
+++ b/packages/client/src/components/MkDrive.vue
@@ -90,7 +90,7 @@
 <script lang="ts" setup>
 import { markRaw, nextTick, onActivated, onBeforeUnmount, onMounted, ref, watch } from 'vue';
 import * as Misskey from 'misskey-js';
-import MkButton from './ui/button.vue';
+import MkButton from './MkButton.vue';
 import XNavFolder from '@/components/MkDrive.navFolder.vue';
 import XFolder from '@/components/MkDrive.folder.vue';
 import XFile from '@/components/MkDrive.file.vue';
diff --git a/packages/client/src/components/MkDriveSelectDialog.vue b/packages/client/src/components/MkDriveSelectDialog.vue
index 95a9a6fdb5..baab7f1324 100644
--- a/packages/client/src/components/MkDriveSelectDialog.vue
+++ b/packages/client/src/components/MkDriveSelectDialog.vue
@@ -22,7 +22,7 @@
 import { ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import XDrive from '@/components/MkDrive.vue';
-import XModalWindow from '@/components/ui/modal-window.vue';
+import XModalWindow from '@/components/MkModalWindow.vue';
 import number from '@/filters/number';
 import { i18n } from '@/i18n';
 
diff --git a/packages/client/src/components/MkDriveWindow.vue b/packages/client/src/components/MkDriveWindow.vue
index 113aabb9f3..617200321b 100644
--- a/packages/client/src/components/MkDriveWindow.vue
+++ b/packages/client/src/components/MkDriveWindow.vue
@@ -17,7 +17,7 @@
 import { } from 'vue';
 import * as Misskey from 'misskey-js';
 import XDrive from '@/components/MkDrive.vue';
-import XWindow from '@/components/ui/window.vue';
+import XWindow from '@/components/MkWindow.vue';
 import { i18n } from '@/i18n';
 
 defineProps<{
diff --git a/packages/client/src/components/MkEmojiPickerDialog.vue b/packages/client/src/components/MkEmojiPickerDialog.vue
index 94e66b971a..3b41f9d75b 100644
--- a/packages/client/src/components/MkEmojiPickerDialog.vue
+++ b/packages/client/src/components/MkEmojiPickerDialog.vue
@@ -27,7 +27,7 @@
 
 <script lang="ts" setup>
 import { ref } from 'vue';
-import MkModal from '@/components/ui/modal.vue';
+import MkModal from '@/components/MkModal.vue';
 import MkEmojiPicker from '@/components/MkEmojiPicker.vue';
 import { defaultStore } from '@/store';
 
diff --git a/packages/client/src/components/MkEmojiPickerWindow.vue b/packages/client/src/components/MkEmojiPickerWindow.vue
index a1a2fd9328..523e4ba695 100644
--- a/packages/client/src/components/MkEmojiPickerWindow.vue
+++ b/packages/client/src/components/MkEmojiPickerWindow.vue
@@ -13,7 +13,7 @@
 
 <script lang="ts" setup>
 import { } from 'vue';
-import MkWindow from '@/components/ui/window.vue';
+import MkWindow from '@/components/MkWindow.vue';
 import MkEmojiPicker from '@/components/MkEmojiPicker.vue';
 
 withDefaults(defineProps<{
diff --git a/packages/client/src/components/MkFileListForAdmin.vue b/packages/client/src/components/MkFileListForAdmin.vue
index e03b8b94d8..b6429eaf8d 100644
--- a/packages/client/src/components/MkFileListForAdmin.vue
+++ b/packages/client/src/components/MkFileListForAdmin.vue
@@ -35,7 +35,7 @@
 import { computed } from 'vue';
 import * as Acct from 'misskey-js/built/acct';
 import MkSwitch from '@/components/ui/switch.vue';
-import MkPagination from '@/components/ui/pagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
 import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
 import bytes from '@/filters/bytes';
 import * as os from '@/os';
diff --git a/packages/client/src/components/MkFolder.vue b/packages/client/src/components/MkFolder.vue
new file mode 100644
index 0000000000..7daa82cbd3
--- /dev/null
+++ b/packages/client/src/components/MkFolder.vue
@@ -0,0 +1,156 @@
+<template>
+<div v-size="{ max: [500] }" class="ssazuxis">
+	<header class="_button" :style="{ background: bg }" @click="showBody = !showBody">
+		<div class="title"><slot name="header"></slot></div>
+		<div class="divider"></div>
+		<button class="_button">
+			<template v-if="showBody"><i class="fas fa-angle-up"></i></template>
+			<template v-else><i class="fas fa-angle-down"></i></template>
+		</button>
+	</header>
+	<transition :name="$store.state.animation ? 'folder-toggle' : ''"
+		@enter="enter"
+		@after-enter="afterEnter"
+		@leave="leave"
+		@after-leave="afterLeave"
+	>
+		<div v-show="showBody">
+			<slot></slot>
+		</div>
+	</transition>
+</div>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue';
+import tinycolor from 'tinycolor2';
+
+const localStoragePrefix = 'ui:folder:';
+
+export default defineComponent({
+	props: {
+		expanded: {
+			type: Boolean,
+			required: false,
+			default: true
+		},
+		persistKey: {
+			type: String,
+			required: false,
+			default: null
+		},
+	},
+	data() {
+		return {
+			bg: null,
+			showBody: (this.persistKey && localStorage.getItem(localStoragePrefix + this.persistKey)) ? localStorage.getItem(localStoragePrefix + this.persistKey) === 't' : this.expanded,
+		};
+	},
+	watch: {
+		showBody() {
+			if (this.persistKey) {
+				localStorage.setItem(localStoragePrefix + this.persistKey, this.showBody ? 't' : 'f');
+			}
+		}
+	},
+	mounted() {
+		function getParentBg(el: Element | null): string {
+			if (el == null || el.tagName === 'BODY') return 'var(--bg)';
+			const bg = el.style.background || el.style.backgroundColor;
+			if (bg) {
+				return bg;
+			} else {
+				return getParentBg(el.parentElement);
+			}
+		}
+		const rawBg = getParentBg(this.$el);
+		const bg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg);
+		bg.setAlpha(0.85);
+		this.bg = bg.toRgbString();
+	},
+	methods: {
+		toggleContent(show: boolean) {
+			this.showBody = show;
+		},
+
+		enter(el) {
+			const elementHeight = el.getBoundingClientRect().height;
+			el.style.height = 0;
+			el.offsetHeight; // reflow
+			el.style.height = elementHeight + 'px';
+		},
+		afterEnter(el) {
+			el.style.height = null;
+		},
+		leave(el) {
+			const elementHeight = el.getBoundingClientRect().height;
+			el.style.height = elementHeight + 'px';
+			el.offsetHeight; // reflow
+			el.style.height = 0;
+		},
+		afterLeave(el) {
+			el.style.height = null;
+		},
+	}
+});
+</script>
+
+<style lang="scss" scoped>
+.folder-toggle-enter-active, .folder-toggle-leave-active {
+	overflow-y: hidden;
+	transition: opacity 0.5s, height 0.5s !important;
+}
+.folder-toggle-enter-from {
+	opacity: 0;
+}
+.folder-toggle-leave-to {
+	opacity: 0;
+}
+
+.ssazuxis {
+	position: relative;
+
+	> header {
+		display: flex;
+		position: relative;
+		z-index: 10;
+		position: sticky;
+		top: var(--stickyTop, 0px);
+		padding: var(--x-padding);
+		-webkit-backdrop-filter: var(--blur, blur(8px));
+		backdrop-filter: var(--blur, blur(20px));
+
+		> .title {
+			margin: 0;
+			padding: 12px 16px 12px 0;
+
+			> i {
+				margin-right: 6px;
+			}
+
+			&:empty {
+				display: none;
+			}
+		}
+
+		> .divider {
+			flex: 1;
+			margin: auto;
+			height: 1px;
+			background: var(--divider);
+		}
+
+		> button {
+			padding: 12px 0 12px 16px;
+		}
+	}
+
+	&.max-width_500px {
+		> header {
+			> .title {
+				padding: 8px 10px 8px 0;
+			}
+		}
+	}
+}
+</style>
diff --git a/packages/client/src/components/MkForgotPassword.vue b/packages/client/src/components/MkForgotPassword.vue
index 6ed89d45d7..1b55451c94 100644
--- a/packages/client/src/components/MkForgotPassword.vue
+++ b/packages/client/src/components/MkForgotPassword.vue
@@ -33,8 +33,8 @@
 
 <script lang="ts" setup>
 import { } from 'vue';
-import XModalWindow from '@/components/ui/modal-window.vue';
-import MkButton from '@/components/ui/button.vue';
+import XModalWindow from '@/components/MkModalWindow.vue';
+import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/form/input.vue';
 import * as os from '@/os';
 import { instance } from '@/instance';
diff --git a/packages/client/src/components/MkFormDialog.vue b/packages/client/src/components/MkFormDialog.vue
index f05dde16f8..b2bf76a8c7 100644
--- a/packages/client/src/components/MkFormDialog.vue
+++ b/packages/client/src/components/MkFormDialog.vue
@@ -61,9 +61,9 @@ import FormTextarea from './form/textarea.vue';
 import FormSwitch from './form/switch.vue';
 import FormSelect from './form/select.vue';
 import FormRange from './form/range.vue';
-import MkButton from './ui/button.vue';
+import MkButton from './MkButton.vue';
 import FormRadios from './form/radios.vue';
-import XModalWindow from '@/components/ui/modal-window.vue';
+import XModalWindow from '@/components/MkModalWindow.vue';
 
 export default defineComponent({
 	components: {
diff --git a/packages/client/src/components/MkImageViewer.vue b/packages/client/src/components/MkImageViewer.vue
index 7bc88399ef..f074b1a2f2 100644
--- a/packages/client/src/components/MkImageViewer.vue
+++ b/packages/client/src/components/MkImageViewer.vue
@@ -17,7 +17,7 @@ import { } from 'vue';
 import * as misskey from 'misskey-js';
 import bytes from '@/filters/bytes';
 import number from '@/filters/number';
-import MkModal from '@/components/ui/modal.vue';
+import MkModal from '@/components/MkModal.vue';
 
 const props = withDefaults(defineProps<{
 	image: misskey.entities.DriveFile;
diff --git a/packages/client/src/components/MkInfo.vue b/packages/client/src/components/MkInfo.vue
new file mode 100644
index 0000000000..4fdfc5c5e6
--- /dev/null
+++ b/packages/client/src/components/MkInfo.vue
@@ -0,0 +1,34 @@
+<template>
+<div class="fpezltsf" :class="{ warn }">
+	<i v-if="warn" class="fas fa-exclamation-triangle"></i>
+	<i v-else class="fas fa-info-circle"></i>
+	<slot></slot>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { } from 'vue';
+
+const props = defineProps<{
+	warn?: boolean;
+}>();
+</script>
+
+<style lang="scss" scoped>
+.fpezltsf {
+	padding: 16px;
+	font-size: 90%;
+	background: var(--infoBg);
+	color: var(--infoFg);
+	border-radius: var(--radius);
+
+	&.warn {
+		background: var(--infoWarnBg);
+		color: var(--infoWarnFg);
+	}
+
+	> i {
+		margin-right: 4px;
+	}
+}
+</style>
diff --git a/packages/client/src/components/MkLaunchPad.vue b/packages/client/src/components/MkLaunchPad.vue
index 7891f61bf1..19283178c9 100644
--- a/packages/client/src/components/MkLaunchPad.vue
+++ b/packages/client/src/components/MkLaunchPad.vue
@@ -21,7 +21,7 @@
 
 <script lang="ts" setup>
 import { } from 'vue';
-import MkModal from '@/components/ui/modal.vue';
+import MkModal from '@/components/MkModal.vue';
 import { navbarItemDef } from '@/navbar';
 import { instanceName } from '@/config';
 import { defaultStore } from '@/store';
diff --git a/packages/client/src/components/MkMediaCaption.vue b/packages/client/src/components/MkMediaCaption.vue
index feed3854f9..c25755d762 100644
--- a/packages/client/src/components/MkMediaCaption.vue
+++ b/packages/client/src/components/MkMediaCaption.vue
@@ -30,8 +30,8 @@
 <script lang="ts">
 import { defineComponent } from 'vue';
 import { length } from 'stringz';
-import MkModal from '@/components/ui/modal.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkModal from '@/components/MkModal.vue';
+import MkButton from '@/components/MkButton.vue';
 import bytes from '@/filters/bytes';
 import number from '@/filters/number';
 
diff --git a/packages/client/src/components/MkMenu.child.vue b/packages/client/src/components/MkMenu.child.vue
new file mode 100644
index 0000000000..3ada4afbdc
--- /dev/null
+++ b/packages/client/src/components/MkMenu.child.vue
@@ -0,0 +1,65 @@
+<template>
+<div ref="el" class="sfhdhdhr">
+	<MkMenu ref="menu" :items="items" :align="align" :width="width" :as-drawer="false" @close="onChildClosed"/>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { on } from 'events';
+import { nextTick, onBeforeUnmount, onMounted, onUnmounted, ref, watch } from 'vue';
+import MkMenu from './MkMenu.vue';
+import { MenuItem } from '@/types/menu';
+import * as os from '@/os';
+
+const props = defineProps<{
+	items: MenuItem[];
+	targetElement: HTMLElement;
+	rootElement: HTMLElement;
+	width?: number;
+	viaKeyboard?: boolean;
+}>();
+
+const emit = defineEmits<{
+	(ev: 'closed'): void;
+	(ev: 'actioned'): void;
+}>();
+
+const el = ref<HTMLElement>();
+const align = 'left';
+
+function setPosition() {
+	const rootRect = props.rootElement.getBoundingClientRect();
+	const rect = props.targetElement.getBoundingClientRect();
+	const left = props.targetElement.offsetWidth;
+	const top = (rect.top - rootRect.top) - 8;
+	el.value.style.left = left + 'px';
+	el.value.style.top = top + 'px';
+}
+
+function onChildClosed(actioned?: boolean) {
+	if (actioned) {
+		emit('actioned');
+	} else {
+		emit('closed');
+	}
+}
+
+onMounted(() => {
+	setPosition();
+	nextTick(() => {
+		setPosition();
+	});
+});
+
+defineExpose({
+	checkHit: (ev: MouseEvent) => {
+		return (ev.target === el.value || el.value.contains(ev.target));
+	},
+});
+</script>
+
+<style lang="scss" scoped>
+.sfhdhdhr {
+	position: absolute;
+}
+</style>
diff --git a/packages/client/src/components/MkMenu.vue b/packages/client/src/components/MkMenu.vue
new file mode 100644
index 0000000000..578e736c83
--- /dev/null
+++ b/packages/client/src/components/MkMenu.vue
@@ -0,0 +1,364 @@
+<template>
+<div>
+	<div
+		ref="itemsEl" v-hotkey="keymap"
+		class="rrevdjwt _popup _shadow"
+		:class="{ center: align === 'center', asDrawer }"
+		:style="{ width: (width && !asDrawer) ? width + 'px' : '', maxHeight: maxHeight ? maxHeight + 'px' : '' }"
+		@contextmenu.self="e => e.preventDefault()"
+	>
+		<template v-for="(item, i) in items2">
+			<div v-if="item === null" class="divider"></div>
+			<span v-else-if="item.type === 'label'" class="label item">
+				<span>{{ item.text }}</span>
+			</span>
+			<span v-else-if="item.type === 'pending'" :tabindex="i" class="pending item">
+				<span><MkEllipsis/></span>
+			</span>
+			<MkA v-else-if="item.type === 'link'" :to="item.to" :tabindex="i" class="_button item" @click.passive="close(true)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
+				<i v-if="item.icon" class="fa-fw" :class="item.icon"></i>
+				<MkAvatar v-if="item.avatar" :user="item.avatar" class="avatar"/>
+				<span>{{ item.text }}</span>
+				<span v-if="item.indicate" class="indicator"><i class="fas fa-circle"></i></span>
+			</MkA>
+			<a v-else-if="item.type === 'a'" :href="item.href" :target="item.target" :download="item.download" :tabindex="i" class="_button item" @click="close(true)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
+				<i v-if="item.icon" class="fa-fw" :class="item.icon"></i>
+				<span>{{ item.text }}</span>
+				<span v-if="item.indicate" class="indicator"><i class="fas fa-circle"></i></span>
+			</a>
+			<button v-else-if="item.type === 'user'" :tabindex="i" class="_button item" :class="{ active: item.active }" :disabled="item.active" @click="clicked(item.action, $event)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
+				<MkAvatar :user="item.user" class="avatar"/><MkUserName :user="item.user"/>
+				<span v-if="item.indicate" class="indicator"><i class="fas fa-circle"></i></span>
+			</button>
+			<span v-else-if="item.type === 'switch'" :tabindex="i" class="item" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
+				<FormSwitch v-model="item.ref" :disabled="item.disabled" class="form-switch">{{ item.text }}</FormSwitch>
+			</span>
+			<button v-else-if="item.type === 'parent'" :tabindex="i" class="_button item parent" :class="{ childShowing: childShowingItem === item }" @mouseenter="showChildren(item, $event)">
+				<i v-if="item.icon" class="fa-fw" :class="item.icon"></i>
+				<span>{{ item.text }}</span>
+				<span class="caret"><i class="fas fa-caret-right fa-fw"></i></span>
+			</button>
+			<button v-else :tabindex="i" class="_button item" :class="{ danger: item.danger, active: item.active }" :disabled="item.active" @click="clicked(item.action, $event)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
+				<i v-if="item.icon" class="fa-fw" :class="item.icon"></i>
+				<MkAvatar v-if="item.avatar" :user="item.avatar" class="avatar"/>
+				<span>{{ item.text }}</span>
+				<span v-if="item.indicate" class="indicator"><i class="fas fa-circle"></i></span>
+			</button>
+		</template>
+		<span v-if="items2.length === 0" class="none item">
+			<span>{{ i18n.ts.none }}</span>
+		</span>
+	</div>
+	<div v-if="childMenu" class="child">
+		<XChild ref="child" :items="childMenu" :target-element="childTarget" :root-element="itemsEl" showing @actioned="childActioned"/>
+	</div>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { defineAsyncComponent, nextTick, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
+import { focusPrev, focusNext } from '@/scripts/focus';
+import FormSwitch from '@/components/form/switch.vue';
+import { MenuItem, InnerMenuItem, MenuPending, MenuAction } from '@/types/menu';
+import * as os from '@/os';
+import { i18n } from '@/i18n';
+
+const XChild = defineAsyncComponent(() => import('./MkMenu.child.vue'));
+
+const props = defineProps<{
+	items: MenuItem[];
+	viaKeyboard?: boolean;
+	asDrawer?: boolean;
+	align?: 'center' | string;
+	width?: number;
+	maxHeight?: number;
+}>();
+
+const emit = defineEmits<{
+	(ev: 'close', actioned?: boolean): void;
+}>();
+
+let itemsEl = $ref<HTMLDivElement>();
+
+let items2: InnerMenuItem[] = $ref([]);
+
+let child = $ref<InstanceType<typeof XChild>>();
+
+let keymap = $computed(() => ({
+	'up|k|shift+tab': focusUp,
+	'down|j|tab': focusDown,
+	'esc': close,
+}));
+
+let childShowingItem = $ref<MenuItem | null>();
+
+watch(() => props.items, () => {
+	const items: (MenuItem | MenuPending)[] = [...props.items].filter(item => item !== undefined);
+
+	for (let i = 0; i < items.length; i++) {
+		const item = items[i];
+
+		if (item && 'then' in item) { // if item is Promise
+			items[i] = { type: 'pending' };
+			item.then(actualItem => {
+				items2[i] = actualItem;
+			});
+		}
+	}
+
+	items2 = items as InnerMenuItem[];
+}, {
+	immediate: true,
+});
+
+let childMenu = $ref<MenuItem[] | null>();
+let childTarget = $ref<HTMLElement | null>();
+
+function closeChild() {
+	childMenu = null;
+	childShowingItem = null;
+}
+
+function childActioned() {
+	closeChild();
+	close(true);
+}
+
+function onGlobalMousedown(event: MouseEvent) {
+	if (childTarget && (event.target === childTarget || childTarget.contains(event.target))) return;
+	if (child && child.checkHit(event)) return;
+	closeChild();
+}
+
+let childCloseTimer: null | number = null;
+function onItemMouseEnter(item) {
+	childCloseTimer = window.setTimeout(() => {
+		closeChild();
+	}, 300);
+}
+function onItemMouseLeave(item) {
+	if (childCloseTimer) window.clearTimeout(childCloseTimer);
+}
+
+async function showChildren(item: MenuItem, ev: MouseEvent) {
+	if (props.asDrawer) {
+		os.popupMenu(item.children, ev.currentTarget ?? ev.target);
+		close();
+	} else {
+		childTarget = ev.currentTarget ?? ev.target;
+		childMenu = item.children;
+		childShowingItem = item;
+	}
+}
+
+function clicked(fn: MenuAction, ev: MouseEvent) {
+	fn(ev);
+	close(true);
+}
+
+function close(actioned = false) {
+	emit('close', actioned);
+}
+
+function focusUp() {
+	focusPrev(document.activeElement);
+}
+
+function focusDown() {
+	focusNext(document.activeElement);
+}
+
+onMounted(() => {
+	if (props.viaKeyboard) {
+		nextTick(() => {
+			focusNext(itemsEl.children[0], true, false);
+		});
+	}
+
+	document.addEventListener('mousedown', onGlobalMousedown, { passive: true });
+});
+
+onBeforeUnmount(() => {
+	document.removeEventListener('mousedown', onGlobalMousedown);
+});
+</script>
+
+<style lang="scss" scoped>
+.rrevdjwt {
+	padding: 8px 0;
+	box-sizing: border-box;
+	min-width: 200px;
+	overflow: auto;
+	overscroll-behavior: contain;
+
+	&.center {
+		> .item {
+			text-align: center;
+		}
+	}
+
+	> .item {
+		display: block;
+		position: relative;
+		padding: 6px 16px;
+		width: 100%;
+		box-sizing: border-box;
+		white-space: nowrap;
+		font-size: 0.9em;
+		line-height: 20px;
+		text-align: left;
+		overflow: hidden;
+		text-overflow: ellipsis;
+
+		&:before {
+			content: "";
+			display: block;
+			position: absolute;
+			top: 0;
+			left: 0;
+			right: 0;
+			margin: auto;
+			width: calc(100% - 16px);
+			height: 100%;
+			border-radius: 6px;
+		}
+
+		> * {
+			position: relative;
+		}
+
+		&:not(:disabled):hover {
+			color: var(--accent);
+			text-decoration: none;
+
+			&:before {
+				background: var(--accentedBg);
+			}
+		}
+
+		&.danger {
+			color: #ff2a2a;
+
+			&:hover {
+				color: #fff;
+
+				&:before {
+					background: #ff4242;
+				}
+			}
+
+			&:active {
+				color: #fff;
+
+				&:before {
+					background: #d42e2e;
+				}
+			}
+		}
+
+		&.active {
+			color: var(--fgOnAccent);
+			opacity: 1;
+
+			&:before {
+				background: var(--accent);
+			}
+		}
+
+		&:not(:active):focus-visible {
+			box-shadow: 0 0 0 2px var(--focus) inset;
+		}
+
+		&.label {
+			pointer-events: none;
+			font-size: 0.7em;
+			padding-bottom: 4px;
+
+			> span {
+				opacity: 0.7;
+			}
+		}
+
+		&.pending {
+			pointer-events: none;
+			opacity: 0.7;
+		}
+
+		&.none {
+			pointer-events: none;
+			opacity: 0.7;
+		}
+
+		&.parent {
+			display: flex;
+			align-items: center;
+			cursor: default;
+
+			> .caret {
+				margin-left: auto;
+			}
+
+			&.childShowing {
+				color: var(--accent);
+				text-decoration: none;
+
+				&:before {
+					background: var(--accentedBg);
+				}
+			}
+		}
+
+		> i {
+			margin-right: 5px;
+			width: 20px;
+		}
+
+		> .avatar {
+			margin-right: 5px;
+			width: 20px;
+			height: 20px;
+		}
+
+		> .indicator {
+			position: absolute;
+			top: 5px;
+			left: 13px;
+			color: var(--indicator);
+			font-size: 12px;
+			animation: blink 1s infinite;
+		}
+	}
+
+	> .divider {
+		margin: 8px 0;
+		border-top: solid 0.5px var(--divider);
+	}
+
+	&.asDrawer {
+		padding: 12px 0 calc(env(safe-area-inset-bottom, 0px) + 12px) 0;
+		width: 100%;
+		border-radius: 24px;
+		border-bottom-right-radius: 0;
+		border-bottom-left-radius: 0;
+
+		> .item {
+			font-size: 1em;
+			padding: 12px 24px;
+
+			&:before {
+				width: calc(100% - 24px);
+				border-radius: 12px;
+			}
+
+			> i {
+				margin-right: 14px;
+				width: 24px;
+			}
+		}
+
+		> .divider {
+			margin: 12px 0;
+		}
+	}
+}
+</style>
diff --git a/packages/client/src/components/MkModal.vue b/packages/client/src/components/MkModal.vue
new file mode 100644
index 0000000000..2305a02794
--- /dev/null
+++ b/packages/client/src/components/MkModal.vue
@@ -0,0 +1,406 @@
+<template>
+<transition :name="$store.state.animation ? (type === 'drawer') ? 'modal-drawer' : (type === 'popup') ? 'modal-popup' : 'modal' : ''" :duration="$store.state.animation ? 200 : 0" appear @after-leave="emit('closed')" @enter="emit('opening')" @after-enter="onOpened">
+	<div v-show="manualShowing != null ? manualShowing : showing" v-hotkey.global="keymap" class="qzhlnise" :class="{ drawer: type === 'drawer', dialog: type === 'dialog' || type === 'dialog:top', popup: type === 'popup' }" :style="{ zIndex, pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }">
+		<div class="bg _modalBg" :class="{ transparent: transparentBg && (type === 'popup') }" :style="{ zIndex }" @click="onBgClick" @contextmenu.prevent.stop="() => {}"></div>
+		<div ref="content" class="content" :class="{ fixed, top: type === 'dialog:top' }" :style="{ zIndex }" @click.self="onBgClick">
+			<slot :max-height="maxHeight" :type="type"></slot>
+		</div>
+	</div>
+</transition>
+</template>
+
+<script lang="ts" setup>
+import { nextTick, onMounted, watch, provide } from 'vue';
+import * as os from '@/os';
+import { isTouchUsing } from '@/scripts/touch';
+import { defaultStore } from '@/store';
+import { deviceKind } from '@/scripts/device-kind';
+
+function getFixedContainer(el: Element | null): Element | null {
+	if (el == null || el.tagName === 'BODY') return null;
+	const position = window.getComputedStyle(el).getPropertyValue('position');
+	if (position === 'fixed') {
+		return el;
+	} else {
+		return getFixedContainer(el.parentElement);
+	}
+}
+
+type ModalTypes = 'popup' | 'dialog' | 'dialog:top' | 'drawer';
+
+const props = withDefaults(defineProps<{
+	manualShowing?: boolean | null;
+	anchor?: { x: string; y: string; };
+	src?: HTMLElement;
+	preferType?: ModalTypes | 'auto';
+	zPriority?: 'low' | 'middle' | 'high';
+	noOverlap?: boolean;
+	transparentBg?: boolean;
+}>(), {
+	manualShowing: null,
+	src: null,
+	anchor: () => ({ x: 'center', y: 'bottom' }),
+	preferType: 'auto',
+	zPriority: 'low',
+	noOverlap: true,
+	transparentBg: false,
+});
+
+const emit = defineEmits<{
+	(ev: 'opening'): void;
+	(ev: 'opened'): void;
+	(ev: 'click'): void;
+	(ev: 'esc'): void;
+	(ev: 'close'): void;
+	(ev: 'closed'): void;
+}>();
+
+provide('modal', true);
+
+let maxHeight = $ref<number>();
+let fixed = $ref(false);
+let transformOrigin = $ref('center');
+let showing = $ref(true);
+let content = $ref<HTMLElement>();
+const zIndex = os.claimZIndex(props.zPriority);
+const type = $computed(() => {
+	if (props.preferType === 'auto') {
+		if (!defaultStore.state.disableDrawer && isTouchUsing && deviceKind === 'smartphone') {
+			return 'drawer';
+		} else {
+			return props.src != null ? 'popup' : 'dialog';
+		}
+	} else {
+		return props.preferType!;
+	}
+});
+
+let contentClicking = false;
+
+const close = () => {
+	// eslint-disable-next-line vue/no-mutating-props
+	if (props.src) props.src.style.pointerEvents = 'auto';
+	showing = false;
+	emit('close');
+};
+
+const onBgClick = () => {
+	if (contentClicking) return;
+	emit('click');
+};
+
+if (type === 'drawer') {
+	maxHeight = window.innerHeight / 1.5;
+}
+
+const keymap = {
+	'esc': () => emit('esc'),
+};
+
+const MARGIN = 16;
+
+const align = () => {
+	if (props.src == null) return;
+	if (type === 'drawer') return;
+	if (type === 'dialog') return;
+
+	if (content == null) return;
+
+	const srcRect = props.src.getBoundingClientRect();
+
+	const width = content!.offsetWidth;
+	const height = content!.offsetHeight;
+
+	let left;
+	let top;
+
+	const x = srcRect.left + (fixed ? 0 : window.pageXOffset);
+	const y = srcRect.top + (fixed ? 0 : window.pageYOffset);
+
+	if (props.anchor.x === 'center') {
+		left = x + (props.src.offsetWidth / 2) - (width / 2);
+	} else if (props.anchor.x === 'left') {
+		// TODO
+	} else if (props.anchor.x === 'right') {
+		left = x + props.src.offsetWidth;
+	}
+
+	if (props.anchor.y === 'center') {
+		top = (y - (height / 2));
+	} else if (props.anchor.y === 'top') {
+		// TODO
+	} else if (props.anchor.y === 'bottom') {
+		top = y + props.src.offsetHeight;
+	}
+
+	if (fixed) {
+		// 画面から横にはみ出る場合
+		if (left + width > window.innerWidth) {
+			left = window.innerWidth - width;
+		}
+
+		const underSpace = (window.innerHeight - MARGIN) - top;
+		const upperSpace = (srcRect.top - MARGIN);
+
+		// 画面から縦にはみ出る場合
+		if (top + height > (window.innerHeight - MARGIN)) {
+			if (props.noOverlap && props.anchor.x === 'center') {
+				if (underSpace >= (upperSpace / 3)) {
+					maxHeight = underSpace;
+				} else {
+					maxHeight = upperSpace;
+					top = (upperSpace + MARGIN) - height;
+				}
+			} else {
+				top = (window.innerHeight - MARGIN) - height;
+			}
+		} else {
+			maxHeight = underSpace;
+		}
+	} else {
+		// 画面から横にはみ出る場合
+		if (left + width - window.pageXOffset > window.innerWidth) {
+			left = window.innerWidth - width + window.pageXOffset - 1;
+		}
+
+		const underSpace = (window.innerHeight - MARGIN) - (top - window.pageYOffset);
+		const upperSpace = (srcRect.top - MARGIN);
+
+		// 画面から縦にはみ出る場合
+		if (top + height - window.pageYOffset > (window.innerHeight - MARGIN)) {
+			if (props.noOverlap && props.anchor.x === 'center') {
+				if (underSpace >= (upperSpace / 3)) {
+					maxHeight = underSpace;
+				} else {
+					maxHeight = upperSpace;
+					top = window.pageYOffset + ((upperSpace + MARGIN) - height);
+				}
+			} else {
+				top = (window.innerHeight - MARGIN) - height + window.pageYOffset - 1;
+			}
+		} else {
+			maxHeight = underSpace;
+		}
+	}
+
+	if (top < 0) {
+		top = MARGIN;
+	}
+
+	if (left < 0) {
+		left = 0;
+	}
+
+	let transformOriginX = 'center';
+	let transformOriginY = 'center';
+
+	if (top >= srcRect.top + props.src.offsetHeight + (fixed ? 0 : window.pageYOffset)) {
+		transformOriginY = 'top';
+	} else if ((top + height) <= srcRect.top + (fixed ? 0 : window.pageYOffset)) {
+		transformOriginY = 'bottom';
+	}
+
+	if (left >= srcRect.left + props.src.offsetWidth + (fixed ? 0 : window.pageXOffset)) {
+		transformOriginX = 'left';
+	} else if ((left + width) <= srcRect.left + (fixed ? 0 : window.pageXOffset)) {
+		transformOriginX = 'right';
+	}
+
+	transformOrigin = `${transformOriginX} ${transformOriginY}`;
+
+	content.style.left = left + 'px';
+	content.style.top = top + 'px';
+};
+
+const onOpened = () => {
+	emit('opened');
+
+	// モーダルコンテンツにマウスボタンが押され、コンテンツ外でマウスボタンが離されたときにモーダルバックグラウンドクリックと判定させないためにマウスイベントを監視しフラグ管理する
+	const el = content!.children[0];
+	el.addEventListener('mousedown', ev => {
+		contentClicking = true;
+		window.addEventListener('mouseup', ev => {
+			// click イベントより先に mouseup イベントが発生するかもしれないのでちょっと待つ
+			window.setTimeout(() => {
+				contentClicking = false;
+			}, 100);
+		}, { passive: true, once: true });
+	}, { passive: true });
+};
+
+onMounted(() => {
+	watch(() => props.src, async () => {
+		if (props.src) {
+			// eslint-disable-next-line vue/no-mutating-props
+			props.src.style.pointerEvents = 'none';
+		}
+		fixed = (type === 'drawer') || (getFixedContainer(props.src) != null);
+
+		await nextTick();
+		
+		align();
+	}, { immediate: true });
+
+	nextTick(() => {
+		new ResizeObserver((entries, observer) => {
+			align();
+		}).observe(content!);
+	});
+});
+
+defineExpose({
+	close,
+});
+</script>
+
+<style lang="scss" scoped>
+.modal-enter-active, .modal-leave-active {
+	> .bg {
+		transition: opacity 0.2s !important;
+	}
+
+	> .content {
+		transform-origin: var(--transformOrigin);
+		transition: opacity 0.2s, transform 0.2s !important;
+	}
+}
+.modal-enter-from, .modal-leave-to {
+	> .bg {
+		opacity: 0;
+	}
+
+	> .content {
+		pointer-events: none;
+		opacity: 0;
+		transform-origin: var(--transformOrigin);
+		transform: scale(0.9);
+	}
+}
+
+.modal-popup-enter-active, .modal-popup-leave-active {
+	> .bg {
+		transition: opacity 0.2s !important;
+	}
+
+	> .content {
+		transform-origin: var(--transformOrigin);
+		transition: opacity 0.2s cubic-bezier(0, 0, 0.2, 1), transform 0.2s cubic-bezier(0, 0, 0.2, 1) !important;
+	}
+}
+.modal-popup-enter-from, .modal-popup-leave-to {
+	> .bg {
+		opacity: 0;
+	}
+
+	> .content {
+		pointer-events: none;
+		opacity: 0;
+		transform-origin: var(--transformOrigin);
+		transform: scale(0.9);
+	}
+}
+
+.modal-drawer-enter-active {
+	> .bg {
+		transition: opacity 0.2s !important;
+	}
+
+	> .content {
+		transition: transform 0.2s cubic-bezier(0,.5,0,1) !important;
+	}
+}
+.modal-drawer-leave-active {
+	> .bg {
+		transition: opacity 0.2s !important;
+	}
+
+	> .content {
+		transition: transform 0.2s cubic-bezier(0,.5,0,1) !important;
+	}
+}
+.modal-drawer-enter-from, .modal-drawer-leave-to {
+	> .bg {
+		opacity: 0;
+	}
+
+	> .content {
+		pointer-events: none;
+		transform: translateY(100%);
+	}
+}
+
+.qzhlnise {
+	> .bg {
+		&.transparent {
+			background: transparent;
+			-webkit-backdrop-filter: none;
+			backdrop-filter: none;
+		}
+	}
+
+	&.dialog {
+		> .content {
+			position: fixed;
+			top: 0;
+			bottom: 0;
+			left: 0;
+			right: 0;
+			margin: auto;
+			padding: 32px;
+			// TODO: mask-imageはiOSだとやたら重い。なんとかしたい
+			-webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 32px, rgba(0,0,0,1) calc(100% - 32px), rgba(0,0,0,0) 100%);
+			mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 32px, rgba(0,0,0,1) calc(100% - 32px), rgba(0,0,0,0) 100%);
+			overflow: auto;
+			display: flex;
+
+			@media (max-width: 500px) {
+				padding: 16px;
+				-webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 16px, rgba(0,0,0,1) calc(100% - 16px), rgba(0,0,0,0) 100%);
+				mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 16px, rgba(0,0,0,1) calc(100% - 16px), rgba(0,0,0,0) 100%);
+			}
+
+			> ::v-deep(*) {
+				margin: auto;
+			}
+
+			&.top {
+				> ::v-deep(*) {
+					margin-top: 0;
+				}
+			}
+		}
+	}
+
+	&.popup {
+		> .content {
+			position: absolute;
+
+			&.fixed {
+				position: fixed;
+			}
+		}
+	}
+
+	&.drawer {
+		position: fixed;
+		top: 0;
+		left: 0;
+		width: 100%;
+		height: 100%;
+		overflow: clip;
+
+		> .content {
+			position: fixed;
+			bottom: 0;
+			left: 0;
+			right: 0;
+			margin: auto;
+
+			> ::v-deep(*) {
+				margin: auto;
+			}
+		}
+	}
+
+}
+</style>
diff --git a/packages/client/src/components/MkModalPageWindow.vue b/packages/client/src/components/MkModalPageWindow.vue
index 2fed0d35e8..cc3f4c96cc 100644
--- a/packages/client/src/components/MkModalPageWindow.vue
+++ b/packages/client/src/components/MkModalPageWindow.vue
@@ -22,7 +22,7 @@
 
 <script lang="ts" setup>
 import { ComputedRef, provide } from 'vue';
-import MkModal from '@/components/ui/modal.vue';
+import MkModal from '@/components/MkModal.vue';
 import { popout as _popout } from '@/scripts/popout';
 import copyToClipboard from '@/scripts/copy-to-clipboard';
 import { url } from '@/config';
diff --git a/packages/client/src/components/MkModalWindow.vue b/packages/client/src/components/MkModalWindow.vue
new file mode 100644
index 0000000000..5acd8c921f
--- /dev/null
+++ b/packages/client/src/components/MkModalWindow.vue
@@ -0,0 +1,146 @@
+<template>
+<MkModal ref="modal" :prefer-type="'dialog'" @click="onBgClick" @closed="$emit('closed')">
+	<div ref="rootEl" class="ebkgoccj _narrow_" :style="{ width: `${width}px`, height: scroll ? (height ? `${height}px` : null) : (height ? `min(${height}px, 100%)` : '100%') }" @keydown="onKeydown">
+		<div ref="headerEl" class="header">
+			<button v-if="withOkButton" class="_button" @click="$emit('close')"><i class="fas fa-times"></i></button>
+			<span class="title">
+				<slot name="header"></slot>
+			</span>
+			<button v-if="!withOkButton" class="_button" @click="$emit('close')"><i class="fas fa-times"></i></button>
+			<button v-if="withOkButton" class="_button" :disabled="okButtonDisabled" @click="$emit('ok')"><i class="fas fa-check"></i></button>
+		</div>
+		<div class="body">
+			<slot :width="bodyWidth" :height="bodyHeight"></slot>
+		</div>
+	</div>
+</MkModal>
+</template>
+
+<script lang="ts" setup>
+import { onMounted, onUnmounted } from 'vue';
+import MkModal from './MkModal.vue';
+
+const props = withDefaults(defineProps<{
+	withOkButton: boolean;
+	okButtonDisabled: boolean;
+	width: number;
+	height: number | null;
+	scroll: boolean;
+}>(), {
+	withOkButton: false,
+	okButtonDisabled: false,
+	width: 400,
+	height: null,
+	scroll: true,
+});
+
+const emit = defineEmits<{
+	(event: 'click'): void;
+	(event: 'close'): void;
+	(event: 'closed'): void;
+	(event: 'ok'): void;
+}>();
+
+let modal = $ref<InstanceType<typeof MkModal>>();
+let rootEl = $ref<HTMLElement>();
+let headerEl = $ref<HTMLElement>();
+let bodyWidth = $ref(0);
+let bodyHeight = $ref(0);
+
+const close = () => {
+	modal.close();
+};
+
+const onBgClick = () => {
+	emit('click');
+};
+
+const onKeydown = (evt) => {
+	if (evt.which === 27) { // Esc
+		evt.preventDefault();
+		evt.stopPropagation();
+		close();
+	}
+};
+
+const ro = new ResizeObserver((entries, observer) => {
+	bodyWidth = rootEl.offsetWidth;
+	bodyHeight = rootEl.offsetHeight - headerEl.offsetHeight;
+});
+
+onMounted(() => {
+	bodyWidth = rootEl.offsetWidth;
+	bodyHeight = rootEl.offsetHeight - headerEl.offsetHeight;
+	ro.observe(rootEl);
+});
+
+onUnmounted(() => {
+	ro.disconnect();
+});
+
+defineExpose({
+	close,
+});
+</script>
+
+<style lang="scss" scoped>
+.ebkgoccj {
+	overflow: hidden;
+	display: flex;
+	flex-direction: column;
+	contain: content;
+	border-radius: var(--radius);
+
+	--root-margin: 24px;
+
+	@media (max-width: 500px) {
+		--root-margin: 16px;
+	}
+
+	> .header {
+		$height: 46px;
+		$height-narrow: 42px;
+		display: flex;
+		flex-shrink: 0;
+		background: var(--windowHeader);
+		-webkit-backdrop-filter: var(--blur, blur(15px));
+		backdrop-filter: var(--blur, blur(15px));
+
+		> button {
+			height: $height;
+			width: $height;
+
+			@media (max-width: 500px) {
+				height: $height-narrow;
+				width: $height-narrow;
+			}
+		}
+
+		> .title {
+			flex: 1;
+			line-height: $height;
+			padding-left: 32px;
+			font-weight: bold;
+			white-space: nowrap;
+			overflow: hidden;
+			text-overflow: ellipsis;
+			pointer-events: none;
+
+			@media (max-width: 500px) {
+				line-height: $height-narrow;
+				padding-left: 16px;
+			}
+		}
+
+		> button + .title {
+			padding-left: 0;
+		}
+	}
+
+	> .body {
+		flex: 1;
+		overflow: auto;
+		background: var(--panel);
+	}
+}
+</style>
diff --git a/packages/client/src/components/MkNotes.vue b/packages/client/src/components/MkNotes.vue
index 67e894a09c..5abcdc2298 100644
--- a/packages/client/src/components/MkNotes.vue
+++ b/packages/client/src/components/MkNotes.vue
@@ -21,7 +21,7 @@
 import { ref } from 'vue';
 import XNote from '@/components/MkNote.vue';
 import XList from '@/components/MkDateSeparatedList.vue';
-import MkPagination, { Paging } from '@/components/ui/pagination.vue';
+import MkPagination, { Paging } from '@/components/MkPagination.vue';
 import { i18n } from '@/i18n';
 
 const props = defineProps<{
diff --git a/packages/client/src/components/MkNotificationSettingWindow.vue b/packages/client/src/components/MkNotificationSettingWindow.vue
index bf0a148f59..75bea2976c 100644
--- a/packages/client/src/components/MkNotificationSettingWindow.vue
+++ b/packages/client/src/components/MkNotificationSettingWindow.vue
@@ -31,9 +31,9 @@
 import { } from 'vue';
 import { notificationTypes } from 'misskey-js';
 import MkSwitch from './form/switch.vue';
-import MkInfo from './ui/info.vue';
-import MkButton from './ui/button.vue';
-import XModalWindow from '@/components/ui/modal-window.vue';
+import MkInfo from './MkInfo.vue';
+import MkButton from './MkButton.vue';
+import XModalWindow from '@/components/MkModalWindow.vue';
 import { i18n } from '@/i18n';
 
 const emit = defineEmits<{
diff --git a/packages/client/src/components/MkNotifications.vue b/packages/client/src/components/MkNotifications.vue
index 1ee091a229..0e1cc06743 100644
--- a/packages/client/src/components/MkNotifications.vue
+++ b/packages/client/src/components/MkNotifications.vue
@@ -19,7 +19,7 @@
 <script lang="ts" setup>
 import { defineComponent, markRaw, onUnmounted, onMounted, computed, ref } from 'vue';
 import { notificationTypes } from 'misskey-js';
-import MkPagination, { Paging } from '@/components/ui/pagination.vue';
+import MkPagination, { Paging } from '@/components/MkPagination.vue';
 import XNotification from '@/components/MkNotification.vue';
 import XList from '@/components/MkDateSeparatedList.vue';
 import XNote from '@/components/MkNote.vue';
diff --git a/packages/client/src/components/MkPageWindow.vue b/packages/client/src/components/MkPageWindow.vue
index 76eb77825f..d58b914036 100644
--- a/packages/client/src/components/MkPageWindow.vue
+++ b/packages/client/src/components/MkPageWindow.vue
@@ -26,7 +26,7 @@
 <script lang="ts" setup>
 import { ComputedRef, inject, provide } from 'vue';
 import RouterView from '@/components/global/RouterView.vue';
-import XWindow from '@/components/ui/window.vue';
+import XWindow from '@/components/MkWindow.vue';
 import { popout as _popout } from '@/scripts/popout';
 import copyToClipboard from '@/scripts/copy-to-clipboard';
 import { url } from '@/config';
diff --git a/packages/client/src/components/MkPagination.vue b/packages/client/src/components/MkPagination.vue
new file mode 100644
index 0000000000..291409171a
--- /dev/null
+++ b/packages/client/src/components/MkPagination.vue
@@ -0,0 +1,317 @@
+<template>
+<transition :name="$store.state.animation ? 'fade' : ''" mode="out-in">
+	<MkLoading v-if="fetching"/>
+
+	<MkError v-else-if="error" @retry="init()"/>
+
+	<div v-else-if="empty" key="_empty_" class="empty">
+		<slot name="empty">
+			<div class="_fullinfo">
+				<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
+				<div>{{ i18n.ts.nothing }}</div>
+			</div>
+		</slot>
+	</div>
+
+	<div v-else ref="rootEl">
+		<div v-show="pagination.reversed && more" key="_more_" class="cxiknjgy _gap">
+			<MkButton v-if="!moreFetching" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMoreAhead">
+				{{ i18n.ts.loadMore }}
+			</MkButton>
+			<MkLoading v-else class="loading"/>
+		</div>
+		<slot :items="items"></slot>
+		<div v-show="!pagination.reversed && more" key="_more_" class="cxiknjgy _gap">
+			<MkButton v-if="!moreFetching" v-appear="($store.state.enableInfiniteScroll && !disableAutoLoad) ? fetchMore : null" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMore">
+				{{ i18n.ts.loadMore }}
+			</MkButton>
+			<MkLoading v-else class="loading"/>
+		</div>
+	</div>
+</transition>
+</template>
+
+<script lang="ts" setup>
+import { computed, ComputedRef, isRef, markRaw, onActivated, onDeactivated, Ref, ref, watch } from 'vue';
+import * as misskey from 'misskey-js';
+import * as os from '@/os';
+import { onScrollTop, isTopVisible, getScrollPosition, getScrollContainer } from '@/scripts/scroll';
+import MkButton from '@/components/MkButton.vue';
+import { i18n } from '@/i18n';
+
+const SECOND_FETCH_LIMIT = 30;
+
+export type Paging<E extends keyof misskey.Endpoints = keyof misskey.Endpoints> = {
+	endpoint: E;
+	limit: number;
+	params?: misskey.Endpoints[E]['req'] | ComputedRef<misskey.Endpoints[E]['req']>;
+
+	/**
+	 * 検索APIのような、ページング不可なエンドポイントを利用する場合
+	 * (そのようなAPIをこの関数で使うのは若干矛盾してるけど)
+	 */
+	noPaging?: boolean;
+
+	/**
+	 * items 配列の中身を逆順にする(新しい方が最後)
+	 */
+	reversed?: boolean;
+
+	offsetMode?: boolean;
+};
+
+const props = withDefaults(defineProps<{
+	pagination: Paging;
+	disableAutoLoad?: boolean;
+	displayLimit?: number;
+}>(), {
+	displayLimit: 30,
+});
+
+const emit = defineEmits<{
+	(ev: 'queue', count: number): void;
+}>();
+
+type Item = { id: string; [another: string]: unknown; };
+
+const rootEl = ref<HTMLElement>();
+const items = ref<Item[]>([]);
+const queue = ref<Item[]>([]);
+const offset = ref(0);
+const fetching = ref(true);
+const moreFetching = ref(false);
+const more = ref(false);
+const backed = ref(false); // 遡り中か否か
+const isBackTop = ref(false);
+const empty = computed(() => items.value.length === 0);
+const error = ref(false);
+
+const init = async (): Promise<void> => {
+	queue.value = [];
+	fetching.value = true;
+	const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {};
+	await os.api(props.pagination.endpoint, {
+		...params,
+		limit: props.pagination.noPaging ? (props.pagination.limit || 10) : (props.pagination.limit || 10) + 1,
+	}).then(res => {
+		for (let i = 0; i < res.length; i++) {
+			const item = res[i];
+			if (props.pagination.reversed) {
+				if (i === res.length - 2) item._shouldInsertAd_ = true;
+			} else {
+				if (i === 3) item._shouldInsertAd_ = true;
+			}
+		}
+		if (!props.pagination.noPaging && (res.length > (props.pagination.limit || 10))) {
+			res.pop();
+			items.value = props.pagination.reversed ? [...res].reverse() : res;
+			more.value = true;
+		} else {
+			items.value = props.pagination.reversed ? [...res].reverse() : res;
+			more.value = false;
+		}
+		offset.value = res.length;
+		error.value = false;
+		fetching.value = false;
+	}, err => {
+		error.value = true;
+		fetching.value = false;
+	});
+};
+
+const reload = (): void => {
+	items.value = [];
+	init();
+};
+
+const fetchMore = async (): Promise<void> => {
+	if (!more.value || fetching.value || moreFetching.value || items.value.length === 0) return;
+	moreFetching.value = true;
+	backed.value = true;
+	const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {};
+	await os.api(props.pagination.endpoint, {
+		...params,
+		limit: SECOND_FETCH_LIMIT + 1,
+		...(props.pagination.offsetMode ? {
+			offset: offset.value,
+		} : props.pagination.reversed ? {
+			sinceId: items.value[0].id,
+		} : {
+			untilId: items.value[items.value.length - 1].id,
+		}),
+	}).then(res => {
+		for (let i = 0; i < res.length; i++) {
+			const item = res[i];
+			if (props.pagination.reversed) {
+				if (i === res.length - 9) item._shouldInsertAd_ = true;
+			} else {
+				if (i === 10) item._shouldInsertAd_ = true;
+			}
+		}
+		if (res.length > SECOND_FETCH_LIMIT) {
+			res.pop();
+			items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res);
+			more.value = true;
+		} else {
+			items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res);
+			more.value = false;
+		}
+		offset.value += res.length;
+		moreFetching.value = false;
+	}, err => {
+		moreFetching.value = false;
+	});
+};
+
+const fetchMoreAhead = async (): Promise<void> => {
+	if (!more.value || fetching.value || moreFetching.value || items.value.length === 0) return;
+	moreFetching.value = true;
+	const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {};
+	await os.api(props.pagination.endpoint, {
+		...params,
+		limit: SECOND_FETCH_LIMIT + 1,
+		...(props.pagination.offsetMode ? {
+			offset: offset.value,
+		} : props.pagination.reversed ? {
+			untilId: items.value[0].id,
+		} : {
+			sinceId: items.value[items.value.length - 1].id,
+		}),
+	}).then(res => {
+		if (res.length > SECOND_FETCH_LIMIT) {
+			res.pop();
+			items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res);
+			more.value = true;
+		} else {
+			items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res);
+			more.value = false;
+		}
+		offset.value += res.length;
+		moreFetching.value = false;
+	}, err => {
+		moreFetching.value = false;
+	});
+};
+
+const prepend = (item: Item): void => {
+	if (props.pagination.reversed) {
+		if (rootEl.value) {
+			const container = getScrollContainer(rootEl.value);
+			if (container == null) {
+				// TODO?
+			} else {
+				const pos = getScrollPosition(rootEl.value);
+				const viewHeight = container.clientHeight;
+				const height = container.scrollHeight;
+				const isBottom = (pos + viewHeight > height - 32);
+				if (isBottom) {
+					// オーバーフローしたら古いアイテムは捨てる
+					if (items.value.length >= props.displayLimit) {
+						// このやり方だとVue 3.2以降アニメーションが動かなくなる
+						//items.value = items.value.slice(-props.displayLimit);
+						while (items.value.length >= props.displayLimit) {
+							items.value.shift();
+						}
+						more.value = true;
+					}
+				}
+			}
+		}
+		items.value.push(item);
+		// TODO
+	} else {
+		// 初回表示時はunshiftだけでOK
+		if (!rootEl.value) {
+			items.value.unshift(item);
+			return;
+		}
+
+		const isTop = isBackTop.value || (document.body.contains(rootEl.value) && isTopVisible(rootEl.value));
+
+		if (isTop) {
+			// Prepend the item
+			items.value.unshift(item);
+
+			// オーバーフローしたら古いアイテムは捨てる
+			if (items.value.length >= props.displayLimit) {
+				// このやり方だとVue 3.2以降アニメーションが動かなくなる
+				//this.items = items.value.slice(0, props.displayLimit);
+				while (items.value.length >= props.displayLimit) {
+					items.value.pop();
+				}
+				more.value = true;
+			}
+		} else {
+			queue.value.push(item);
+			onScrollTop(rootEl.value, () => {
+				for (const item of queue.value) {
+					prepend(item);
+				}
+				queue.value = [];
+			});
+		}
+	}
+};
+
+const append = (item: Item): void => {
+	items.value.push(item);
+};
+
+const removeItem = (finder: (item: Item) => boolean) => {
+	const i = items.value.findIndex(finder);
+	items.value.splice(i, 1);
+};
+
+const updateItem = (id: Item['id'], replacer: (old: Item) => Item): void => {
+	const i = items.value.findIndex(item => item.id === id);
+	items.value[i] = replacer(items.value[i]);
+};
+
+if (props.pagination.params && isRef(props.pagination.params)) {
+	watch(props.pagination.params, init, { deep: true });
+}
+
+watch(queue, (a, b) => {
+	if (a.length === 0 && b.length === 0) return;
+	emit('queue', queue.value.length);
+}, { deep: true });
+
+init();
+
+onActivated(() => {
+	isBackTop.value = false;
+});
+
+onDeactivated(() => {
+	isBackTop.value = window.scrollY === 0;
+});
+
+defineExpose({
+	items,
+	queue,
+	backed,
+	reload,
+	prepend,
+	append,
+	removeItem,
+	updateItem,
+});
+</script>
+
+<style lang="scss" scoped>
+.fade-enter-active,
+.fade-leave-active {
+	transition: opacity 0.125s ease;
+}
+.fade-enter-from,
+.fade-leave-to {
+	opacity: 0;
+}
+
+.cxiknjgy {
+	> .button {
+		margin-left: auto;
+		margin-right: auto;
+	}
+}
+</style>
diff --git a/packages/client/src/components/MkPollEditor.vue b/packages/client/src/components/MkPollEditor.vue
index 6c1a4cc89f..3b08a63535 100644
--- a/packages/client/src/components/MkPollEditor.vue
+++ b/packages/client/src/components/MkPollEditor.vue
@@ -52,7 +52,7 @@ import { ref, watch } from 'vue';
 import MkInput from './form/input.vue';
 import MkSelect from './form/select.vue';
 import MkSwitch from './form/switch.vue';
-import MkButton from './ui/button.vue';
+import MkButton from './MkButton.vue';
 import { formatDateTimeString } from '@/scripts/format-time-string';
 import { addTime } from '@/scripts/time';
 import { i18n } from '@/i18n';
diff --git a/packages/client/src/components/MkPopupMenu.vue b/packages/client/src/components/MkPopupMenu.vue
new file mode 100644
index 0000000000..f04c7f5618
--- /dev/null
+++ b/packages/client/src/components/MkPopupMenu.vue
@@ -0,0 +1,36 @@
+<template>
+<MkModal ref="modal" v-slot="{ type, maxHeight }" :z-priority="'high'" :src="src" :transparent-bg="true" @click="modal.close()" @closed="emit('closed')">
+	<MkMenu :items="items" :align="align" :width="width" :max-height="maxHeight" :as-drawer="type === 'drawer'" class="sfhdhdhq" :class="{ drawer: type === 'drawer' }" @close="modal.close()"/>
+</MkModal>
+</template>
+
+<script lang="ts" setup>
+import { } from 'vue';
+import MkModal from './MkModal.vue';
+import MkMenu from './MkMenu.vue';
+import { MenuItem } from '@/types/menu';
+
+defineProps<{
+	items: MenuItem[];
+	align?: 'center' | string;
+	width?: number;
+	viaKeyboard?: boolean;
+	src?: any;
+}>();
+
+const emit = defineEmits<{
+	(ev: 'closed'): void;
+}>();
+
+let modal = $ref<InstanceType<typeof MkModal>>();
+</script>
+
+<style lang="scss" scoped>
+.sfhdhdhq {
+	&.drawer {
+		border-radius: 24px;
+		border-bottom-right-radius: 0;
+		border-bottom-left-radius: 0;
+	}
+}
+</style>
diff --git a/packages/client/src/components/MkPostForm.vue b/packages/client/src/components/MkPostForm.vue
index a499a62dde..0c57a5a57a 100644
--- a/packages/client/src/components/MkPostForm.vue
+++ b/packages/client/src/components/MkPostForm.vue
@@ -84,7 +84,7 @@ import * as os from '@/os';
 import { stream } from '@/stream';
 import { selectFiles } from '@/scripts/select-file';
 import { defaultStore, notePostInterruptors, postFormActions } from '@/store';
-import MkInfo from '@/components/ui/info.vue';
+import MkInfo from '@/components/MkInfo.vue';
 import { i18n } from '@/i18n';
 import { instance } from '@/instance';
 import { $i, getAccounts, openAccountMenu as openAccountMenu_ } from '@/account';
diff --git a/packages/client/src/components/MkPostFormDialog.vue b/packages/client/src/components/MkPostFormDialog.vue
index 90b281d2ce..6dabb1db14 100644
--- a/packages/client/src/components/MkPostFormDialog.vue
+++ b/packages/client/src/components/MkPostFormDialog.vue
@@ -6,7 +6,7 @@
 
 <script lang="ts">
 import { defineComponent } from 'vue';
-import MkModal from '@/components/ui/modal.vue';
+import MkModal from '@/components/MkModal.vue';
 import MkPostForm from '@/components/MkPostForm.vue';
 
 export default defineComponent({
diff --git a/packages/client/src/components/MkReactionTooltip.vue b/packages/client/src/components/MkReactionTooltip.vue
index 44f2681700..2da97cf4f3 100644
--- a/packages/client/src/components/MkReactionTooltip.vue
+++ b/packages/client/src/components/MkReactionTooltip.vue
@@ -9,7 +9,7 @@
 
 <script lang="ts" setup>
 import { } from 'vue';
-import MkTooltip from './ui/tooltip.vue';
+import MkTooltip from './MkTooltip.vue';
 import XReactionIcon from '@/components/MkReactionIcon.vue';
 
 const props = defineProps<{
diff --git a/packages/client/src/components/MkReactionsViewer.details.vue b/packages/client/src/components/MkReactionsViewer.details.vue
index e886ff5023..8c423807ba 100644
--- a/packages/client/src/components/MkReactionsViewer.details.vue
+++ b/packages/client/src/components/MkReactionsViewer.details.vue
@@ -18,7 +18,7 @@
 
 <script lang="ts" setup>
 import { } from 'vue';
-import MkTooltip from './ui/tooltip.vue';
+import MkTooltip from './MkTooltip.vue';
 import XReactionIcon from '@/components/MkReactionIcon.vue';
 
 const props = defineProps<{
diff --git a/packages/client/src/components/MkSample.vue b/packages/client/src/components/MkSample.vue
index f80b9c96b7..1169654d09 100644
--- a/packages/client/src/components/MkSample.vue
+++ b/packages/client/src/components/MkSample.vue
@@ -29,7 +29,7 @@
 
 <script lang="ts">
 import { defineComponent } from 'vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/form/input.vue';
 import MkSwitch from '@/components/form/switch.vue';
 import MkTextarea from '@/components/form/textarea.vue';
diff --git a/packages/client/src/components/MkSignin.vue b/packages/client/src/components/MkSignin.vue
index e841631717..5613e5cf02 100644
--- a/packages/client/src/components/MkSignin.vue
+++ b/packages/client/src/components/MkSignin.vue
@@ -51,9 +51,9 @@
 <script lang="ts" setup>
 import { defineAsyncComponent } from 'vue';
 import { toUnicode } from 'punycode/';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/form/input.vue';
-import MkInfo from '@/components/ui/info.vue';
+import MkInfo from '@/components/MkInfo.vue';
 import { apiUrl, host as configHost } from '@/config';
 import { byteify, hexify } from '@/scripts/2fa';
 import * as os from '@/os';
diff --git a/packages/client/src/components/MkSigninDialog.vue b/packages/client/src/components/MkSigninDialog.vue
index 19b82b36d5..fd27244516 100644
--- a/packages/client/src/components/MkSigninDialog.vue
+++ b/packages/client/src/components/MkSigninDialog.vue
@@ -15,7 +15,7 @@
 <script lang="ts" setup>
 import { } from 'vue';
 import MkSignin from '@/components/MkSignin.vue';
-import XModalWindow from '@/components/ui/modal-window.vue';
+import XModalWindow from '@/components/MkModalWindow.vue';
 import { i18n } from '@/i18n';
 
 const props = withDefaults(defineProps<{
diff --git a/packages/client/src/components/MkSignup.vue b/packages/client/src/components/MkSignup.vue
index dbd332ce6e..a324bb6f5a 100644
--- a/packages/client/src/components/MkSignup.vue
+++ b/packages/client/src/components/MkSignup.vue
@@ -67,7 +67,7 @@
 import { } from 'vue';
 import getPasswordStrength from 'syuilo-password-strength';
 import { toUnicode } from 'punycode/';
-import MkButton from './ui/button.vue';
+import MkButton from './MkButton.vue';
 import MkInput from './form/input.vue';
 import MkSwitch from './form/switch.vue';
 import MkCaptcha from '@/components/MkCaptcha.vue';
diff --git a/packages/client/src/components/MkSignupDialog.vue b/packages/client/src/components/MkSignupDialog.vue
index 3e989fcb71..d6d4553bc6 100644
--- a/packages/client/src/components/MkSignupDialog.vue
+++ b/packages/client/src/components/MkSignupDialog.vue
@@ -19,7 +19,7 @@
 <script lang="ts" setup>
 import { } from 'vue';
 import XSignup from '@/components/MkSignup.vue';
-import XModalWindow from '@/components/ui/modal-window.vue';
+import XModalWindow from '@/components/MkModalWindow.vue';
 import { i18n } from '@/i18n';
 
 const props = withDefaults(defineProps<{
diff --git a/packages/client/src/components/MkSuperMenu.vue b/packages/client/src/components/MkSuperMenu.vue
new file mode 100644
index 0000000000..8ce2dc5dc2
--- /dev/null
+++ b/packages/client/src/components/MkSuperMenu.vue
@@ -0,0 +1,148 @@
+<template>
+<div class="rrevdjwu" :class="{ grid }">
+	<div v-for="group in def" class="group">
+		<div v-if="group.title" class="title">{{ group.title }}</div>
+
+		<div class="items">
+			<template v-for="(item, i) in group.items">
+				<a v-if="item.type === 'a'" :href="item.href" :target="item.target" :tabindex="i" class="_button item" :class="{ danger: item.danger, active: item.active }">
+					<i v-if="item.icon" class="icon fa-fw" :class="item.icon"></i>
+					<span class="text">{{ item.text }}</span>
+				</a>
+				<button v-else-if="item.type === 'button'" :tabindex="i" class="_button item" :class="{ danger: item.danger, active: item.active }" :disabled="item.active" @click="ev => item.action(ev)">
+					<i v-if="item.icon" class="icon fa-fw" :class="item.icon"></i>
+					<span class="text">{{ item.text }}</span>
+				</button>
+				<MkA v-else :to="item.to" :tabindex="i" class="_button item" :class="{ danger: item.danger, active: item.active }">
+					<i v-if="item.icon" class="icon fa-fw" :class="item.icon"></i>
+					<span class="text">{{ item.text }}</span>
+				</MkA>
+			</template>
+		</div>
+	</div>
+</div>
+</template>
+
+<script lang="ts">
+import { defineComponent, ref, unref } from 'vue';
+
+export default defineComponent({
+	props: {
+		def: {
+			type: Array,
+			required: true,
+		},
+		grid: {
+			type: Boolean,
+			required: false,
+			default: false,
+		},
+	},
+});
+</script>
+
+<style lang="scss" scoped>
+.rrevdjwu {
+	> .group {
+		& + .group {
+			margin-top: 16px;
+			padding-top: 16px;
+			border-top: solid 0.5px var(--divider);
+		}
+
+		> .title {
+			opacity: 0.7;
+			margin: 0 0 8px 0;
+			font-size: 0.9em;
+		}
+	
+		> .items {
+			> .item {
+				display: flex;
+				align-items: center;
+				width: 100%;
+				box-sizing: border-box;
+				padding: 10px 16px 10px 8px;
+				border-radius: 9px;
+				font-size: 0.9em;
+
+				&:hover {
+					text-decoration: none;
+					background: var(--panelHighlight);
+				}
+
+				&.active {
+					color: var(--accent);
+					background: var(--accentedBg);
+				}
+
+				&.danger {
+					color: var(--error);
+				}
+
+				> .icon {
+					width: 32px;
+					margin-right: 2px;
+					flex-shrink: 0;
+					text-align: center;
+					opacity: 0.8;
+				}
+
+				> .text {
+					white-space: nowrap;
+					text-overflow: ellipsis;
+					overflow: hidden;
+					padding-right: 12px;
+				}
+
+			}
+		}
+	}
+
+	&.grid {
+		> .group {
+			& + .group {
+				padding-top: 0;
+				border-top: none;
+			}
+
+			margin-left: 0;
+			margin-right: 0;
+
+			> .title {
+				font-size: 1em;
+				opacity: 0.7;
+				margin: 0 0 8px 16px;
+			}
+
+			> .items {
+				display: grid;
+				grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
+				grid-gap: 8px;
+				padding: 0 16px;
+
+				> .item {
+					flex-direction: column;
+					padding: 18px 16px 16px 16px;
+					background: var(--panel);
+					border-radius: 8px;
+					text-align: center;
+
+					> .icon {
+						display: block;
+						margin-right: 0;
+						margin-bottom: 12px;
+						font-size: 1.5em;
+					}
+
+					> .text {
+						padding-right: 0;
+						width: 100%;
+						font-size: 0.8em;
+					}
+				}
+			}
+		}
+	}
+}
+</style>
diff --git a/packages/client/src/components/MkTokenGenerateWindow.vue b/packages/client/src/components/MkTokenGenerateWindow.vue
index 789218a8cd..b846034a24 100644
--- a/packages/client/src/components/MkTokenGenerateWindow.vue
+++ b/packages/client/src/components/MkTokenGenerateWindow.vue
@@ -33,9 +33,9 @@ import { } from 'vue';
 import { permissions as kinds } from 'misskey-js';
 import MkInput from './form/input.vue';
 import MkSwitch from './form/switch.vue';
-import MkButton from './ui/button.vue';
-import MkInfo from './ui/info.vue';
-import XModalWindow from '@/components/ui/modal-window.vue';
+import MkButton from './MkButton.vue';
+import MkInfo from './MkInfo.vue';
+import XModalWindow from '@/components/MkModalWindow.vue';
 
 const props = withDefaults(defineProps<{
 	title?: string | null;
diff --git a/packages/client/src/components/MkTooltip.vue b/packages/client/src/components/MkTooltip.vue
new file mode 100644
index 0000000000..4c6258d245
--- /dev/null
+++ b/packages/client/src/components/MkTooltip.vue
@@ -0,0 +1,101 @@
+<template>
+<transition :name="$store.state.animation ? 'tooltip' : ''" appear @after-leave="emit('closed')">
+	<div v-show="showing" ref="el" class="buebdbiu _acrylic _shadow" :style="{ zIndex, maxWidth: maxWidth + 'px' }">
+		<slot>
+			<Mfm v-if="asMfm" :text="text"/>
+			<span v-else>{{ text }}</span>
+		</slot>
+	</div>
+</transition>
+</template>
+
+<script lang="ts" setup>
+import { nextTick, onMounted, onUnmounted, ref } from 'vue';
+import * as os from '@/os';
+import { calcPopupPosition } from '@/scripts/popup-position';
+
+const props = withDefaults(defineProps<{
+	showing: boolean;
+	targetElement?: HTMLElement;
+	x?: number;
+	y?: number;
+	text?: string;
+	asMfm?: boolean;
+	maxWidth?: number;
+	direction?: 'top' | 'bottom' | 'right' | 'left';
+	innerMargin?: number;
+}>(), {
+	maxWidth: 250,
+	direction: 'top',
+	innerMargin: 0,
+});
+
+const emit = defineEmits<{
+	(ev: 'closed'): void;
+}>();
+
+const el = ref<HTMLElement>();
+const zIndex = os.claimZIndex('high');
+
+function setPosition() {
+	const data = calcPopupPosition(el.value, {
+		anchorElement: props.targetElement,
+		direction: props.direction,
+		align: 'center',
+		innerMargin: props.innerMargin,
+		x: props.x,
+		y: props.y,
+	});
+
+	el.value.style.transformOrigin = data.transformOrigin;
+	el.value.style.left = data.left + 'px';
+	el.value.style.top = data.top + 'px';
+}
+
+let loopHandler;
+
+onMounted(() => {
+	nextTick(() => {
+		setPosition();
+
+		const loop = () => {
+			loopHandler = window.requestAnimationFrame(() => {
+				setPosition();
+				loop();
+			});
+		};
+
+		loop();
+	});
+});
+
+onUnmounted(() => {
+	window.cancelAnimationFrame(loopHandler);
+});
+</script>
+
+<style lang="scss" scoped>
+.tooltip-enter-active,
+.tooltip-leave-active {
+	opacity: 1;
+	transform: scale(1);
+	transition: transform 200ms cubic-bezier(0.23, 1, 0.32, 1), opacity 200ms cubic-bezier(0.23, 1, 0.32, 1);
+}
+.tooltip-enter-from,
+.tooltip-leave-active {
+	opacity: 0;
+	transform: scale(0.75);
+}
+
+.buebdbiu {
+	position: absolute;
+	font-size: 0.8em;
+	padding: 8px 12px;
+	box-sizing: border-box;
+	text-align: center;
+	border-radius: 4px;
+	border: solid 0.5px var(--divider);
+	pointer-events: none;
+	transform-origin: center center;
+}
+</style>
diff --git a/packages/client/src/components/MkUpdated.vue b/packages/client/src/components/MkUpdated.vue
index f3ce2731b9..48aeb30224 100644
--- a/packages/client/src/components/MkUpdated.vue
+++ b/packages/client/src/components/MkUpdated.vue
@@ -11,8 +11,8 @@
 
 <script lang="ts" setup>
 import { ref } from 'vue';
-import MkModal from '@/components/ui/modal.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkModal from '@/components/MkModal.vue';
+import MkButton from '@/components/MkButton.vue';
 import MkSparkle from '@/components/MkSparkle.vue';
 import { version } from '@/config';
 import { i18n } from '@/i18n';
diff --git a/packages/client/src/components/MkUserList.vue b/packages/client/src/components/MkUserList.vue
index d8e28e17db..e1f47c7673 100644
--- a/packages/client/src/components/MkUserList.vue
+++ b/packages/client/src/components/MkUserList.vue
@@ -18,7 +18,7 @@
 <script lang="ts" setup>
 import { ref } from 'vue';
 import MkUserInfo from '@/components/MkUserInfo.vue';
-import MkPagination, { Paging } from '@/components/ui/pagination.vue';
+import MkPagination, { Paging } from '@/components/MkPagination.vue';
 import { userPage } from '@/filters/user';
 import { i18n } from '@/i18n';
 
diff --git a/packages/client/src/components/MkUserSelectDialog.vue b/packages/client/src/components/MkUserSelectDialog.vue
index 4d8e427a72..07caedfe3a 100644
--- a/packages/client/src/components/MkUserSelectDialog.vue
+++ b/packages/client/src/components/MkUserSelectDialog.vue
@@ -56,7 +56,7 @@ import { nextTick, onMounted } from 'vue';
 import * as misskey from 'misskey-js';
 import MkInput from '@/components/form/input.vue';
 import FormSplit from '@/components/form/split.vue';
-import XModalWindow from '@/components/ui/modal-window.vue';
+import XModalWindow from '@/components/MkModalWindow.vue';
 import * as os from '@/os';
 import { defaultStore } from '@/store';
 import { i18n } from '@/i18n';
diff --git a/packages/client/src/components/MkUsersTooltip.vue b/packages/client/src/components/MkUsersTooltip.vue
index 2df19bcd3f..4ccc44b47e 100644
--- a/packages/client/src/components/MkUsersTooltip.vue
+++ b/packages/client/src/components/MkUsersTooltip.vue
@@ -12,7 +12,7 @@
 
 <script lang="ts" setup>
 import { } from 'vue';
-import MkTooltip from './ui/tooltip.vue';
+import MkTooltip from './MkTooltip.vue';
 
 const props = defineProps<{
 	users: any[]; // TODO
diff --git a/packages/client/src/components/MkVisibilityPicker.vue b/packages/client/src/components/MkVisibilityPicker.vue
index f4830cd2c6..ecc022eca0 100644
--- a/packages/client/src/components/MkVisibilityPicker.vue
+++ b/packages/client/src/components/MkVisibilityPicker.vue
@@ -45,7 +45,7 @@
 <script lang="ts" setup>
 import { nextTick, watch } from 'vue';
 import * as misskey from 'misskey-js';
-import MkModal from '@/components/ui/modal.vue';
+import MkModal from '@/components/MkModal.vue';
 import { i18n } from '@/i18n';
 
 const modal = $ref<InstanceType<typeof MkModal>>();
diff --git a/packages/client/src/components/MkWaitingDialog.vue b/packages/client/src/components/MkWaitingDialog.vue
index 9e631b55b1..77b664d3b1 100644
--- a/packages/client/src/components/MkWaitingDialog.vue
+++ b/packages/client/src/components/MkWaitingDialog.vue
@@ -10,7 +10,7 @@
 
 <script lang="ts" setup>
 import { watch, ref } from 'vue';
-import MkModal from '@/components/ui/modal.vue';
+import MkModal from '@/components/MkModal.vue';
 
 const modal = ref<InstanceType<typeof MkModal>>();
 
diff --git a/packages/client/src/components/MkWidgets.vue b/packages/client/src/components/MkWidgets.vue
index 54d4c57af3..fcb0d11af4 100644
--- a/packages/client/src/components/MkWidgets.vue
+++ b/packages/client/src/components/MkWidgets.vue
@@ -34,7 +34,7 @@
 import { defineAsyncComponent, reactive, ref, computed } from 'vue';
 import { v4 as uuid } from 'uuid';
 import MkSelect from '@/components/form/select.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import { widgets as widgetDefs } from '@/widgets';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
diff --git a/packages/client/src/components/MkWindow.vue b/packages/client/src/components/MkWindow.vue
new file mode 100644
index 0000000000..758d4d47b6
--- /dev/null
+++ b/packages/client/src/components/MkWindow.vue
@@ -0,0 +1,563 @@
+<template>
+<transition :name="$store.state.animation ? 'window' : ''" appear @after-leave="$emit('closed')">
+	<div v-if="showing" ref="rootEl" class="ebkgocck" :class="{ maximized }">
+		<div class="body _shadow _narrow_" @mousedown="onBodyMousedown" @keydown="onKeydown">
+			<div class="header" :class="{ mini }" @contextmenu.prevent.stop="onContextmenu">
+				<span class="left">
+					<button v-for="button in buttonsLeft" v-tooltip="button.title" class="button _button" :class="{ highlighted: button.highlighted }" @click="button.onClick"><i :class="button.icon"></i></button>
+				</span>
+				<span class="title" @mousedown.prevent="onHeaderMousedown" @touchstart.prevent="onHeaderMousedown">
+					<slot name="header"></slot>
+				</span>
+				<span class="right">
+					<button v-for="button in buttonsRight" v-tooltip="button.title" class="button _button" :class="{ highlighted: button.highlighted }" @click="button.onClick"><i :class="button.icon"></i></button>
+					<button v-if="canResize && maximized" class="button _button" @click="unMaximize()"><i class="fas fa-window-restore"></i></button>
+					<button v-else-if="canResize && !maximized" class="button _button" @click="maximize()"><i class="fas fa-window-maximize"></i></button>
+					<button v-if="closeButton" class="button _button" @click="close()"><i class="fas fa-times"></i></button>
+				</span>
+			</div>
+			<div class="body">
+				<slot></slot>
+			</div>
+		</div>
+		<template v-if="canResize">
+			<div class="handle top" @mousedown.prevent="onTopHandleMousedown"></div>
+			<div class="handle right" @mousedown.prevent="onRightHandleMousedown"></div>
+			<div class="handle bottom" @mousedown.prevent="onBottomHandleMousedown"></div>
+			<div class="handle left" @mousedown.prevent="onLeftHandleMousedown"></div>
+			<div class="handle top-left" @mousedown.prevent="onTopLeftHandleMousedown"></div>
+			<div class="handle top-right" @mousedown.prevent="onTopRightHandleMousedown"></div>
+			<div class="handle bottom-right" @mousedown.prevent="onBottomRightHandleMousedown"></div>
+			<div class="handle bottom-left" @mousedown.prevent="onBottomLeftHandleMousedown"></div>
+		</template>
+	</div>
+</transition>
+</template>
+
+<script lang="ts" setup>
+import { onBeforeUnmount, onMounted, provide } from 'vue';
+import contains from '@/scripts/contains';
+import * as os from '@/os';
+import { MenuItem } from '@/types/menu';
+
+const minHeight = 50;
+const minWidth = 250;
+
+function dragListen(fn: (ev: MouseEvent) => void) {
+	window.addEventListener('mousemove', fn);
+	window.addEventListener('touchmove', fn);
+	window.addEventListener('mouseleave', dragClear.bind(null, fn));
+	window.addEventListener('mouseup', dragClear.bind(null, fn));
+	window.addEventListener('touchend', dragClear.bind(null, fn));
+}
+
+function dragClear(fn) {
+	window.removeEventListener('mousemove', fn);
+	window.removeEventListener('touchmove', fn);
+	window.removeEventListener('mouseleave', dragClear);
+	window.removeEventListener('mouseup', dragClear);
+	window.removeEventListener('touchend', dragClear);
+}
+
+const props = withDefaults(defineProps<{
+	initialWidth?: number;
+	initialHeight?: number | null;
+	canResize?: boolean;
+	closeButton?: boolean;
+	mini?: boolean;
+	front?: boolean;
+	contextmenu?: MenuItem[] | null;
+	buttonsLeft?: any[];
+	buttonsRight?: any[];
+}>(), {
+	initialWidth: 400,
+	initialHeight: null,
+	canResize: false,
+	closeButton: true,
+	mini: false,
+	front: false,
+	contextmenu: null,
+	buttonsLeft: () => [],
+	buttonsRight: () => [],
+});
+
+const emit = defineEmits<{
+	(ev: 'closed'): void;
+}>();
+
+provide('inWindow', true);
+
+let rootEl = $ref<HTMLElement | null>();
+let showing = $ref(true);
+let beforeClickedAt = 0;
+let maximized = $ref(false);
+let unMaximizedTop = '';
+let unMaximizedLeft = '';
+let unMaximizedWidth = '';
+let unMaximizedHeight = '';
+
+function close() {
+	showing = false;
+}
+
+function onKeydown(evt) {
+	if (evt.which === 27) { // Esc
+		evt.preventDefault();
+		evt.stopPropagation();
+		close();
+	}
+}
+
+function onContextmenu(ev: MouseEvent) {
+	if (props.contextmenu) {
+		os.contextMenu(props.contextmenu, ev);
+	}
+}
+
+// 最前面へ移動
+function top() {
+	if (rootEl) {
+		rootEl.style.zIndex = os.claimZIndex(props.front ? 'middle' : 'low');
+	}
+}
+
+function maximize() {
+	maximized = true;
+	unMaximizedTop = rootEl.style.top;
+	unMaximizedLeft = rootEl.style.left;
+	unMaximizedWidth = rootEl.style.width;
+	unMaximizedHeight = rootEl.style.height;
+	rootEl.style.top = '0';
+	rootEl.style.left = '0';
+	rootEl.style.width = '100%';
+	rootEl.style.height = '100%';
+}
+
+function unMaximize() {
+	maximized = false;
+	rootEl.style.top = unMaximizedTop;
+	rootEl.style.left = unMaximizedLeft;
+	rootEl.style.width = unMaximizedWidth;
+	rootEl.style.height = unMaximizedHeight;
+}
+
+function onBodyMousedown() {
+	top();
+}
+
+function onDblClick() {
+	maximize();
+}
+
+function onHeaderMousedown(evt: MouseEvent) {
+	// 右クリックはコンテキストメニューを開こうとした可能性が高いため無視
+	if (evt.button === 2) return;
+
+	let beforeMaximized = false;
+
+	if (maximized) {
+		beforeMaximized = true;
+		unMaximize();
+	}
+
+	// ダブルクリック判定
+	if (Date.now() - beforeClickedAt < 300) {
+		beforeClickedAt = Date.now();
+		onDblClick();
+		return;
+	}
+
+	beforeClickedAt = Date.now();
+
+	const main = rootEl;
+	if (main == null) return;
+
+	if (!contains(main, document.activeElement)) main.focus();
+
+	const position = main.getBoundingClientRect();
+
+	const clickX = evt.touches && evt.touches.length > 0 ? evt.touches[0].clientX : evt.clientX;
+	const clickY = evt.touches && evt.touches.length > 0 ? evt.touches[0].clientY : evt.clientY;
+	const moveBaseX = beforeMaximized ? parseInt(unMaximizedWidth, 10) / 2 : clickX - position.left; // TODO: parseIntやめる
+	const moveBaseY = beforeMaximized ? 20 : clickY - position.top;
+	const browserWidth = window.innerWidth;
+	const browserHeight = window.innerHeight;
+	const windowWidth = main.offsetWidth;
+	const windowHeight = main.offsetHeight;
+
+	function move(x: number, y: number) {
+		let moveLeft = x - moveBaseX;
+		let moveTop = y - moveBaseY;
+
+		// 下はみ出し
+		if (moveTop + windowHeight > browserHeight) moveTop = browserHeight - windowHeight;
+
+		// 左はみ出し
+		if (moveLeft < 0) moveLeft = 0;
+
+		// 上はみ出し
+		if (moveTop < 0) moveTop = 0;
+
+		// 右はみ出し
+		if (moveLeft + windowWidth > browserWidth) moveLeft = browserWidth - windowWidth;
+
+		rootEl.style.left = moveLeft + 'px';
+		rootEl.style.top = moveTop + 'px';
+	}
+
+	if (beforeMaximized) {
+		move(clickX, clickY);
+	}
+
+	// 動かした時
+	dragListen(me => {
+		const x = me.touches && me.touches.length > 0 ? me.touches[0].clientX : me.clientX;
+		const y = me.touches && me.touches.length > 0 ? me.touches[0].clientY : me.clientY;
+
+		move(x, y);
+	});
+}
+
+// 上ハンドル掴み時
+function onTopHandleMousedown(evt) {
+	const main = rootEl;
+
+	const base = evt.clientY;
+	const height = parseInt(getComputedStyle(main, '').height, 10);
+	const top = parseInt(getComputedStyle(main, '').top, 10);
+
+	// 動かした時
+	dragListen(me => {
+		const move = me.clientY - base;
+		if (top + move > 0) {
+			if (height + -move > minHeight) {
+				applyTransformHeight(height + -move);
+				applyTransformTop(top + move);
+			} else { // 最小の高さより小さくなろうとした時
+				applyTransformHeight(minHeight);
+				applyTransformTop(top + (height - minHeight));
+			}
+		} else { // 上のはみ出し時
+			applyTransformHeight(top + height);
+			applyTransformTop(0);
+		}
+	});
+}
+
+// 右ハンドル掴み時
+function onRightHandleMousedown(evt) {
+	const main = rootEl;
+
+	const base = evt.clientX;
+	const width = parseInt(getComputedStyle(main, '').width, 10);
+	const left = parseInt(getComputedStyle(main, '').left, 10);
+	const browserWidth = window.innerWidth;
+
+	// 動かした時
+	dragListen(me => {
+		const move = me.clientX - base;
+		if (left + width + move < browserWidth) {
+			if (width + move > minWidth) {
+				applyTransformWidth(width + move);
+			} else { // 最小の幅より小さくなろうとした時
+				applyTransformWidth(minWidth);
+			}
+		} else { // 右のはみ出し時
+			applyTransformWidth(browserWidth - left);
+		}
+	});
+}
+
+// 下ハンドル掴み時
+function onBottomHandleMousedown(evt) {
+	const main = rootEl;
+
+	const base = evt.clientY;
+	const height = parseInt(getComputedStyle(main, '').height, 10);
+	const top = parseInt(getComputedStyle(main, '').top, 10);
+	const browserHeight = window.innerHeight;
+
+	// 動かした時
+	dragListen(me => {
+		const move = me.clientY - base;
+		if (top + height + move < browserHeight) {
+			if (height + move > minHeight) {
+				applyTransformHeight(height + move);
+			} else { // 最小の高さより小さくなろうとした時
+				applyTransformHeight(minHeight);
+			}
+		} else { // 下のはみ出し時
+			applyTransformHeight(browserHeight - top);
+		}
+	});
+}
+
+// 左ハンドル掴み時
+function onLeftHandleMousedown(evt) {
+	const main = rootEl;
+
+	const base = evt.clientX;
+	const width = parseInt(getComputedStyle(main, '').width, 10);
+	const left = parseInt(getComputedStyle(main, '').left, 10);
+
+	// 動かした時
+	dragListen(me => {
+		const move = me.clientX - base;
+		if (left + move > 0) {
+			if (width + -move > minWidth) {
+				applyTransformWidth(width + -move);
+				applyTransformLeft(left + move);
+			} else { // 最小の幅より小さくなろうとした時
+				applyTransformWidth(minWidth);
+				applyTransformLeft(left + (width - minWidth));
+			}
+		} else { // 左のはみ出し時
+			applyTransformWidth(left + width);
+			applyTransformLeft(0);
+		}
+	});
+}
+
+// 左上ハンドル掴み時
+function onTopLeftHandleMousedown(evt) {
+	onTopHandleMousedown(evt);
+	onLeftHandleMousedown(evt);
+}
+
+// 右上ハンドル掴み時
+function onTopRightHandleMousedown(evt) {
+	onTopHandleMousedown(evt);
+	onRightHandleMousedown(evt);
+}
+
+// 右下ハンドル掴み時
+function onBottomRightHandleMousedown(evt) {
+	onBottomHandleMousedown(evt);
+	onRightHandleMousedown(evt);
+}
+
+// 左下ハンドル掴み時
+function onBottomLeftHandleMousedown(evt) {
+	onBottomHandleMousedown(evt);
+	onLeftHandleMousedown(evt);
+}
+
+// 高さを適用
+function applyTransformHeight(height) {
+	if (height > window.innerHeight) height = window.innerHeight;
+	rootEl.style.height = height + 'px';
+}
+
+// 幅を適用
+function applyTransformWidth(width) {
+	if (width > window.innerWidth) width = window.innerWidth;
+	rootEl.style.width = width + 'px';
+}
+
+// Y座標を適用
+function applyTransformTop(top) {
+	rootEl.style.top = top + 'px';
+}
+
+// X座標を適用
+function applyTransformLeft(left) {
+	rootEl.style.left = left + 'px';
+}
+
+function onBrowserResize() {
+	const main = rootEl;
+	const position = main.getBoundingClientRect();
+	const browserWidth = window.innerWidth;
+	const browserHeight = window.innerHeight;
+	const windowWidth = main.offsetWidth;
+	const windowHeight = main.offsetHeight;
+	if (position.left < 0) main.style.left = '0'; // 左はみ出し
+	if (position.top + windowHeight > browserHeight) main.style.top = browserHeight - windowHeight + 'px'; // 下はみ出し
+	if (position.left + windowWidth > browserWidth) main.style.left = browserWidth - windowWidth + 'px'; // 右はみ出し
+	if (position.top < 0) main.style.top = '0'; // 上はみ出し
+}
+
+onMounted(() => {
+	if (props.initialWidth) applyTransformWidth(props.initialWidth);
+	if (props.initialHeight) applyTransformHeight(props.initialHeight);
+
+	applyTransformTop((window.innerHeight / 2) - (rootEl.offsetHeight / 2));
+	applyTransformLeft((window.innerWidth / 2) - (rootEl.offsetWidth / 2));
+
+	// 他のウィンドウ内のボタンなどを押してこのウィンドウが開かれた場合、親が最前面になろうとするのでそれに隠されないようにする
+	top();
+
+	window.addEventListener('resize', onBrowserResize);
+});
+
+onBeforeUnmount(() => {
+	window.removeEventListener('resize', onBrowserResize);
+});
+
+defineExpose({
+	close,
+});
+</script>
+
+<style lang="scss" scoped>
+.window-enter-active, .window-leave-active {
+	transition: opacity 0.2s, transform 0.2s !important;
+}
+.window-enter-from, .window-leave-to {
+	pointer-events: none;
+	opacity: 0;
+	transform: scale(0.9);
+}
+
+.ebkgocck {
+	position: fixed;
+	top: 0;
+	left: 0;
+
+	> .body {
+		overflow: clip;
+		display: flex;
+		flex-direction: column;
+		contain: content;
+		width: 100%;
+		height: 100%;
+		border-radius: var(--radius);
+
+		> .header {
+			--height: 42px;
+
+			&.mini {
+				--height: 38px;
+			}
+
+			display: flex;
+			position: relative;
+			z-index: 1;
+			flex-shrink: 0;
+			user-select: none;
+			height: var(--height);
+			background: var(--windowHeader);
+			-webkit-backdrop-filter: var(--blur, blur(15px));
+			backdrop-filter: var(--blur, blur(15px));
+			//border-bottom: solid 1px var(--divider);
+			font-size: 95%;
+			font-weight: bold;
+
+			> .left, > .right {
+				> .button {
+					height: var(--height);
+					width: var(--height);
+
+					&:hover {
+						color: var(--fgHighlighted);
+					}
+
+					&.highlighted {
+						color: var(--accent);
+					}
+				}
+			}
+
+			> .left {
+				margin-right: 16px;
+			}
+
+			> .right {
+				min-width: 16px;
+			}
+
+			> .title {
+				flex: 1;
+				position: relative;
+				line-height: var(--height);
+				white-space: nowrap;
+				overflow: hidden;
+				text-overflow: ellipsis;
+				cursor: move;
+			}
+		}
+
+		> .body {
+			flex: 1;
+			overflow: auto;
+			background: var(--panel);
+		}
+	}
+
+	> .handle {
+		$size: 8px;
+
+		position: absolute;
+
+		&.top {
+			top: -($size);
+			left: 0;
+			width: 100%;
+			height: $size;
+			cursor: ns-resize;
+		}
+
+		&.right {
+			top: 0;
+			right: -($size);
+			width: $size;
+			height: 100%;
+			cursor: ew-resize;
+		}
+
+		&.bottom {
+			bottom: -($size);
+			left: 0;
+			width: 100%;
+			height: $size;
+			cursor: ns-resize;
+		}
+
+		&.left {
+			top: 0;
+			left: -($size);
+			width: $size;
+			height: 100%;
+			cursor: ew-resize;
+		}
+
+		&.top-left {
+			top: -($size);
+			left: -($size);
+			width: $size * 2;
+			height: $size * 2;
+			cursor: nwse-resize;
+		}
+
+		&.top-right {
+			top: -($size);
+			right: -($size);
+			width: $size * 2;
+			height: $size * 2;
+			cursor: nesw-resize;
+		}
+
+		&.bottom-right {
+			bottom: -($size);
+			right: -($size);
+			width: $size * 2;
+			height: $size * 2;
+			cursor: nwse-resize;
+		}
+
+		&.bottom-left {
+			bottom: -($size);
+			left: -($size);
+			width: $size * 2;
+			height: $size * 2;
+			cursor: nesw-resize;
+		}
+	}
+
+	&.maximized {
+		> .body {
+			border-radius: 0;
+		}
+	}
+}
+</style>
diff --git a/packages/client/src/components/form/input.vue b/packages/client/src/components/form/input.vue
index 1c9fee8c77..382b2ed528 100644
--- a/packages/client/src/components/form/input.vue
+++ b/packages/client/src/components/form/input.vue
@@ -36,7 +36,7 @@
 <script lang="ts" setup>
 import { onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue';
 import { debounce } from 'throttle-debounce';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import { useInterval } from '@/scripts/use-interval';
 import { i18n } from '@/i18n';
 
diff --git a/packages/client/src/components/form/range.vue b/packages/client/src/components/form/range.vue
index 8301d27830..c0fc8c6510 100644
--- a/packages/client/src/components/form/range.vue
+++ b/packages/client/src/components/form/range.vue
@@ -96,7 +96,7 @@ const onMousedown = (ev: MouseEvent | TouchEvent) => {
 	ev.preventDefault();
 
 	const tooltipShowing = ref(true);
-	os.popup(defineAsyncComponent(() => import('@/components/ui/tooltip.vue')), {
+	os.popup(defineAsyncComponent(() => import('@/components/MkTooltip.vue')), {
 		showing: tooltipShowing,
 		text: computed(() => {
 			return props.textConverter(finalValue.value);
diff --git a/packages/client/src/components/form/select.vue b/packages/client/src/components/form/select.vue
index 70db2dbae3..313ba41cd3 100644
--- a/packages/client/src/components/form/select.vue
+++ b/packages/client/src/components/form/select.vue
@@ -28,7 +28,7 @@
 
 <script lang="ts" setup>
 import { onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs, VNode, useSlots } from 'vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os';
 import { useInterval } from '@/scripts/use-interval';
 import { i18n } from '@/i18n';
diff --git a/packages/client/src/components/form/suspense.vue b/packages/client/src/components/form/suspense.vue
index 2ad55dacae..132eafd138 100644
--- a/packages/client/src/components/form/suspense.vue
+++ b/packages/client/src/components/form/suspense.vue
@@ -17,7 +17,7 @@
 
 <script lang="ts">
 import { defineComponent, PropType, ref, watch } from 'vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 
 export default defineComponent({
 	components: {
diff --git a/packages/client/src/components/form/textarea.vue b/packages/client/src/components/form/textarea.vue
index 73633399de..ca57aa62a5 100644
--- a/packages/client/src/components/form/textarea.vue
+++ b/packages/client/src/components/form/textarea.vue
@@ -29,7 +29,7 @@
 <script lang="ts">
 import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue';
 import { debounce } from 'throttle-debounce';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import { i18n } from '@/i18n';
 
 export default defineComponent({
diff --git a/packages/client/src/components/global/MkAd.vue b/packages/client/src/components/global/MkAd.vue
index e1b7ae6aff..8161ef3792 100644
--- a/packages/client/src/components/global/MkAd.vue
+++ b/packages/client/src/components/global/MkAd.vue
@@ -22,7 +22,7 @@
 import { ref } from 'vue';
 import { instance } from '@/instance';
 import { host } from '@/config';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import { defaultStore } from '@/store';
 import * as os from '@/os';
 
diff --git a/packages/client/src/components/global/MkError.vue b/packages/client/src/components/global/MkError.vue
index 4e2ba07d30..6e75a69ec3 100644
--- a/packages/client/src/components/global/MkError.vue
+++ b/packages/client/src/components/global/MkError.vue
@@ -9,7 +9,7 @@
 </template>
 
 <script lang="ts" setup>
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import { i18n } from '@/i18n';
 </script>
 
diff --git a/packages/client/src/components/page/page.button.vue b/packages/client/src/components/page/page.button.vue
index a87f6e9f02..e8878df8de 100644
--- a/packages/client/src/components/page/page.button.vue
+++ b/packages/client/src/components/page/page.button.vue
@@ -6,7 +6,7 @@
 
 <script lang="ts">
 import { defineComponent, PropType, unref } from 'vue';
-import MkButton from '../ui/button.vue';
+import MkButton from '../MkButton.vue';
 import * as os from '@/os';
 import { ButtonBlock } from '@/scripts/hpml/block';
 import { Hpml } from '@/scripts/hpml/evaluator';
diff --git a/packages/client/src/components/page/page.counter.vue b/packages/client/src/components/page/page.counter.vue
index b1af8954b0..6eeef71431 100644
--- a/packages/client/src/components/page/page.counter.vue
+++ b/packages/client/src/components/page/page.counter.vue
@@ -6,7 +6,7 @@
 
 <script lang="ts">
 import { computed, defineComponent, PropType } from 'vue';
-import MkButton from '../ui/button.vue';
+import MkButton from '../MkButton.vue';
 import * as os from '@/os';
 import { CounterVarBlock } from '@/scripts/hpml/block';
 import { Hpml } from '@/scripts/hpml/evaluator';
diff --git a/packages/client/src/components/page/page.post.vue b/packages/client/src/components/page/page.post.vue
index 3401f945bd..f655196359 100644
--- a/packages/client/src/components/page/page.post.vue
+++ b/packages/client/src/components/page/page.post.vue
@@ -11,7 +11,7 @@
 <script lang="ts">
 import { defineComponent, PropType } from 'vue';
 import MkTextarea from '../form/textarea.vue';
-import MkButton from '../ui/button.vue';
+import MkButton from '../MkButton.vue';
 import { apiUrl } from '@/config';
 import * as os from '@/os';
 import { PostBlock } from '@/scripts/hpml/block';
diff --git a/packages/client/src/components/ui/button.vue b/packages/client/src/components/ui/button.vue
deleted file mode 100644
index a052f8f7a9..0000000000
--- a/packages/client/src/components/ui/button.vue
+++ /dev/null
@@ -1,226 +0,0 @@
-<template>
-<button
-	v-if="!link" class="bghgjjyj _button"
-	:class="{ inline, primary, gradate, danger, rounded, full }"
-	:type="type"
-	@click="emit('click', $event)"
-	@mousedown="onMousedown"
->
-	<div ref="ripples" class="ripples"></div>
-	<div class="content">
-		<slot></slot>
-	</div>
-</button>
-<MkA
-	v-else class="bghgjjyj _button"
-	:class="{ inline, primary, gradate, danger, rounded, full }"
-	:to="to"
-	@mousedown="onMousedown"
->
-	<div ref="ripples" class="ripples"></div>
-	<div class="content">
-		<slot></slot>
-	</div>
-</MkA>
-</template>
-
-<script lang="ts" setup>
-import { nextTick, onMounted } from 'vue';
-
-const props = defineProps<{
-	type?: 'button' | 'submit' | 'reset';
-	primary?: boolean;
-	gradate?: boolean;
-	rounded?: boolean;
-	inline?: boolean;
-	link?: boolean;
-	to?: string;
-	autofocus?: boolean;
-	wait?: boolean;
-	danger?: boolean;
-	full?: boolean;
-}>();
-
-const emit = defineEmits<{
-	(ev: 'click', payload: MouseEvent): void;
-}>();
-
-let el = $ref<HTMLElement | null>(null);
-let ripples = $ref<HTMLElement | null>(null);
-
-onMounted(() => {
-	if (props.autofocus) {
-		nextTick(() => {
-			el!.focus();
-		});
-	}
-});
-
-function distance(p, q): number {
-	return Math.hypot(p.x - q.x, p.y - q.y);
-}
-
-function calcCircleScale(boxW, boxH, circleCenterX, circleCenterY): number {
-	const origin = { x: circleCenterX, y: circleCenterY };
-	const dist1 = distance({ x: 0, y: 0 }, origin);
-	const dist2 = distance({ x: boxW, y: 0 }, origin);
-	const dist3 = distance({ x: 0, y: boxH }, origin);
-	const dist4 = distance({ x: boxW, y: boxH }, origin);
-	return Math.max(dist1, dist2, dist3, dist4) * 2;
-}
-
-function onMousedown(evt: MouseEvent): void {
-	const target = evt.target! as HTMLElement;
-	const rect = target.getBoundingClientRect();
-
-	const ripple = document.createElement('div');
-	ripple.style.top = (evt.clientY - rect.top - 1).toString() + 'px';
-	ripple.style.left = (evt.clientX - rect.left - 1).toString() + 'px';
-
-	ripples!.appendChild(ripple);
-
-	const circleCenterX = evt.clientX - rect.left;
-	const circleCenterY = evt.clientY - rect.top;
-
-	const scale = calcCircleScale(target.clientWidth, target.clientHeight, circleCenterX, circleCenterY);
-
-	window.setTimeout(() => {
-		ripple.style.transform = 'scale(' + (scale / 2) + ')';
-	}, 1);
-	window.setTimeout(() => {
-		ripple.style.transition = 'all 1s ease';
-		ripple.style.opacity = '0';
-	}, 1000);
-	window.setTimeout(() => {
-		if (ripples) ripples.removeChild(ripple);
-	}, 2000);
-}
-</script>
-
-<style lang="scss" scoped>
-.bghgjjyj {
-	position: relative;
-	z-index: 1; // 他コンポーネントのbox-shadowに隠されないようにするため
-	display: block;
-	min-width: 100px;
-	width: max-content;
-	padding: 8px 16px;
-	text-align: center;
-	font-weight: normal;
-	font-size: 1em;
-	box-shadow: none;
-	text-decoration: none;
-	background: var(--buttonBg);
-	border-radius: 5px;
-	overflow: clip;
-	box-sizing: border-box;
-	transition: background 0.1s ease;
-
-	&:not(:disabled):hover {
-		background: var(--buttonHoverBg);
-	}
-
-	&:not(:disabled):active {
-		background: var(--buttonHoverBg);
-	}
-
-	&.full {
-		width: 100%;
-	}
-
-	&.rounded {
-		border-radius: 999px;
-	}
-
-	&.primary {
-		font-weight: bold;
-		color: var(--fgOnAccent) !important;
-		background: var(--accent);
-
-		&:not(:disabled):hover {
-			background: var(--X8);
-		}
-
-		&:not(:disabled):active {
-			background: var(--X8);
-		}
-	}
-
-	&.gradate {
-		font-weight: bold;
-		color: var(--fgOnAccent) !important;
-		background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
-
-		&:not(:disabled):hover {
-			background: linear-gradient(90deg, var(--X8), var(--X8));
-		}
-
-		&:not(:disabled):active {
-			background: linear-gradient(90deg, var(--X8), var(--X8));
-		}
-	}
-
-	&.danger {
-		color: #ff2a2a;
-
-		&.primary {
-			color: #fff;
-			background: #ff2a2a;
-
-			&:not(:disabled):hover {
-				background: #ff4242;
-			}
-
-			&:not(:disabled):active {
-				background: #d42e2e;
-			}
-		}
-	}
-
-	&:disabled {
-		opacity: 0.7;
-	}
-
-	&:focus-visible {
-		outline: solid 2px var(--focus);
-		outline-offset: 2px;
-	}
-
-	&.inline {
-		display: inline-block;
-		width: auto;
-		min-width: 100px;
-	}
-
-	> .ripples {
-		position: absolute;
-		z-index: 0;
-		top: 0;
-		left: 0;
-		width: 100%;
-		height: 100%;
-		border-radius: 6px;
-		overflow: hidden;
-
-		::v-deep(div) {
-			position: absolute;
-			width: 2px;
-			height: 2px;
-			border-radius: 100%;
-			background: rgba(0, 0, 0, 0.1);
-			opacity: 1;
-			transform: scale(1);
-			transition: all 0.5s cubic-bezier(0,.5,0,1);
-		}
-	}
-
-	&.primary > .ripples ::v-deep(div) {
-		background: rgba(0, 0, 0, 0.15);
-	}
-
-	> .content {
-		position: relative;
-		z-index: 1;
-	}
-}
-</style>
diff --git a/packages/client/src/components/ui/container.vue b/packages/client/src/components/ui/container.vue
deleted file mode 100644
index 4be59adc2a..0000000000
--- a/packages/client/src/components/ui/container.vue
+++ /dev/null
@@ -1,264 +0,0 @@
-<template>
-<div v-size="{ max: [380] }" class="ukygtjoj _panel" :class="{ naked, thin, hideHeader: !showHeader, scrollable, closed: !showBody }">
-	<header v-if="showHeader" ref="header">
-		<div class="title"><slot name="header"></slot></div>
-		<div class="sub">
-			<slot name="func"></slot>
-			<button v-if="foldable" class="_button" @click="() => showBody = !showBody">
-				<template v-if="showBody"><i class="fas fa-angle-up"></i></template>
-				<template v-else><i class="fas fa-angle-down"></i></template>
-			</button>
-		</div>
-	</header>
-	<transition
-		:name="$store.state.animation ? 'container-toggle' : ''"
-		@enter="enter"
-		@after-enter="afterEnter"
-		@leave="leave"
-		@after-leave="afterLeave"
-	>
-		<div v-show="showBody" ref="content" class="content" :class="{ omitted }">
-			<slot></slot>
-			<button v-if="omitted" class="fade _button" @click="() => { ignoreOmit = true; omitted = false; }">
-				<span>{{ $ts.showMore }}</span>
-			</button>
-		</div>
-	</transition>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-
-export default defineComponent({
-	props: {
-		showHeader: {
-			type: Boolean,
-			required: false,
-			default: true,
-		},
-		thin: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-		naked: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-		foldable: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-		expanded: {
-			type: Boolean,
-			required: false,
-			default: true,
-		},
-		scrollable: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-		maxHeight: {
-			type: Number,
-			required: false,
-			default: null,
-		},
-	},
-	data() {
-		return {
-			showBody: this.expanded,
-			omitted: null,
-			ignoreOmit: false,
-		};
-	},
-	mounted() {
-		this.$watch('showBody', showBody => {
-			const headerHeight = this.showHeader ? this.$refs.header.offsetHeight : 0;
-			this.$el.style.minHeight = `${headerHeight}px`;
-			if (showBody) {
-				this.$el.style.flexBasis = 'auto';
-			} else {
-				this.$el.style.flexBasis = `${headerHeight}px`;
-			}
-		}, {
-			immediate: true,
-		});
-
-		this.$el.style.setProperty('--maxHeight', this.maxHeight + 'px');
-
-		const calcOmit = () => {
-			if (this.omitted || this.ignoreOmit || this.maxHeight == null) return;
-			const height = this.$refs.content.offsetHeight;
-			this.omitted = height > this.maxHeight;
-		};
-
-		calcOmit();
-		new ResizeObserver((entries, observer) => {
-			calcOmit();
-		}).observe(this.$refs.content);
-	},
-	methods: {
-		toggleContent(show: boolean) {
-			if (!this.foldable) return;
-			this.showBody = show;
-		},
-
-		enter(el) {
-			const elementHeight = el.getBoundingClientRect().height;
-			el.style.height = 0;
-			el.offsetHeight; // reflow
-			el.style.height = elementHeight + 'px';
-		},
-		afterEnter(el) {
-			el.style.height = null;
-		},
-		leave(el) {
-			const elementHeight = el.getBoundingClientRect().height;
-			el.style.height = elementHeight + 'px';
-			el.offsetHeight; // reflow
-			el.style.height = 0;
-		},
-		afterLeave(el) {
-			el.style.height = null;
-		},
-	},
-});
-</script>
-
-<style lang="scss" scoped>
-.container-toggle-enter-active, .container-toggle-leave-active {
-	overflow-y: hidden;
-	transition: opacity 0.5s, height 0.5s !important;
-}
-.container-toggle-enter-from {
-	opacity: 0;
-}
-.container-toggle-leave-to {
-	opacity: 0;
-}
-
-.ukygtjoj {
-	position: relative;
-	overflow: clip;
-	contain: content;
-
-	&.naked {
-		background: transparent !important;
-		box-shadow: none !important;
-	}
-
-	&.scrollable {
-		display: flex;
-		flex-direction: column;
-
-		> .content {
-			overflow: auto;
-		}
-	}
-
-	> header {
-		position: sticky;
-		top: var(--stickyTop, 0px);
-		left: 0;
-		color: var(--panelHeaderFg);
-		background: var(--panelHeaderBg);
-		border-bottom: solid 0.5px var(--panelHeaderDivider);
-		z-index: 2;
-		line-height: 1.4em;
-
-		> .title {
-			margin: 0;
-			padding: 12px 16px;
-
-			> ::v-deep(i) {
-				margin-right: 6px;
-			}
-
-			&:empty {
-				display: none;
-			}
-		}
-
-		> .sub {
-			position: absolute;
-			z-index: 2;
-			top: 0;
-			right: 0;
-			height: 100%;
-
-			> ::v-deep(button) {
-				width: 42px;
-				height: 100%;
-			}
-		}
-	}
-
-	> .content {
-		--stickyTop: 0px;
-
-		&.omitted {
-			position: relative;
-			max-height: var(--maxHeight);
-			overflow: hidden;
-
-			> .fade {
-				display: block;
-				position: absolute;
-				z-index: 10;
-				bottom: 0;
-				left: 0;
-				width: 100%;
-				height: 64px;
-				background: linear-gradient(0deg, var(--panel), var(--X15));
-
-				> span {
-					display: inline-block;
-					background: var(--panel);
-					padding: 6px 10px;
-					font-size: 0.8em;
-					border-radius: 999px;
-					box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
-				}
-
-				&:hover {
-					> span {
-						background: var(--panelHighlight);
-					}
-				}
-			}
-		}
-	}
-
-	&.max-width_380px, &.thin {
-		> header {
-			> .title {
-				padding: 8px 10px;
-				font-size: 0.9em;
-			}
-		}
-
-		> .content {
-		}
-	}
-}
-
-._forceContainerFull_ .ukygtjoj {
-	> header {
-		> .title {
-			padding: 12px 16px !important;
-		}
-	}
-}
-
-._forceContainerFull_.ukygtjoj {
-	> header {
-		> .title {
-			padding: 12px 16px !important;
-		}
-	}
-}
-</style>
diff --git a/packages/client/src/components/ui/context-menu.vue b/packages/client/src/components/ui/context-menu.vue
deleted file mode 100644
index 165c3db462..0000000000
--- a/packages/client/src/components/ui/context-menu.vue
+++ /dev/null
@@ -1,85 +0,0 @@
-<template>
-<transition :name="$store.state.animation ? 'fade' : ''" appear>
-	<div ref="rootEl" class="nvlagfpb" :style="{ zIndex }" @contextmenu.prevent.stop="() => {}">
-		<MkMenu :items="items" :align="'left'" @close="$emit('closed')"/>
-	</div>
-</transition>
-</template>
-
-<script lang="ts" setup>
-import { onMounted, onBeforeUnmount } from 'vue';
-import MkMenu from './menu.vue';
-import { MenuItem } from './types/menu.vue';
-import contains from '@/scripts/contains';
-import * as os from '@/os';
-
-const props = defineProps<{
-	items: MenuItem[];
-	ev: MouseEvent;
-}>();
-
-const emit = defineEmits<{
-	(ev: 'closed'): void;
-}>();
-
-let rootEl = $ref<HTMLDivElement>();
-
-let zIndex = $ref<number>(os.claimZIndex('high'));
-
-onMounted(() => {
-	let left = props.ev.pageX + 1; // 間違って右ダブルクリックした場合に意図せずアイテムがクリックされるのを防ぐため + 1
-	let top = props.ev.pageY + 1; // 間違って右ダブルクリックした場合に意図せずアイテムがクリックされるのを防ぐため + 1
-
-	const width = rootEl.offsetWidth;
-	const height = rootEl.offsetHeight;
-
-	if (left + width - window.pageXOffset > window.innerWidth) {
-		left = window.innerWidth - width + window.pageXOffset;
-	}
-
-	if (top + height - window.pageYOffset > window.innerHeight) {
-		top = window.innerHeight - height + window.pageYOffset;
-	}
-
-	if (top < 0) {
-		top = 0;
-	}
-
-	if (left < 0) {
-		left = 0;
-	}
-
-	rootEl.style.top = `${top}px`;
-	rootEl.style.left = `${left}px`;
-
-	for (const el of Array.from(document.querySelectorAll('body *'))) {
-		el.addEventListener('mousedown', onMousedown);
-	}
-});
-
-onBeforeUnmount(() => {
-	for (const el of Array.from(document.querySelectorAll('body *'))) {
-		el.removeEventListener('mousedown', onMousedown);
-	}
-});
-
-function onMousedown(evt: Event) {
-	if (!contains(rootEl, evt.target) && (rootEl !== evt.target)) emit('closed');
-}
-</script>
-
-<style lang="scss" scoped>
-.nvlagfpb {
-	position: absolute;
-}
-
-.fade-enter-active, .fade-leave-active {
-	transition: opacity 0.5s cubic-bezier(0.16, 1, 0.3, 1), transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
-	transform-origin: left top;
-}
-
-.fade-enter-from, .fade-leave-to {
-	opacity: 0;
-	transform: scale(0.9);
-}
-</style>
diff --git a/packages/client/src/components/ui/folder.vue b/packages/client/src/components/ui/folder.vue
deleted file mode 100644
index 7daa82cbd3..0000000000
--- a/packages/client/src/components/ui/folder.vue
+++ /dev/null
@@ -1,156 +0,0 @@
-<template>
-<div v-size="{ max: [500] }" class="ssazuxis">
-	<header class="_button" :style="{ background: bg }" @click="showBody = !showBody">
-		<div class="title"><slot name="header"></slot></div>
-		<div class="divider"></div>
-		<button class="_button">
-			<template v-if="showBody"><i class="fas fa-angle-up"></i></template>
-			<template v-else><i class="fas fa-angle-down"></i></template>
-		</button>
-	</header>
-	<transition :name="$store.state.animation ? 'folder-toggle' : ''"
-		@enter="enter"
-		@after-enter="afterEnter"
-		@leave="leave"
-		@after-leave="afterLeave"
-	>
-		<div v-show="showBody">
-			<slot></slot>
-		</div>
-	</transition>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import tinycolor from 'tinycolor2';
-
-const localStoragePrefix = 'ui:folder:';
-
-export default defineComponent({
-	props: {
-		expanded: {
-			type: Boolean,
-			required: false,
-			default: true
-		},
-		persistKey: {
-			type: String,
-			required: false,
-			default: null
-		},
-	},
-	data() {
-		return {
-			bg: null,
-			showBody: (this.persistKey && localStorage.getItem(localStoragePrefix + this.persistKey)) ? localStorage.getItem(localStoragePrefix + this.persistKey) === 't' : this.expanded,
-		};
-	},
-	watch: {
-		showBody() {
-			if (this.persistKey) {
-				localStorage.setItem(localStoragePrefix + this.persistKey, this.showBody ? 't' : 'f');
-			}
-		}
-	},
-	mounted() {
-		function getParentBg(el: Element | null): string {
-			if (el == null || el.tagName === 'BODY') return 'var(--bg)';
-			const bg = el.style.background || el.style.backgroundColor;
-			if (bg) {
-				return bg;
-			} else {
-				return getParentBg(el.parentElement);
-			}
-		}
-		const rawBg = getParentBg(this.$el);
-		const bg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg);
-		bg.setAlpha(0.85);
-		this.bg = bg.toRgbString();
-	},
-	methods: {
-		toggleContent(show: boolean) {
-			this.showBody = show;
-		},
-
-		enter(el) {
-			const elementHeight = el.getBoundingClientRect().height;
-			el.style.height = 0;
-			el.offsetHeight; // reflow
-			el.style.height = elementHeight + 'px';
-		},
-		afterEnter(el) {
-			el.style.height = null;
-		},
-		leave(el) {
-			const elementHeight = el.getBoundingClientRect().height;
-			el.style.height = elementHeight + 'px';
-			el.offsetHeight; // reflow
-			el.style.height = 0;
-		},
-		afterLeave(el) {
-			el.style.height = null;
-		},
-	}
-});
-</script>
-
-<style lang="scss" scoped>
-.folder-toggle-enter-active, .folder-toggle-leave-active {
-	overflow-y: hidden;
-	transition: opacity 0.5s, height 0.5s !important;
-}
-.folder-toggle-enter-from {
-	opacity: 0;
-}
-.folder-toggle-leave-to {
-	opacity: 0;
-}
-
-.ssazuxis {
-	position: relative;
-
-	> header {
-		display: flex;
-		position: relative;
-		z-index: 10;
-		position: sticky;
-		top: var(--stickyTop, 0px);
-		padding: var(--x-padding);
-		-webkit-backdrop-filter: var(--blur, blur(8px));
-		backdrop-filter: var(--blur, blur(20px));
-
-		> .title {
-			margin: 0;
-			padding: 12px 16px 12px 0;
-
-			> i {
-				margin-right: 6px;
-			}
-
-			&:empty {
-				display: none;
-			}
-		}
-
-		> .divider {
-			flex: 1;
-			margin: auto;
-			height: 1px;
-			background: var(--divider);
-		}
-
-		> button {
-			padding: 12px 0 12px 16px;
-		}
-	}
-
-	&.max-width_500px {
-		> header {
-			> .title {
-				padding: 8px 10px 8px 0;
-			}
-		}
-	}
-}
-</style>
diff --git a/packages/client/src/components/ui/info.vue b/packages/client/src/components/ui/info.vue
deleted file mode 100644
index 4fdfc5c5e6..0000000000
--- a/packages/client/src/components/ui/info.vue
+++ /dev/null
@@ -1,34 +0,0 @@
-<template>
-<div class="fpezltsf" :class="{ warn }">
-	<i v-if="warn" class="fas fa-exclamation-triangle"></i>
-	<i v-else class="fas fa-info-circle"></i>
-	<slot></slot>
-</div>
-</template>
-
-<script lang="ts" setup>
-import { } from 'vue';
-
-const props = defineProps<{
-	warn?: boolean;
-}>();
-</script>
-
-<style lang="scss" scoped>
-.fpezltsf {
-	padding: 16px;
-	font-size: 90%;
-	background: var(--infoBg);
-	color: var(--infoFg);
-	border-radius: var(--radius);
-
-	&.warn {
-		background: var(--infoWarnBg);
-		color: var(--infoWarnFg);
-	}
-
-	> i {
-		margin-right: 4px;
-	}
-}
-</style>
diff --git a/packages/client/src/components/ui/menu.child.vue b/packages/client/src/components/ui/menu.child.vue
deleted file mode 100644
index b67224d3e1..0000000000
--- a/packages/client/src/components/ui/menu.child.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-<template>
-<div ref="el" class="sfhdhdhr">
-	<MkMenu ref="menu" :items="items" :align="align" :width="width" :as-drawer="false" @close="onChildClosed"/>
-</div>
-</template>
-
-<script lang="ts" setup>
-import { on } from 'events';
-import { nextTick, onBeforeUnmount, onMounted, onUnmounted, ref, watch } from 'vue';
-import MkMenu from './menu.vue';
-import { MenuItem } from '@/types/menu';
-import * as os from '@/os';
-
-const props = defineProps<{
-	items: MenuItem[];
-	targetElement: HTMLElement;
-	rootElement: HTMLElement;
-	width?: number;
-	viaKeyboard?: boolean;
-}>();
-
-const emit = defineEmits<{
-	(ev: 'closed'): void;
-	(ev: 'actioned'): void;
-}>();
-
-const el = ref<HTMLElement>();
-const align = 'left';
-
-function setPosition() {
-	const rootRect = props.rootElement.getBoundingClientRect();
-	const rect = props.targetElement.getBoundingClientRect();
-	const left = props.targetElement.offsetWidth;
-	const top = (rect.top - rootRect.top) - 8;
-	el.value.style.left = left + 'px';
-	el.value.style.top = top + 'px';
-}
-
-function onChildClosed(actioned?: boolean) {
-	if (actioned) {
-		emit('actioned');
-	} else {
-		emit('closed');
-	}
-}
-
-onMounted(() => {
-	setPosition();
-	nextTick(() => {
-		setPosition();
-	});
-});
-
-defineExpose({
-	checkHit: (ev: MouseEvent) => {
-		return (ev.target === el.value || el.value.contains(ev.target));
-	},
-});
-</script>
-
-<style lang="scss" scoped>
-.sfhdhdhr {
-	position: absolute;
-}
-</style>
diff --git a/packages/client/src/components/ui/menu.vue b/packages/client/src/components/ui/menu.vue
deleted file mode 100644
index 60b68954d6..0000000000
--- a/packages/client/src/components/ui/menu.vue
+++ /dev/null
@@ -1,364 +0,0 @@
-<template>
-<div>
-	<div
-		ref="itemsEl" v-hotkey="keymap"
-		class="rrevdjwt _popup _shadow"
-		:class="{ center: align === 'center', asDrawer }"
-		:style="{ width: (width && !asDrawer) ? width + 'px' : '', maxHeight: maxHeight ? maxHeight + 'px' : '' }"
-		@contextmenu.self="e => e.preventDefault()"
-	>
-		<template v-for="(item, i) in items2">
-			<div v-if="item === null" class="divider"></div>
-			<span v-else-if="item.type === 'label'" class="label item">
-				<span>{{ item.text }}</span>
-			</span>
-			<span v-else-if="item.type === 'pending'" :tabindex="i" class="pending item">
-				<span><MkEllipsis/></span>
-			</span>
-			<MkA v-else-if="item.type === 'link'" :to="item.to" :tabindex="i" class="_button item" @click.passive="close(true)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
-				<i v-if="item.icon" class="fa-fw" :class="item.icon"></i>
-				<MkAvatar v-if="item.avatar" :user="item.avatar" class="avatar"/>
-				<span>{{ item.text }}</span>
-				<span v-if="item.indicate" class="indicator"><i class="fas fa-circle"></i></span>
-			</MkA>
-			<a v-else-if="item.type === 'a'" :href="item.href" :target="item.target" :download="item.download" :tabindex="i" class="_button item" @click="close(true)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
-				<i v-if="item.icon" class="fa-fw" :class="item.icon"></i>
-				<span>{{ item.text }}</span>
-				<span v-if="item.indicate" class="indicator"><i class="fas fa-circle"></i></span>
-			</a>
-			<button v-else-if="item.type === 'user'" :tabindex="i" class="_button item" :class="{ active: item.active }" :disabled="item.active" @click="clicked(item.action, $event)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
-				<MkAvatar :user="item.user" class="avatar"/><MkUserName :user="item.user"/>
-				<span v-if="item.indicate" class="indicator"><i class="fas fa-circle"></i></span>
-			</button>
-			<span v-else-if="item.type === 'switch'" :tabindex="i" class="item" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
-				<FormSwitch v-model="item.ref" :disabled="item.disabled" class="form-switch">{{ item.text }}</FormSwitch>
-			</span>
-			<button v-else-if="item.type === 'parent'" :tabindex="i" class="_button item parent" :class="{ childShowing: childShowingItem === item }" @mouseenter="showChildren(item, $event)">
-				<i v-if="item.icon" class="fa-fw" :class="item.icon"></i>
-				<span>{{ item.text }}</span>
-				<span class="caret"><i class="fas fa-caret-right fa-fw"></i></span>
-			</button>
-			<button v-else :tabindex="i" class="_button item" :class="{ danger: item.danger, active: item.active }" :disabled="item.active" @click="clicked(item.action, $event)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
-				<i v-if="item.icon" class="fa-fw" :class="item.icon"></i>
-				<MkAvatar v-if="item.avatar" :user="item.avatar" class="avatar"/>
-				<span>{{ item.text }}</span>
-				<span v-if="item.indicate" class="indicator"><i class="fas fa-circle"></i></span>
-			</button>
-		</template>
-		<span v-if="items2.length === 0" class="none item">
-			<span>{{ i18n.ts.none }}</span>
-		</span>
-	</div>
-	<div v-if="childMenu" class="child">
-		<XChild ref="child" :items="childMenu" :target-element="childTarget" :root-element="itemsEl" showing @actioned="childActioned"/>
-	</div>
-</div>
-</template>
-
-<script lang="ts" setup>
-import { defineAsyncComponent, nextTick, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
-import { focusPrev, focusNext } from '@/scripts/focus';
-import FormSwitch from '@/components/form/switch.vue';
-import { MenuItem, InnerMenuItem, MenuPending, MenuAction } from '@/types/menu';
-import * as os from '@/os';
-import { i18n } from '@/i18n';
-
-const XChild = defineAsyncComponent(() => import('./menu.child.vue'));
-
-const props = defineProps<{
-	items: MenuItem[];
-	viaKeyboard?: boolean;
-	asDrawer?: boolean;
-	align?: 'center' | string;
-	width?: number;
-	maxHeight?: number;
-}>();
-
-const emit = defineEmits<{
-	(ev: 'close', actioned?: boolean): void;
-}>();
-
-let itemsEl = $ref<HTMLDivElement>();
-
-let items2: InnerMenuItem[] = $ref([]);
-
-let child = $ref<InstanceType<typeof XChild>>();
-
-let keymap = $computed(() => ({
-	'up|k|shift+tab': focusUp,
-	'down|j|tab': focusDown,
-	'esc': close,
-}));
-
-let childShowingItem = $ref<MenuItem | null>();
-
-watch(() => props.items, () => {
-	const items: (MenuItem | MenuPending)[] = [...props.items].filter(item => item !== undefined);
-
-	for (let i = 0; i < items.length; i++) {
-		const item = items[i];
-
-		if (item && 'then' in item) { // if item is Promise
-			items[i] = { type: 'pending' };
-			item.then(actualItem => {
-				items2[i] = actualItem;
-			});
-		}
-	}
-
-	items2 = items as InnerMenuItem[];
-}, {
-	immediate: true,
-});
-
-let childMenu = $ref<MenuItem[] | null>();
-let childTarget = $ref<HTMLElement | null>();
-
-function closeChild() {
-	childMenu = null;
-	childShowingItem = null;
-}
-
-function childActioned() {
-	closeChild();
-	close(true);
-}
-
-function onGlobalMousedown(event: MouseEvent) {
-	if (childTarget && (event.target === childTarget || childTarget.contains(event.target))) return;
-	if (child && child.checkHit(event)) return;
-	closeChild();
-}
-
-let childCloseTimer: null | number = null;
-function onItemMouseEnter(item) {
-	childCloseTimer = window.setTimeout(() => {
-		closeChild();
-	}, 300);
-}
-function onItemMouseLeave(item) {
-	if (childCloseTimer) window.clearTimeout(childCloseTimer);
-}
-
-async function showChildren(item: MenuItem, ev: MouseEvent) {
-	if (props.asDrawer) {
-		os.popupMenu(item.children, ev.currentTarget ?? ev.target);
-		close();
-	} else {
-		childTarget = ev.currentTarget ?? ev.target;
-		childMenu = item.children;
-		childShowingItem = item;
-	}
-}
-
-function clicked(fn: MenuAction, ev: MouseEvent) {
-	fn(ev);
-	close(true);
-}
-
-function close(actioned = false) {
-	emit('close', actioned);
-}
-
-function focusUp() {
-	focusPrev(document.activeElement);
-}
-
-function focusDown() {
-	focusNext(document.activeElement);
-}
-
-onMounted(() => {
-	if (props.viaKeyboard) {
-		nextTick(() => {
-			focusNext(itemsEl.children[0], true, false);
-		});
-	}
-
-	document.addEventListener('mousedown', onGlobalMousedown, { passive: true });
-});
-
-onBeforeUnmount(() => {
-	document.removeEventListener('mousedown', onGlobalMousedown);
-});
-</script>
-
-<style lang="scss" scoped>
-.rrevdjwt {
-	padding: 8px 0;
-	box-sizing: border-box;
-	min-width: 200px;
-	overflow: auto;
-	overscroll-behavior: contain;
-
-	&.center {
-		> .item {
-			text-align: center;
-		}
-	}
-
-	> .item {
-		display: block;
-		position: relative;
-		padding: 6px 16px;
-		width: 100%;
-		box-sizing: border-box;
-		white-space: nowrap;
-		font-size: 0.9em;
-		line-height: 20px;
-		text-align: left;
-		overflow: hidden;
-		text-overflow: ellipsis;
-
-		&:before {
-			content: "";
-			display: block;
-			position: absolute;
-			top: 0;
-			left: 0;
-			right: 0;
-			margin: auto;
-			width: calc(100% - 16px);
-			height: 100%;
-			border-radius: 6px;
-		}
-
-		> * {
-			position: relative;
-		}
-
-		&:not(:disabled):hover {
-			color: var(--accent);
-			text-decoration: none;
-
-			&:before {
-				background: var(--accentedBg);
-			}
-		}
-
-		&.danger {
-			color: #ff2a2a;
-
-			&:hover {
-				color: #fff;
-
-				&:before {
-					background: #ff4242;
-				}
-			}
-
-			&:active {
-				color: #fff;
-
-				&:before {
-					background: #d42e2e;
-				}
-			}
-		}
-
-		&.active {
-			color: var(--fgOnAccent);
-			opacity: 1;
-
-			&:before {
-				background: var(--accent);
-			}
-		}
-
-		&:not(:active):focus-visible {
-			box-shadow: 0 0 0 2px var(--focus) inset;
-		}
-
-		&.label {
-			pointer-events: none;
-			font-size: 0.7em;
-			padding-bottom: 4px;
-
-			> span {
-				opacity: 0.7;
-			}
-		}
-
-		&.pending {
-			pointer-events: none;
-			opacity: 0.7;
-		}
-
-		&.none {
-			pointer-events: none;
-			opacity: 0.7;
-		}
-
-		&.parent {
-			display: flex;
-			align-items: center;
-			cursor: default;
-
-			> .caret {
-				margin-left: auto;
-			}
-
-			&.childShowing {
-				color: var(--accent);
-				text-decoration: none;
-
-				&:before {
-					background: var(--accentedBg);
-				}
-			}
-		}
-
-		> i {
-			margin-right: 5px;
-			width: 20px;
-		}
-
-		> .avatar {
-			margin-right: 5px;
-			width: 20px;
-			height: 20px;
-		}
-
-		> .indicator {
-			position: absolute;
-			top: 5px;
-			left: 13px;
-			color: var(--indicator);
-			font-size: 12px;
-			animation: blink 1s infinite;
-		}
-	}
-
-	> .divider {
-		margin: 8px 0;
-		border-top: solid 0.5px var(--divider);
-	}
-
-	&.asDrawer {
-		padding: 12px 0 calc(env(safe-area-inset-bottom, 0px) + 12px) 0;
-		width: 100%;
-		border-radius: 24px;
-		border-bottom-right-radius: 0;
-		border-bottom-left-radius: 0;
-
-		> .item {
-			font-size: 1em;
-			padding: 12px 24px;
-
-			&:before {
-				width: calc(100% - 24px);
-				border-radius: 12px;
-			}
-
-			> i {
-				margin-right: 14px;
-				width: 24px;
-			}
-		}
-
-		> .divider {
-			margin: 12px 0;
-		}
-	}
-}
-</style>
diff --git a/packages/client/src/components/ui/modal-window.vue b/packages/client/src/components/ui/modal-window.vue
deleted file mode 100644
index b29ea4fd81..0000000000
--- a/packages/client/src/components/ui/modal-window.vue
+++ /dev/null
@@ -1,146 +0,0 @@
-<template>
-<MkModal ref="modal" :prefer-type="'dialog'" @click="onBgClick" @closed="$emit('closed')">
-	<div ref="rootEl" class="ebkgoccj _narrow_" :style="{ width: `${width}px`, height: scroll ? (height ? `${height}px` : null) : (height ? `min(${height}px, 100%)` : '100%') }" @keydown="onKeydown">
-		<div ref="headerEl" class="header">
-			<button v-if="withOkButton" class="_button" @click="$emit('close')"><i class="fas fa-times"></i></button>
-			<span class="title">
-				<slot name="header"></slot>
-			</span>
-			<button v-if="!withOkButton" class="_button" @click="$emit('close')"><i class="fas fa-times"></i></button>
-			<button v-if="withOkButton" class="_button" :disabled="okButtonDisabled" @click="$emit('ok')"><i class="fas fa-check"></i></button>
-		</div>
-		<div class="body">
-			<slot :width="bodyWidth" :height="bodyHeight"></slot>
-		</div>
-	</div>
-</MkModal>
-</template>
-
-<script lang="ts" setup>
-import { onMounted, onUnmounted } from 'vue';
-import MkModal from './modal.vue';
-
-const props = withDefaults(defineProps<{
-	withOkButton: boolean;
-	okButtonDisabled: boolean;
-	width: number;
-	height: number | null;
-	scroll: boolean;
-}>(), {
-	withOkButton: false,
-	okButtonDisabled: false,
-	width: 400,
-	height: null,
-	scroll: true,
-});
-
-const emit = defineEmits<{
-	(event: 'click'): void;
-	(event: 'close'): void;
-	(event: 'closed'): void;
-	(event: 'ok'): void;
-}>();
-
-let modal = $ref<InstanceType<typeof MkModal>>();
-let rootEl = $ref<HTMLElement>();
-let headerEl = $ref<HTMLElement>();
-let bodyWidth = $ref(0);
-let bodyHeight = $ref(0);
-
-const close = () => {
-	modal.close();
-};
-
-const onBgClick = () => {
-	emit('click');
-};
-
-const onKeydown = (evt) => {
-	if (evt.which === 27) { // Esc
-		evt.preventDefault();
-		evt.stopPropagation();
-		close();
-	}
-};
-
-const ro = new ResizeObserver((entries, observer) => {
-	bodyWidth = rootEl.offsetWidth;
-	bodyHeight = rootEl.offsetHeight - headerEl.offsetHeight;
-});
-
-onMounted(() => {
-	bodyWidth = rootEl.offsetWidth;
-	bodyHeight = rootEl.offsetHeight - headerEl.offsetHeight;
-	ro.observe(rootEl);
-});
-
-onUnmounted(() => {
-	ro.disconnect();
-});
-
-defineExpose({
-	close,
-});
-</script>
-
-<style lang="scss" scoped>
-.ebkgoccj {
-	overflow: hidden;
-	display: flex;
-	flex-direction: column;
-	contain: content;
-	border-radius: var(--radius);
-
-	--root-margin: 24px;
-
-	@media (max-width: 500px) {
-		--root-margin: 16px;
-	}
-
-	> .header {
-		$height: 46px;
-		$height-narrow: 42px;
-		display: flex;
-		flex-shrink: 0;
-		background: var(--windowHeader);
-		-webkit-backdrop-filter: var(--blur, blur(15px));
-		backdrop-filter: var(--blur, blur(15px));
-
-		> button {
-			height: $height;
-			width: $height;
-
-			@media (max-width: 500px) {
-				height: $height-narrow;
-				width: $height-narrow;
-			}
-		}
-
-		> .title {
-			flex: 1;
-			line-height: $height;
-			padding-left: 32px;
-			font-weight: bold;
-			white-space: nowrap;
-			overflow: hidden;
-			text-overflow: ellipsis;
-			pointer-events: none;
-
-			@media (max-width: 500px) {
-				line-height: $height-narrow;
-				padding-left: 16px;
-			}
-		}
-
-		> button + .title {
-			padding-left: 0;
-		}
-	}
-
-	> .body {
-		flex: 1;
-		overflow: auto;
-		background: var(--panel);
-	}
-}
-</style>
diff --git a/packages/client/src/components/ui/modal.vue b/packages/client/src/components/ui/modal.vue
deleted file mode 100644
index 2305a02794..0000000000
--- a/packages/client/src/components/ui/modal.vue
+++ /dev/null
@@ -1,406 +0,0 @@
-<template>
-<transition :name="$store.state.animation ? (type === 'drawer') ? 'modal-drawer' : (type === 'popup') ? 'modal-popup' : 'modal' : ''" :duration="$store.state.animation ? 200 : 0" appear @after-leave="emit('closed')" @enter="emit('opening')" @after-enter="onOpened">
-	<div v-show="manualShowing != null ? manualShowing : showing" v-hotkey.global="keymap" class="qzhlnise" :class="{ drawer: type === 'drawer', dialog: type === 'dialog' || type === 'dialog:top', popup: type === 'popup' }" :style="{ zIndex, pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }">
-		<div class="bg _modalBg" :class="{ transparent: transparentBg && (type === 'popup') }" :style="{ zIndex }" @click="onBgClick" @contextmenu.prevent.stop="() => {}"></div>
-		<div ref="content" class="content" :class="{ fixed, top: type === 'dialog:top' }" :style="{ zIndex }" @click.self="onBgClick">
-			<slot :max-height="maxHeight" :type="type"></slot>
-		</div>
-	</div>
-</transition>
-</template>
-
-<script lang="ts" setup>
-import { nextTick, onMounted, watch, provide } from 'vue';
-import * as os from '@/os';
-import { isTouchUsing } from '@/scripts/touch';
-import { defaultStore } from '@/store';
-import { deviceKind } from '@/scripts/device-kind';
-
-function getFixedContainer(el: Element | null): Element | null {
-	if (el == null || el.tagName === 'BODY') return null;
-	const position = window.getComputedStyle(el).getPropertyValue('position');
-	if (position === 'fixed') {
-		return el;
-	} else {
-		return getFixedContainer(el.parentElement);
-	}
-}
-
-type ModalTypes = 'popup' | 'dialog' | 'dialog:top' | 'drawer';
-
-const props = withDefaults(defineProps<{
-	manualShowing?: boolean | null;
-	anchor?: { x: string; y: string; };
-	src?: HTMLElement;
-	preferType?: ModalTypes | 'auto';
-	zPriority?: 'low' | 'middle' | 'high';
-	noOverlap?: boolean;
-	transparentBg?: boolean;
-}>(), {
-	manualShowing: null,
-	src: null,
-	anchor: () => ({ x: 'center', y: 'bottom' }),
-	preferType: 'auto',
-	zPriority: 'low',
-	noOverlap: true,
-	transparentBg: false,
-});
-
-const emit = defineEmits<{
-	(ev: 'opening'): void;
-	(ev: 'opened'): void;
-	(ev: 'click'): void;
-	(ev: 'esc'): void;
-	(ev: 'close'): void;
-	(ev: 'closed'): void;
-}>();
-
-provide('modal', true);
-
-let maxHeight = $ref<number>();
-let fixed = $ref(false);
-let transformOrigin = $ref('center');
-let showing = $ref(true);
-let content = $ref<HTMLElement>();
-const zIndex = os.claimZIndex(props.zPriority);
-const type = $computed(() => {
-	if (props.preferType === 'auto') {
-		if (!defaultStore.state.disableDrawer && isTouchUsing && deviceKind === 'smartphone') {
-			return 'drawer';
-		} else {
-			return props.src != null ? 'popup' : 'dialog';
-		}
-	} else {
-		return props.preferType!;
-	}
-});
-
-let contentClicking = false;
-
-const close = () => {
-	// eslint-disable-next-line vue/no-mutating-props
-	if (props.src) props.src.style.pointerEvents = 'auto';
-	showing = false;
-	emit('close');
-};
-
-const onBgClick = () => {
-	if (contentClicking) return;
-	emit('click');
-};
-
-if (type === 'drawer') {
-	maxHeight = window.innerHeight / 1.5;
-}
-
-const keymap = {
-	'esc': () => emit('esc'),
-};
-
-const MARGIN = 16;
-
-const align = () => {
-	if (props.src == null) return;
-	if (type === 'drawer') return;
-	if (type === 'dialog') return;
-
-	if (content == null) return;
-
-	const srcRect = props.src.getBoundingClientRect();
-
-	const width = content!.offsetWidth;
-	const height = content!.offsetHeight;
-
-	let left;
-	let top;
-
-	const x = srcRect.left + (fixed ? 0 : window.pageXOffset);
-	const y = srcRect.top + (fixed ? 0 : window.pageYOffset);
-
-	if (props.anchor.x === 'center') {
-		left = x + (props.src.offsetWidth / 2) - (width / 2);
-	} else if (props.anchor.x === 'left') {
-		// TODO
-	} else if (props.anchor.x === 'right') {
-		left = x + props.src.offsetWidth;
-	}
-
-	if (props.anchor.y === 'center') {
-		top = (y - (height / 2));
-	} else if (props.anchor.y === 'top') {
-		// TODO
-	} else if (props.anchor.y === 'bottom') {
-		top = y + props.src.offsetHeight;
-	}
-
-	if (fixed) {
-		// 画面から横にはみ出る場合
-		if (left + width > window.innerWidth) {
-			left = window.innerWidth - width;
-		}
-
-		const underSpace = (window.innerHeight - MARGIN) - top;
-		const upperSpace = (srcRect.top - MARGIN);
-
-		// 画面から縦にはみ出る場合
-		if (top + height > (window.innerHeight - MARGIN)) {
-			if (props.noOverlap && props.anchor.x === 'center') {
-				if (underSpace >= (upperSpace / 3)) {
-					maxHeight = underSpace;
-				} else {
-					maxHeight = upperSpace;
-					top = (upperSpace + MARGIN) - height;
-				}
-			} else {
-				top = (window.innerHeight - MARGIN) - height;
-			}
-		} else {
-			maxHeight = underSpace;
-		}
-	} else {
-		// 画面から横にはみ出る場合
-		if (left + width - window.pageXOffset > window.innerWidth) {
-			left = window.innerWidth - width + window.pageXOffset - 1;
-		}
-
-		const underSpace = (window.innerHeight - MARGIN) - (top - window.pageYOffset);
-		const upperSpace = (srcRect.top - MARGIN);
-
-		// 画面から縦にはみ出る場合
-		if (top + height - window.pageYOffset > (window.innerHeight - MARGIN)) {
-			if (props.noOverlap && props.anchor.x === 'center') {
-				if (underSpace >= (upperSpace / 3)) {
-					maxHeight = underSpace;
-				} else {
-					maxHeight = upperSpace;
-					top = window.pageYOffset + ((upperSpace + MARGIN) - height);
-				}
-			} else {
-				top = (window.innerHeight - MARGIN) - height + window.pageYOffset - 1;
-			}
-		} else {
-			maxHeight = underSpace;
-		}
-	}
-
-	if (top < 0) {
-		top = MARGIN;
-	}
-
-	if (left < 0) {
-		left = 0;
-	}
-
-	let transformOriginX = 'center';
-	let transformOriginY = 'center';
-
-	if (top >= srcRect.top + props.src.offsetHeight + (fixed ? 0 : window.pageYOffset)) {
-		transformOriginY = 'top';
-	} else if ((top + height) <= srcRect.top + (fixed ? 0 : window.pageYOffset)) {
-		transformOriginY = 'bottom';
-	}
-
-	if (left >= srcRect.left + props.src.offsetWidth + (fixed ? 0 : window.pageXOffset)) {
-		transformOriginX = 'left';
-	} else if ((left + width) <= srcRect.left + (fixed ? 0 : window.pageXOffset)) {
-		transformOriginX = 'right';
-	}
-
-	transformOrigin = `${transformOriginX} ${transformOriginY}`;
-
-	content.style.left = left + 'px';
-	content.style.top = top + 'px';
-};
-
-const onOpened = () => {
-	emit('opened');
-
-	// モーダルコンテンツにマウスボタンが押され、コンテンツ外でマウスボタンが離されたときにモーダルバックグラウンドクリックと判定させないためにマウスイベントを監視しフラグ管理する
-	const el = content!.children[0];
-	el.addEventListener('mousedown', ev => {
-		contentClicking = true;
-		window.addEventListener('mouseup', ev => {
-			// click イベントより先に mouseup イベントが発生するかもしれないのでちょっと待つ
-			window.setTimeout(() => {
-				contentClicking = false;
-			}, 100);
-		}, { passive: true, once: true });
-	}, { passive: true });
-};
-
-onMounted(() => {
-	watch(() => props.src, async () => {
-		if (props.src) {
-			// eslint-disable-next-line vue/no-mutating-props
-			props.src.style.pointerEvents = 'none';
-		}
-		fixed = (type === 'drawer') || (getFixedContainer(props.src) != null);
-
-		await nextTick();
-		
-		align();
-	}, { immediate: true });
-
-	nextTick(() => {
-		new ResizeObserver((entries, observer) => {
-			align();
-		}).observe(content!);
-	});
-});
-
-defineExpose({
-	close,
-});
-</script>
-
-<style lang="scss" scoped>
-.modal-enter-active, .modal-leave-active {
-	> .bg {
-		transition: opacity 0.2s !important;
-	}
-
-	> .content {
-		transform-origin: var(--transformOrigin);
-		transition: opacity 0.2s, transform 0.2s !important;
-	}
-}
-.modal-enter-from, .modal-leave-to {
-	> .bg {
-		opacity: 0;
-	}
-
-	> .content {
-		pointer-events: none;
-		opacity: 0;
-		transform-origin: var(--transformOrigin);
-		transform: scale(0.9);
-	}
-}
-
-.modal-popup-enter-active, .modal-popup-leave-active {
-	> .bg {
-		transition: opacity 0.2s !important;
-	}
-
-	> .content {
-		transform-origin: var(--transformOrigin);
-		transition: opacity 0.2s cubic-bezier(0, 0, 0.2, 1), transform 0.2s cubic-bezier(0, 0, 0.2, 1) !important;
-	}
-}
-.modal-popup-enter-from, .modal-popup-leave-to {
-	> .bg {
-		opacity: 0;
-	}
-
-	> .content {
-		pointer-events: none;
-		opacity: 0;
-		transform-origin: var(--transformOrigin);
-		transform: scale(0.9);
-	}
-}
-
-.modal-drawer-enter-active {
-	> .bg {
-		transition: opacity 0.2s !important;
-	}
-
-	> .content {
-		transition: transform 0.2s cubic-bezier(0,.5,0,1) !important;
-	}
-}
-.modal-drawer-leave-active {
-	> .bg {
-		transition: opacity 0.2s !important;
-	}
-
-	> .content {
-		transition: transform 0.2s cubic-bezier(0,.5,0,1) !important;
-	}
-}
-.modal-drawer-enter-from, .modal-drawer-leave-to {
-	> .bg {
-		opacity: 0;
-	}
-
-	> .content {
-		pointer-events: none;
-		transform: translateY(100%);
-	}
-}
-
-.qzhlnise {
-	> .bg {
-		&.transparent {
-			background: transparent;
-			-webkit-backdrop-filter: none;
-			backdrop-filter: none;
-		}
-	}
-
-	&.dialog {
-		> .content {
-			position: fixed;
-			top: 0;
-			bottom: 0;
-			left: 0;
-			right: 0;
-			margin: auto;
-			padding: 32px;
-			// TODO: mask-imageはiOSだとやたら重い。なんとかしたい
-			-webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 32px, rgba(0,0,0,1) calc(100% - 32px), rgba(0,0,0,0) 100%);
-			mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 32px, rgba(0,0,0,1) calc(100% - 32px), rgba(0,0,0,0) 100%);
-			overflow: auto;
-			display: flex;
-
-			@media (max-width: 500px) {
-				padding: 16px;
-				-webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 16px, rgba(0,0,0,1) calc(100% - 16px), rgba(0,0,0,0) 100%);
-				mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 16px, rgba(0,0,0,1) calc(100% - 16px), rgba(0,0,0,0) 100%);
-			}
-
-			> ::v-deep(*) {
-				margin: auto;
-			}
-
-			&.top {
-				> ::v-deep(*) {
-					margin-top: 0;
-				}
-			}
-		}
-	}
-
-	&.popup {
-		> .content {
-			position: absolute;
-
-			&.fixed {
-				position: fixed;
-			}
-		}
-	}
-
-	&.drawer {
-		position: fixed;
-		top: 0;
-		left: 0;
-		width: 100%;
-		height: 100%;
-		overflow: clip;
-
-		> .content {
-			position: fixed;
-			bottom: 0;
-			left: 0;
-			right: 0;
-			margin: auto;
-
-			> ::v-deep(*) {
-				margin: auto;
-			}
-		}
-	}
-
-}
-</style>
diff --git a/packages/client/src/components/ui/pagination.vue b/packages/client/src/components/ui/pagination.vue
deleted file mode 100644
index 7650c5b33a..0000000000
--- a/packages/client/src/components/ui/pagination.vue
+++ /dev/null
@@ -1,317 +0,0 @@
-<template>
-<transition :name="$store.state.animation ? 'fade' : ''" mode="out-in">
-	<MkLoading v-if="fetching"/>
-
-	<MkError v-else-if="error" @retry="init()"/>
-
-	<div v-else-if="empty" key="_empty_" class="empty">
-		<slot name="empty">
-			<div class="_fullinfo">
-				<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
-				<div>{{ i18n.ts.nothing }}</div>
-			</div>
-		</slot>
-	</div>
-
-	<div v-else ref="rootEl">
-		<div v-show="pagination.reversed && more" key="_more_" class="cxiknjgy _gap">
-			<MkButton v-if="!moreFetching" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMoreAhead">
-				{{ i18n.ts.loadMore }}
-			</MkButton>
-			<MkLoading v-else class="loading"/>
-		</div>
-		<slot :items="items"></slot>
-		<div v-show="!pagination.reversed && more" key="_more_" class="cxiknjgy _gap">
-			<MkButton v-if="!moreFetching" v-appear="($store.state.enableInfiniteScroll && !disableAutoLoad) ? fetchMore : null" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMore">
-				{{ i18n.ts.loadMore }}
-			</MkButton>
-			<MkLoading v-else class="loading"/>
-		</div>
-	</div>
-</transition>
-</template>
-
-<script lang="ts" setup>
-import { computed, ComputedRef, isRef, markRaw, onActivated, onDeactivated, Ref, ref, watch } from 'vue';
-import * as misskey from 'misskey-js';
-import * as os from '@/os';
-import { onScrollTop, isTopVisible, getScrollPosition, getScrollContainer } from '@/scripts/scroll';
-import MkButton from '@/components/ui/button.vue';
-import { i18n } from '@/i18n';
-
-const SECOND_FETCH_LIMIT = 30;
-
-export type Paging<E extends keyof misskey.Endpoints = keyof misskey.Endpoints> = {
-	endpoint: E;
-	limit: number;
-	params?: misskey.Endpoints[E]['req'] | ComputedRef<misskey.Endpoints[E]['req']>;
-
-	/**
-	 * 検索APIのような、ページング不可なエンドポイントを利用する場合
-	 * (そのようなAPIをこの関数で使うのは若干矛盾してるけど)
-	 */
-	noPaging?: boolean;
-
-	/**
-	 * items 配列の中身を逆順にする(新しい方が最後)
-	 */
-	reversed?: boolean;
-
-	offsetMode?: boolean;
-};
-
-const props = withDefaults(defineProps<{
-	pagination: Paging;
-	disableAutoLoad?: boolean;
-	displayLimit?: number;
-}>(), {
-	displayLimit: 30,
-});
-
-const emit = defineEmits<{
-	(ev: 'queue', count: number): void;
-}>();
-
-type Item = { id: string; [another: string]: unknown; };
-
-const rootEl = ref<HTMLElement>();
-const items = ref<Item[]>([]);
-const queue = ref<Item[]>([]);
-const offset = ref(0);
-const fetching = ref(true);
-const moreFetching = ref(false);
-const more = ref(false);
-const backed = ref(false); // 遡り中か否か
-const isBackTop = ref(false);
-const empty = computed(() => items.value.length === 0);
-const error = ref(false);
-
-const init = async (): Promise<void> => {
-	queue.value = [];
-	fetching.value = true;
-	const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {};
-	await os.api(props.pagination.endpoint, {
-		...params,
-		limit: props.pagination.noPaging ? (props.pagination.limit || 10) : (props.pagination.limit || 10) + 1,
-	}).then(res => {
-		for (let i = 0; i < res.length; i++) {
-			const item = res[i];
-			if (props.pagination.reversed) {
-				if (i === res.length - 2) item._shouldInsertAd_ = true;
-			} else {
-				if (i === 3) item._shouldInsertAd_ = true;
-			}
-		}
-		if (!props.pagination.noPaging && (res.length > (props.pagination.limit || 10))) {
-			res.pop();
-			items.value = props.pagination.reversed ? [...res].reverse() : res;
-			more.value = true;
-		} else {
-			items.value = props.pagination.reversed ? [...res].reverse() : res;
-			more.value = false;
-		}
-		offset.value = res.length;
-		error.value = false;
-		fetching.value = false;
-	}, err => {
-		error.value = true;
-		fetching.value = false;
-	});
-};
-
-const reload = (): void => {
-	items.value = [];
-	init();
-};
-
-const fetchMore = async (): Promise<void> => {
-	if (!more.value || fetching.value || moreFetching.value || items.value.length === 0) return;
-	moreFetching.value = true;
-	backed.value = true;
-	const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {};
-	await os.api(props.pagination.endpoint, {
-		...params,
-		limit: SECOND_FETCH_LIMIT + 1,
-		...(props.pagination.offsetMode ? {
-			offset: offset.value,
-		} : props.pagination.reversed ? {
-			sinceId: items.value[0].id,
-		} : {
-			untilId: items.value[items.value.length - 1].id,
-		}),
-	}).then(res => {
-		for (let i = 0; i < res.length; i++) {
-			const item = res[i];
-			if (props.pagination.reversed) {
-				if (i === res.length - 9) item._shouldInsertAd_ = true;
-			} else {
-				if (i === 10) item._shouldInsertAd_ = true;
-			}
-		}
-		if (res.length > SECOND_FETCH_LIMIT) {
-			res.pop();
-			items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res);
-			more.value = true;
-		} else {
-			items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res);
-			more.value = false;
-		}
-		offset.value += res.length;
-		moreFetching.value = false;
-	}, err => {
-		moreFetching.value = false;
-	});
-};
-
-const fetchMoreAhead = async (): Promise<void> => {
-	if (!more.value || fetching.value || moreFetching.value || items.value.length === 0) return;
-	moreFetching.value = true;
-	const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {};
-	await os.api(props.pagination.endpoint, {
-		...params,
-		limit: SECOND_FETCH_LIMIT + 1,
-		...(props.pagination.offsetMode ? {
-			offset: offset.value,
-		} : props.pagination.reversed ? {
-			untilId: items.value[0].id,
-		} : {
-			sinceId: items.value[items.value.length - 1].id,
-		}),
-	}).then(res => {
-		if (res.length > SECOND_FETCH_LIMIT) {
-			res.pop();
-			items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res);
-			more.value = true;
-		} else {
-			items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res);
-			more.value = false;
-		}
-		offset.value += res.length;
-		moreFetching.value = false;
-	}, err => {
-		moreFetching.value = false;
-	});
-};
-
-const prepend = (item: Item): void => {
-	if (props.pagination.reversed) {
-		if (rootEl.value) {
-			const container = getScrollContainer(rootEl.value);
-			if (container == null) {
-				// TODO?
-			} else {
-				const pos = getScrollPosition(rootEl.value);
-				const viewHeight = container.clientHeight;
-				const height = container.scrollHeight;
-				const isBottom = (pos + viewHeight > height - 32);
-				if (isBottom) {
-					// オーバーフローしたら古いアイテムは捨てる
-					if (items.value.length >= props.displayLimit) {
-						// このやり方だとVue 3.2以降アニメーションが動かなくなる
-						//items.value = items.value.slice(-props.displayLimit);
-						while (items.value.length >= props.displayLimit) {
-							items.value.shift();
-						}
-						more.value = true;
-					}
-				}
-			}
-		}
-		items.value.push(item);
-		// TODO
-	} else {
-		// 初回表示時はunshiftだけでOK
-		if (!rootEl.value) {
-			items.value.unshift(item);
-			return;
-		}
-
-		const isTop = isBackTop.value || (document.body.contains(rootEl.value) && isTopVisible(rootEl.value));
-
-		if (isTop) {
-			// Prepend the item
-			items.value.unshift(item);
-
-			// オーバーフローしたら古いアイテムは捨てる
-			if (items.value.length >= props.displayLimit) {
-				// このやり方だとVue 3.2以降アニメーションが動かなくなる
-				//this.items = items.value.slice(0, props.displayLimit);
-				while (items.value.length >= props.displayLimit) {
-					items.value.pop();
-				}
-				more.value = true;
-			}
-		} else {
-			queue.value.push(item);
-			onScrollTop(rootEl.value, () => {
-				for (const item of queue.value) {
-					prepend(item);
-				}
-				queue.value = [];
-			});
-		}
-	}
-};
-
-const append = (item: Item): void => {
-	items.value.push(item);
-};
-
-const removeItem = (finder: (item: Item) => boolean) => {
-	const i = items.value.findIndex(finder);
-	items.value.splice(i, 1);
-};
-
-const updateItem = (id: Item['id'], replacer: (old: Item) => Item): void => {
-	const i = items.value.findIndex(item => item.id === id);
-	items.value[i] = replacer(items.value[i]);
-};
-
-if (props.pagination.params && isRef(props.pagination.params)) {
-	watch(props.pagination.params, init, { deep: true });
-}
-
-watch(queue, (a, b) => {
-	if (a.length === 0 && b.length === 0) return;
-	emit('queue', queue.value.length);
-}, { deep: true });
-
-init();
-
-onActivated(() => {
-	isBackTop.value = false;
-});
-
-onDeactivated(() => {
-	isBackTop.value = window.scrollY === 0;
-});
-
-defineExpose({
-	items,
-	queue,
-	backed,
-	reload,
-	prepend,
-	append,
-	removeItem,
-	updateItem,
-});
-</script>
-
-<style lang="scss" scoped>
-.fade-enter-active,
-.fade-leave-active {
-	transition: opacity 0.125s ease;
-}
-.fade-enter-from,
-.fade-leave-to {
-	opacity: 0;
-}
-
-.cxiknjgy {
-	> .button {
-		margin-left: auto;
-		margin-right: auto;
-	}
-}
-</style>
diff --git a/packages/client/src/components/ui/popup-menu.vue b/packages/client/src/components/ui/popup-menu.vue
deleted file mode 100644
index c29aff45e7..0000000000
--- a/packages/client/src/components/ui/popup-menu.vue
+++ /dev/null
@@ -1,36 +0,0 @@
-<template>
-<MkModal ref="modal" v-slot="{ type, maxHeight }" :z-priority="'high'" :src="src" :transparent-bg="true" @click="modal.close()" @closed="emit('closed')">
-	<MkMenu :items="items" :align="align" :width="width" :max-height="maxHeight" :as-drawer="type === 'drawer'" class="sfhdhdhq" :class="{ drawer: type === 'drawer' }" @close="modal.close()"/>
-</MkModal>
-</template>
-
-<script lang="ts" setup>
-import { } from 'vue';
-import MkModal from './modal.vue';
-import MkMenu from './menu.vue';
-import { MenuItem } from '@/types/menu';
-
-defineProps<{
-	items: MenuItem[];
-	align?: 'center' | string;
-	width?: number;
-	viaKeyboard?: boolean;
-	src?: any;
-}>();
-
-const emit = defineEmits<{
-	(ev: 'closed'): void;
-}>();
-
-let modal = $ref<InstanceType<typeof MkModal>>();
-</script>
-
-<style lang="scss" scoped>
-.sfhdhdhq {
-	&.drawer {
-		border-radius: 24px;
-		border-bottom-right-radius: 0;
-		border-bottom-left-radius: 0;
-	}
-}
-</style>
diff --git a/packages/client/src/components/ui/super-menu.vue b/packages/client/src/components/ui/super-menu.vue
deleted file mode 100644
index 8ce2dc5dc2..0000000000
--- a/packages/client/src/components/ui/super-menu.vue
+++ /dev/null
@@ -1,148 +0,0 @@
-<template>
-<div class="rrevdjwu" :class="{ grid }">
-	<div v-for="group in def" class="group">
-		<div v-if="group.title" class="title">{{ group.title }}</div>
-
-		<div class="items">
-			<template v-for="(item, i) in group.items">
-				<a v-if="item.type === 'a'" :href="item.href" :target="item.target" :tabindex="i" class="_button item" :class="{ danger: item.danger, active: item.active }">
-					<i v-if="item.icon" class="icon fa-fw" :class="item.icon"></i>
-					<span class="text">{{ item.text }}</span>
-				</a>
-				<button v-else-if="item.type === 'button'" :tabindex="i" class="_button item" :class="{ danger: item.danger, active: item.active }" :disabled="item.active" @click="ev => item.action(ev)">
-					<i v-if="item.icon" class="icon fa-fw" :class="item.icon"></i>
-					<span class="text">{{ item.text }}</span>
-				</button>
-				<MkA v-else :to="item.to" :tabindex="i" class="_button item" :class="{ danger: item.danger, active: item.active }">
-					<i v-if="item.icon" class="icon fa-fw" :class="item.icon"></i>
-					<span class="text">{{ item.text }}</span>
-				</MkA>
-			</template>
-		</div>
-	</div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent, ref, unref } from 'vue';
-
-export default defineComponent({
-	props: {
-		def: {
-			type: Array,
-			required: true,
-		},
-		grid: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-	},
-});
-</script>
-
-<style lang="scss" scoped>
-.rrevdjwu {
-	> .group {
-		& + .group {
-			margin-top: 16px;
-			padding-top: 16px;
-			border-top: solid 0.5px var(--divider);
-		}
-
-		> .title {
-			opacity: 0.7;
-			margin: 0 0 8px 0;
-			font-size: 0.9em;
-		}
-	
-		> .items {
-			> .item {
-				display: flex;
-				align-items: center;
-				width: 100%;
-				box-sizing: border-box;
-				padding: 10px 16px 10px 8px;
-				border-radius: 9px;
-				font-size: 0.9em;
-
-				&:hover {
-					text-decoration: none;
-					background: var(--panelHighlight);
-				}
-
-				&.active {
-					color: var(--accent);
-					background: var(--accentedBg);
-				}
-
-				&.danger {
-					color: var(--error);
-				}
-
-				> .icon {
-					width: 32px;
-					margin-right: 2px;
-					flex-shrink: 0;
-					text-align: center;
-					opacity: 0.8;
-				}
-
-				> .text {
-					white-space: nowrap;
-					text-overflow: ellipsis;
-					overflow: hidden;
-					padding-right: 12px;
-				}
-
-			}
-		}
-	}
-
-	&.grid {
-		> .group {
-			& + .group {
-				padding-top: 0;
-				border-top: none;
-			}
-
-			margin-left: 0;
-			margin-right: 0;
-
-			> .title {
-				font-size: 1em;
-				opacity: 0.7;
-				margin: 0 0 8px 16px;
-			}
-
-			> .items {
-				display: grid;
-				grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
-				grid-gap: 8px;
-				padding: 0 16px;
-
-				> .item {
-					flex-direction: column;
-					padding: 18px 16px 16px 16px;
-					background: var(--panel);
-					border-radius: 8px;
-					text-align: center;
-
-					> .icon {
-						display: block;
-						margin-right: 0;
-						margin-bottom: 12px;
-						font-size: 1.5em;
-					}
-
-					> .text {
-						padding-right: 0;
-						width: 100%;
-						font-size: 0.8em;
-					}
-				}
-			}
-		}
-	}
-}
-</style>
diff --git a/packages/client/src/components/ui/tooltip.vue b/packages/client/src/components/ui/tooltip.vue
deleted file mode 100644
index 4c6258d245..0000000000
--- a/packages/client/src/components/ui/tooltip.vue
+++ /dev/null
@@ -1,101 +0,0 @@
-<template>
-<transition :name="$store.state.animation ? 'tooltip' : ''" appear @after-leave="emit('closed')">
-	<div v-show="showing" ref="el" class="buebdbiu _acrylic _shadow" :style="{ zIndex, maxWidth: maxWidth + 'px' }">
-		<slot>
-			<Mfm v-if="asMfm" :text="text"/>
-			<span v-else>{{ text }}</span>
-		</slot>
-	</div>
-</transition>
-</template>
-
-<script lang="ts" setup>
-import { nextTick, onMounted, onUnmounted, ref } from 'vue';
-import * as os from '@/os';
-import { calcPopupPosition } from '@/scripts/popup-position';
-
-const props = withDefaults(defineProps<{
-	showing: boolean;
-	targetElement?: HTMLElement;
-	x?: number;
-	y?: number;
-	text?: string;
-	asMfm?: boolean;
-	maxWidth?: number;
-	direction?: 'top' | 'bottom' | 'right' | 'left';
-	innerMargin?: number;
-}>(), {
-	maxWidth: 250,
-	direction: 'top',
-	innerMargin: 0,
-});
-
-const emit = defineEmits<{
-	(ev: 'closed'): void;
-}>();
-
-const el = ref<HTMLElement>();
-const zIndex = os.claimZIndex('high');
-
-function setPosition() {
-	const data = calcPopupPosition(el.value, {
-		anchorElement: props.targetElement,
-		direction: props.direction,
-		align: 'center',
-		innerMargin: props.innerMargin,
-		x: props.x,
-		y: props.y,
-	});
-
-	el.value.style.transformOrigin = data.transformOrigin;
-	el.value.style.left = data.left + 'px';
-	el.value.style.top = data.top + 'px';
-}
-
-let loopHandler;
-
-onMounted(() => {
-	nextTick(() => {
-		setPosition();
-
-		const loop = () => {
-			loopHandler = window.requestAnimationFrame(() => {
-				setPosition();
-				loop();
-			});
-		};
-
-		loop();
-	});
-});
-
-onUnmounted(() => {
-	window.cancelAnimationFrame(loopHandler);
-});
-</script>
-
-<style lang="scss" scoped>
-.tooltip-enter-active,
-.tooltip-leave-active {
-	opacity: 1;
-	transform: scale(1);
-	transition: transform 200ms cubic-bezier(0.23, 1, 0.32, 1), opacity 200ms cubic-bezier(0.23, 1, 0.32, 1);
-}
-.tooltip-enter-from,
-.tooltip-leave-active {
-	opacity: 0;
-	transform: scale(0.75);
-}
-
-.buebdbiu {
-	position: absolute;
-	font-size: 0.8em;
-	padding: 8px 12px;
-	box-sizing: border-box;
-	text-align: center;
-	border-radius: 4px;
-	border: solid 0.5px var(--divider);
-	pointer-events: none;
-	transform-origin: center center;
-}
-</style>
diff --git a/packages/client/src/components/ui/window.vue b/packages/client/src/components/ui/window.vue
deleted file mode 100644
index 758d4d47b6..0000000000
--- a/packages/client/src/components/ui/window.vue
+++ /dev/null
@@ -1,563 +0,0 @@
-<template>
-<transition :name="$store.state.animation ? 'window' : ''" appear @after-leave="$emit('closed')">
-	<div v-if="showing" ref="rootEl" class="ebkgocck" :class="{ maximized }">
-		<div class="body _shadow _narrow_" @mousedown="onBodyMousedown" @keydown="onKeydown">
-			<div class="header" :class="{ mini }" @contextmenu.prevent.stop="onContextmenu">
-				<span class="left">
-					<button v-for="button in buttonsLeft" v-tooltip="button.title" class="button _button" :class="{ highlighted: button.highlighted }" @click="button.onClick"><i :class="button.icon"></i></button>
-				</span>
-				<span class="title" @mousedown.prevent="onHeaderMousedown" @touchstart.prevent="onHeaderMousedown">
-					<slot name="header"></slot>
-				</span>
-				<span class="right">
-					<button v-for="button in buttonsRight" v-tooltip="button.title" class="button _button" :class="{ highlighted: button.highlighted }" @click="button.onClick"><i :class="button.icon"></i></button>
-					<button v-if="canResize && maximized" class="button _button" @click="unMaximize()"><i class="fas fa-window-restore"></i></button>
-					<button v-else-if="canResize && !maximized" class="button _button" @click="maximize()"><i class="fas fa-window-maximize"></i></button>
-					<button v-if="closeButton" class="button _button" @click="close()"><i class="fas fa-times"></i></button>
-				</span>
-			</div>
-			<div class="body">
-				<slot></slot>
-			</div>
-		</div>
-		<template v-if="canResize">
-			<div class="handle top" @mousedown.prevent="onTopHandleMousedown"></div>
-			<div class="handle right" @mousedown.prevent="onRightHandleMousedown"></div>
-			<div class="handle bottom" @mousedown.prevent="onBottomHandleMousedown"></div>
-			<div class="handle left" @mousedown.prevent="onLeftHandleMousedown"></div>
-			<div class="handle top-left" @mousedown.prevent="onTopLeftHandleMousedown"></div>
-			<div class="handle top-right" @mousedown.prevent="onTopRightHandleMousedown"></div>
-			<div class="handle bottom-right" @mousedown.prevent="onBottomRightHandleMousedown"></div>
-			<div class="handle bottom-left" @mousedown.prevent="onBottomLeftHandleMousedown"></div>
-		</template>
-	</div>
-</transition>
-</template>
-
-<script lang="ts" setup>
-import { onBeforeUnmount, onMounted, provide } from 'vue';
-import contains from '@/scripts/contains';
-import * as os from '@/os';
-import { MenuItem } from '@/types/menu';
-
-const minHeight = 50;
-const minWidth = 250;
-
-function dragListen(fn: (ev: MouseEvent) => void) {
-	window.addEventListener('mousemove', fn);
-	window.addEventListener('touchmove', fn);
-	window.addEventListener('mouseleave', dragClear.bind(null, fn));
-	window.addEventListener('mouseup', dragClear.bind(null, fn));
-	window.addEventListener('touchend', dragClear.bind(null, fn));
-}
-
-function dragClear(fn) {
-	window.removeEventListener('mousemove', fn);
-	window.removeEventListener('touchmove', fn);
-	window.removeEventListener('mouseleave', dragClear);
-	window.removeEventListener('mouseup', dragClear);
-	window.removeEventListener('touchend', dragClear);
-}
-
-const props = withDefaults(defineProps<{
-	initialWidth?: number;
-	initialHeight?: number | null;
-	canResize?: boolean;
-	closeButton?: boolean;
-	mini?: boolean;
-	front?: boolean;
-	contextmenu?: MenuItem[] | null;
-	buttonsLeft?: any[];
-	buttonsRight?: any[];
-}>(), {
-	initialWidth: 400,
-	initialHeight: null,
-	canResize: false,
-	closeButton: true,
-	mini: false,
-	front: false,
-	contextmenu: null,
-	buttonsLeft: () => [],
-	buttonsRight: () => [],
-});
-
-const emit = defineEmits<{
-	(ev: 'closed'): void;
-}>();
-
-provide('inWindow', true);
-
-let rootEl = $ref<HTMLElement | null>();
-let showing = $ref(true);
-let beforeClickedAt = 0;
-let maximized = $ref(false);
-let unMaximizedTop = '';
-let unMaximizedLeft = '';
-let unMaximizedWidth = '';
-let unMaximizedHeight = '';
-
-function close() {
-	showing = false;
-}
-
-function onKeydown(evt) {
-	if (evt.which === 27) { // Esc
-		evt.preventDefault();
-		evt.stopPropagation();
-		close();
-	}
-}
-
-function onContextmenu(ev: MouseEvent) {
-	if (props.contextmenu) {
-		os.contextMenu(props.contextmenu, ev);
-	}
-}
-
-// 最前面へ移動
-function top() {
-	if (rootEl) {
-		rootEl.style.zIndex = os.claimZIndex(props.front ? 'middle' : 'low');
-	}
-}
-
-function maximize() {
-	maximized = true;
-	unMaximizedTop = rootEl.style.top;
-	unMaximizedLeft = rootEl.style.left;
-	unMaximizedWidth = rootEl.style.width;
-	unMaximizedHeight = rootEl.style.height;
-	rootEl.style.top = '0';
-	rootEl.style.left = '0';
-	rootEl.style.width = '100%';
-	rootEl.style.height = '100%';
-}
-
-function unMaximize() {
-	maximized = false;
-	rootEl.style.top = unMaximizedTop;
-	rootEl.style.left = unMaximizedLeft;
-	rootEl.style.width = unMaximizedWidth;
-	rootEl.style.height = unMaximizedHeight;
-}
-
-function onBodyMousedown() {
-	top();
-}
-
-function onDblClick() {
-	maximize();
-}
-
-function onHeaderMousedown(evt: MouseEvent) {
-	// 右クリックはコンテキストメニューを開こうとした可能性が高いため無視
-	if (evt.button === 2) return;
-
-	let beforeMaximized = false;
-
-	if (maximized) {
-		beforeMaximized = true;
-		unMaximize();
-	}
-
-	// ダブルクリック判定
-	if (Date.now() - beforeClickedAt < 300) {
-		beforeClickedAt = Date.now();
-		onDblClick();
-		return;
-	}
-
-	beforeClickedAt = Date.now();
-
-	const main = rootEl;
-	if (main == null) return;
-
-	if (!contains(main, document.activeElement)) main.focus();
-
-	const position = main.getBoundingClientRect();
-
-	const clickX = evt.touches && evt.touches.length > 0 ? evt.touches[0].clientX : evt.clientX;
-	const clickY = evt.touches && evt.touches.length > 0 ? evt.touches[0].clientY : evt.clientY;
-	const moveBaseX = beforeMaximized ? parseInt(unMaximizedWidth, 10) / 2 : clickX - position.left; // TODO: parseIntやめる
-	const moveBaseY = beforeMaximized ? 20 : clickY - position.top;
-	const browserWidth = window.innerWidth;
-	const browserHeight = window.innerHeight;
-	const windowWidth = main.offsetWidth;
-	const windowHeight = main.offsetHeight;
-
-	function move(x: number, y: number) {
-		let moveLeft = x - moveBaseX;
-		let moveTop = y - moveBaseY;
-
-		// 下はみ出し
-		if (moveTop + windowHeight > browserHeight) moveTop = browserHeight - windowHeight;
-
-		// 左はみ出し
-		if (moveLeft < 0) moveLeft = 0;
-
-		// 上はみ出し
-		if (moveTop < 0) moveTop = 0;
-
-		// 右はみ出し
-		if (moveLeft + windowWidth > browserWidth) moveLeft = browserWidth - windowWidth;
-
-		rootEl.style.left = moveLeft + 'px';
-		rootEl.style.top = moveTop + 'px';
-	}
-
-	if (beforeMaximized) {
-		move(clickX, clickY);
-	}
-
-	// 動かした時
-	dragListen(me => {
-		const x = me.touches && me.touches.length > 0 ? me.touches[0].clientX : me.clientX;
-		const y = me.touches && me.touches.length > 0 ? me.touches[0].clientY : me.clientY;
-
-		move(x, y);
-	});
-}
-
-// 上ハンドル掴み時
-function onTopHandleMousedown(evt) {
-	const main = rootEl;
-
-	const base = evt.clientY;
-	const height = parseInt(getComputedStyle(main, '').height, 10);
-	const top = parseInt(getComputedStyle(main, '').top, 10);
-
-	// 動かした時
-	dragListen(me => {
-		const move = me.clientY - base;
-		if (top + move > 0) {
-			if (height + -move > minHeight) {
-				applyTransformHeight(height + -move);
-				applyTransformTop(top + move);
-			} else { // 最小の高さより小さくなろうとした時
-				applyTransformHeight(minHeight);
-				applyTransformTop(top + (height - minHeight));
-			}
-		} else { // 上のはみ出し時
-			applyTransformHeight(top + height);
-			applyTransformTop(0);
-		}
-	});
-}
-
-// 右ハンドル掴み時
-function onRightHandleMousedown(evt) {
-	const main = rootEl;
-
-	const base = evt.clientX;
-	const width = parseInt(getComputedStyle(main, '').width, 10);
-	const left = parseInt(getComputedStyle(main, '').left, 10);
-	const browserWidth = window.innerWidth;
-
-	// 動かした時
-	dragListen(me => {
-		const move = me.clientX - base;
-		if (left + width + move < browserWidth) {
-			if (width + move > minWidth) {
-				applyTransformWidth(width + move);
-			} else { // 最小の幅より小さくなろうとした時
-				applyTransformWidth(minWidth);
-			}
-		} else { // 右のはみ出し時
-			applyTransformWidth(browserWidth - left);
-		}
-	});
-}
-
-// 下ハンドル掴み時
-function onBottomHandleMousedown(evt) {
-	const main = rootEl;
-
-	const base = evt.clientY;
-	const height = parseInt(getComputedStyle(main, '').height, 10);
-	const top = parseInt(getComputedStyle(main, '').top, 10);
-	const browserHeight = window.innerHeight;
-
-	// 動かした時
-	dragListen(me => {
-		const move = me.clientY - base;
-		if (top + height + move < browserHeight) {
-			if (height + move > minHeight) {
-				applyTransformHeight(height + move);
-			} else { // 最小の高さより小さくなろうとした時
-				applyTransformHeight(minHeight);
-			}
-		} else { // 下のはみ出し時
-			applyTransformHeight(browserHeight - top);
-		}
-	});
-}
-
-// 左ハンドル掴み時
-function onLeftHandleMousedown(evt) {
-	const main = rootEl;
-
-	const base = evt.clientX;
-	const width = parseInt(getComputedStyle(main, '').width, 10);
-	const left = parseInt(getComputedStyle(main, '').left, 10);
-
-	// 動かした時
-	dragListen(me => {
-		const move = me.clientX - base;
-		if (left + move > 0) {
-			if (width + -move > minWidth) {
-				applyTransformWidth(width + -move);
-				applyTransformLeft(left + move);
-			} else { // 最小の幅より小さくなろうとした時
-				applyTransformWidth(minWidth);
-				applyTransformLeft(left + (width - minWidth));
-			}
-		} else { // 左のはみ出し時
-			applyTransformWidth(left + width);
-			applyTransformLeft(0);
-		}
-	});
-}
-
-// 左上ハンドル掴み時
-function onTopLeftHandleMousedown(evt) {
-	onTopHandleMousedown(evt);
-	onLeftHandleMousedown(evt);
-}
-
-// 右上ハンドル掴み時
-function onTopRightHandleMousedown(evt) {
-	onTopHandleMousedown(evt);
-	onRightHandleMousedown(evt);
-}
-
-// 右下ハンドル掴み時
-function onBottomRightHandleMousedown(evt) {
-	onBottomHandleMousedown(evt);
-	onRightHandleMousedown(evt);
-}
-
-// 左下ハンドル掴み時
-function onBottomLeftHandleMousedown(evt) {
-	onBottomHandleMousedown(evt);
-	onLeftHandleMousedown(evt);
-}
-
-// 高さを適用
-function applyTransformHeight(height) {
-	if (height > window.innerHeight) height = window.innerHeight;
-	rootEl.style.height = height + 'px';
-}
-
-// 幅を適用
-function applyTransformWidth(width) {
-	if (width > window.innerWidth) width = window.innerWidth;
-	rootEl.style.width = width + 'px';
-}
-
-// Y座標を適用
-function applyTransformTop(top) {
-	rootEl.style.top = top + 'px';
-}
-
-// X座標を適用
-function applyTransformLeft(left) {
-	rootEl.style.left = left + 'px';
-}
-
-function onBrowserResize() {
-	const main = rootEl;
-	const position = main.getBoundingClientRect();
-	const browserWidth = window.innerWidth;
-	const browserHeight = window.innerHeight;
-	const windowWidth = main.offsetWidth;
-	const windowHeight = main.offsetHeight;
-	if (position.left < 0) main.style.left = '0'; // 左はみ出し
-	if (position.top + windowHeight > browserHeight) main.style.top = browserHeight - windowHeight + 'px'; // 下はみ出し
-	if (position.left + windowWidth > browserWidth) main.style.left = browserWidth - windowWidth + 'px'; // 右はみ出し
-	if (position.top < 0) main.style.top = '0'; // 上はみ出し
-}
-
-onMounted(() => {
-	if (props.initialWidth) applyTransformWidth(props.initialWidth);
-	if (props.initialHeight) applyTransformHeight(props.initialHeight);
-
-	applyTransformTop((window.innerHeight / 2) - (rootEl.offsetHeight / 2));
-	applyTransformLeft((window.innerWidth / 2) - (rootEl.offsetWidth / 2));
-
-	// 他のウィンドウ内のボタンなどを押してこのウィンドウが開かれた場合、親が最前面になろうとするのでそれに隠されないようにする
-	top();
-
-	window.addEventListener('resize', onBrowserResize);
-});
-
-onBeforeUnmount(() => {
-	window.removeEventListener('resize', onBrowserResize);
-});
-
-defineExpose({
-	close,
-});
-</script>
-
-<style lang="scss" scoped>
-.window-enter-active, .window-leave-active {
-	transition: opacity 0.2s, transform 0.2s !important;
-}
-.window-enter-from, .window-leave-to {
-	pointer-events: none;
-	opacity: 0;
-	transform: scale(0.9);
-}
-
-.ebkgocck {
-	position: fixed;
-	top: 0;
-	left: 0;
-
-	> .body {
-		overflow: clip;
-		display: flex;
-		flex-direction: column;
-		contain: content;
-		width: 100%;
-		height: 100%;
-		border-radius: var(--radius);
-
-		> .header {
-			--height: 42px;
-
-			&.mini {
-				--height: 38px;
-			}
-
-			display: flex;
-			position: relative;
-			z-index: 1;
-			flex-shrink: 0;
-			user-select: none;
-			height: var(--height);
-			background: var(--windowHeader);
-			-webkit-backdrop-filter: var(--blur, blur(15px));
-			backdrop-filter: var(--blur, blur(15px));
-			//border-bottom: solid 1px var(--divider);
-			font-size: 95%;
-			font-weight: bold;
-
-			> .left, > .right {
-				> .button {
-					height: var(--height);
-					width: var(--height);
-
-					&:hover {
-						color: var(--fgHighlighted);
-					}
-
-					&.highlighted {
-						color: var(--accent);
-					}
-				}
-			}
-
-			> .left {
-				margin-right: 16px;
-			}
-
-			> .right {
-				min-width: 16px;
-			}
-
-			> .title {
-				flex: 1;
-				position: relative;
-				line-height: var(--height);
-				white-space: nowrap;
-				overflow: hidden;
-				text-overflow: ellipsis;
-				cursor: move;
-			}
-		}
-
-		> .body {
-			flex: 1;
-			overflow: auto;
-			background: var(--panel);
-		}
-	}
-
-	> .handle {
-		$size: 8px;
-
-		position: absolute;
-
-		&.top {
-			top: -($size);
-			left: 0;
-			width: 100%;
-			height: $size;
-			cursor: ns-resize;
-		}
-
-		&.right {
-			top: 0;
-			right: -($size);
-			width: $size;
-			height: 100%;
-			cursor: ew-resize;
-		}
-
-		&.bottom {
-			bottom: -($size);
-			left: 0;
-			width: 100%;
-			height: $size;
-			cursor: ns-resize;
-		}
-
-		&.left {
-			top: 0;
-			left: -($size);
-			width: $size;
-			height: 100%;
-			cursor: ew-resize;
-		}
-
-		&.top-left {
-			top: -($size);
-			left: -($size);
-			width: $size * 2;
-			height: $size * 2;
-			cursor: nwse-resize;
-		}
-
-		&.top-right {
-			top: -($size);
-			right: -($size);
-			width: $size * 2;
-			height: $size * 2;
-			cursor: nesw-resize;
-		}
-
-		&.bottom-right {
-			bottom: -($size);
-			right: -($size);
-			width: $size * 2;
-			height: $size * 2;
-			cursor: nwse-resize;
-		}
-
-		&.bottom-left {
-			bottom: -($size);
-			left: -($size);
-			width: $size * 2;
-			height: $size * 2;
-			cursor: nesw-resize;
-		}
-	}
-
-	&.maximized {
-		> .body {
-			border-radius: 0;
-		}
-	}
-}
-</style>
diff --git a/packages/client/src/directives/tooltip.ts b/packages/client/src/directives/tooltip.ts
index 8b68771cad..5d13497b5f 100644
--- a/packages/client/src/directives/tooltip.ts
+++ b/packages/client/src/directives/tooltip.ts
@@ -46,7 +46,7 @@ export default {
 			if (self.text == null) return;
 
 			const showing = ref(true);
-			popup(defineAsyncComponent(() => import('@/components/ui/tooltip.vue')), {
+			popup(defineAsyncComponent(() => import('@/components/MkTooltip.vue')), {
 				showing,
 				text: self.text,
 				asMfm: binding.modifiers.mfm,
diff --git a/packages/client/src/os.ts b/packages/client/src/os.ts
index e014eb529e..515fc47819 100644
--- a/packages/client/src/os.ts
+++ b/packages/client/src/os.ts
@@ -514,7 +514,7 @@ export function popupMenu(items: MenuItem[] | Ref<MenuItem[]>, src?: HTMLElement
 }) {
 	return new Promise((resolve, reject) => {
 		let dispose;
-		popup(defineAsyncComponent(() => import('@/components/ui/popup-menu.vue')), {
+		popup(defineAsyncComponent(() => import('@/components/MkPopupMenu.vue')), {
 			items,
 			src,
 			width: options?.width,
@@ -535,7 +535,7 @@ export function contextMenu(items: MenuItem[] | Ref<MenuItem[]>, ev: MouseEvent)
 	ev.preventDefault();
 	return new Promise((resolve, reject) => {
 		let dispose;
-		popup(defineAsyncComponent(() => import('@/components/ui/context-menu.vue')), {
+		popup(defineAsyncComponent(() => import('@/components/MkContextMenu.vue')), {
 			items,
 			ev,
 		}, {
diff --git a/packages/client/src/pages/_error_.vue b/packages/client/src/pages/_error_.vue
index 6ac1f4297a..a90a023cb6 100644
--- a/packages/client/src/pages/_error_.vue
+++ b/packages/client/src/pages/_error_.vue
@@ -20,7 +20,7 @@
 <script lang="ts" setup>
 import { } from 'vue';
 import * as misskey from 'misskey-js';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import { version } from '@/config';
 import * as os from '@/os';
 import { unisonReload } from '@/scripts/unison-reload';
diff --git a/packages/client/src/pages/about-misskey.vue b/packages/client/src/pages/about-misskey.vue
index 06c0161160..7bcccea98f 100644
--- a/packages/client/src/pages/about-misskey.vue
+++ b/packages/client/src/pages/about-misskey.vue
@@ -67,7 +67,7 @@ import { nextTick, onBeforeUnmount } from 'vue';
 import { version } from '@/config';
 import FormLink from '@/components/form/link.vue';
 import FormSection from '@/components/form/section.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import MkLink from '@/components/MkLink.vue';
 import { physics } from '@/scripts/physics';
 import { i18n } from '@/i18n';
diff --git a/packages/client/src/pages/about.emojis.vue b/packages/client/src/pages/about.emojis.vue
index 8049975da3..df64378c01 100644
--- a/packages/client/src/pages/about.emojis.vue
+++ b/packages/client/src/pages/about.emojis.vue
@@ -30,14 +30,14 @@
 
 <script lang="ts">
 import { defineComponent, computed } from 'vue';
-import MkButton from '@/components/ui/button.vue';
+import XEmoji from './emojis.emoji.vue';
+import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/form/input.vue';
 import MkSelect from '@/components/form/select.vue';
-import MkFolder from '@/components/ui/folder.vue';
+import MkFolder from '@/components/MkFolder.vue';
 import MkTab from '@/components/MkTab.vue';
 import * as os from '@/os';
 import { emojiCategories, emojiTags } from '@/instance';
-import XEmoji from './emojis.emoji.vue';
 
 export default defineComponent({
 	components: {
@@ -66,7 +66,7 @@ export default defineComponent({
 			handler() {
 				this.search();
 			},
-			deep: true
+			deep: true,
 		},
 	},
 
@@ -90,8 +90,8 @@ export default defineComponent({
 			} else {
 				this.selectedTags.add(tag);
 			}
-		}
-	}
+		},
+	},
 });
 </script>
 
diff --git a/packages/client/src/pages/about.federation.vue b/packages/client/src/pages/about.federation.vue
index 51a49ba59f..c501a77582 100644
--- a/packages/client/src/pages/about.federation.vue
+++ b/packages/client/src/pages/about.federation.vue
@@ -48,10 +48,10 @@
 
 <script lang="ts" setup>
 import { computed } from 'vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/form/input.vue';
 import MkSelect from '@/components/form/select.vue';
-import MkPagination from '@/components/ui/pagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
 import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue';
 import FormSplit from '@/components/form/split.vue';
 import * as os from '@/os';
diff --git a/packages/client/src/pages/admin-file.vue b/packages/client/src/pages/admin-file.vue
index dfdbb1f0da..a62e0f630f 100644
--- a/packages/client/src/pages/admin-file.vue
+++ b/packages/client/src/pages/admin-file.vue
@@ -63,14 +63,14 @@
 
 <script lang="ts" setup>
 import { computed } from 'vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import MkSwitch from '@/components/form/switch.vue';
 import MkObjectView from '@/components/MkObjectView.vue';
 import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
 import MkKeyValue from '@/components/MkKeyValue.vue';
 import FormSection from '@/components/form/section.vue';
 import MkUserCardMini from '@/components/MkUserCardMini.vue';
-import MkInfo from '@/components/ui/info.vue';
+import MkInfo from '@/components/MkInfo.vue';
 import bytes from '@/filters/bytes';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
diff --git a/packages/client/src/pages/admin/_header_.vue b/packages/client/src/pages/admin/_header_.vue
index b2c33fdc95..bdb41b2d2c 100644
--- a/packages/client/src/pages/admin/_header_.vue
+++ b/packages/client/src/pages/admin/_header_.vue
@@ -33,7 +33,7 @@ import tinycolor from 'tinycolor2';
 import { popupMenu } from '@/os';
 import { url } from '@/config';
 import { scrollToTop } from '@/scripts/scroll';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import { i18n } from '@/i18n';
 import { globalEvents } from '@/events';
 import { injectPageMetadata } from '@/scripts/page-metadata';
diff --git a/packages/client/src/pages/admin/abuses.vue b/packages/client/src/pages/admin/abuses.vue
index 25fd8bcb6d..9907d4d235 100644
--- a/packages/client/src/pages/admin/abuses.vue
+++ b/packages/client/src/pages/admin/abuses.vue
@@ -52,7 +52,7 @@ import { computed } from 'vue';
 import XHeader from './_header_.vue';
 import MkInput from '@/components/form/input.vue';
 import MkSelect from '@/components/form/select.vue';
-import MkPagination from '@/components/ui/pagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
 import XAbuseReport from '@/components/MkAbuseReport.vue';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
diff --git a/packages/client/src/pages/admin/ads.vue b/packages/client/src/pages/admin/ads.vue
index 21feafc0bb..9a28d2ad61 100644
--- a/packages/client/src/pages/admin/ads.vue
+++ b/packages/client/src/pages/admin/ads.vue
@@ -49,7 +49,7 @@
 <script lang="ts" setup>
 import { } from 'vue';
 import XHeader from './_header_.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/form/input.vue';
 import MkTextarea from '@/components/form/textarea.vue';
 import FormRadios from '@/components/form/radios.vue';
diff --git a/packages/client/src/pages/admin/announcements.vue b/packages/client/src/pages/admin/announcements.vue
index 5107c2f302..f10693314a 100644
--- a/packages/client/src/pages/admin/announcements.vue
+++ b/packages/client/src/pages/admin/announcements.vue
@@ -29,7 +29,7 @@
 <script lang="ts" setup>
 import { } from 'vue';
 import XHeader from './_header_.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/form/input.vue';
 import MkTextarea from '@/components/form/textarea.vue';
 import * as os from '@/os';
diff --git a/packages/client/src/pages/admin/bot-protection.vue b/packages/client/src/pages/admin/bot-protection.vue
index 2c404380af..72d5e379de 100644
--- a/packages/client/src/pages/admin/bot-protection.vue
+++ b/packages/client/src/pages/admin/bot-protection.vue
@@ -47,7 +47,7 @@
 import { defineAsyncComponent } from 'vue';
 import FormRadios from '@/components/form/radios.vue';
 import FormInput from '@/components/form/input.vue';
-import FormButton from '@/components/ui/button.vue';
+import FormButton from '@/components/MkButton.vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import FormSlot from '@/components/form/slot.vue';
 import * as os from '@/os';
diff --git a/packages/client/src/pages/admin/email-settings.vue b/packages/client/src/pages/admin/email-settings.vue
index 46cfd3db72..64137f0c3e 100644
--- a/packages/client/src/pages/admin/email-settings.vue
+++ b/packages/client/src/pages/admin/email-settings.vue
@@ -50,7 +50,7 @@ import { } from 'vue';
 import XHeader from './_header_.vue';
 import FormSwitch from '@/components/form/switch.vue';
 import FormInput from '@/components/form/input.vue';
-import FormInfo from '@/components/ui/info.vue';
+import FormInfo from '@/components/MkInfo.vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import FormSplit from '@/components/form/split.vue';
 import FormSection from '@/components/form/section.vue';
diff --git a/packages/client/src/pages/admin/emoji-edit-dialog.vue b/packages/client/src/pages/admin/emoji-edit-dialog.vue
index af91044e7d..090dd9afc1 100644
--- a/packages/client/src/pages/admin/emoji-edit-dialog.vue
+++ b/packages/client/src/pages/admin/emoji-edit-dialog.vue
@@ -30,8 +30,8 @@
 
 <script lang="ts" setup>
 import { } from 'vue';
-import XModalWindow from '@/components/ui/modal-window.vue';
-import MkButton from '@/components/ui/button.vue';
+import XModalWindow from '@/components/MkModalWindow.vue';
+import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/form/input.vue';
 import * as os from '@/os';
 import { unique } from '@/scripts/array';
diff --git a/packages/client/src/pages/admin/emojis.vue b/packages/client/src/pages/admin/emojis.vue
index cb886ee37f..94f152d7db 100644
--- a/packages/client/src/pages/admin/emojis.vue
+++ b/packages/client/src/pages/admin/emojis.vue
@@ -70,9 +70,9 @@
 <script lang="ts" setup>
 import { computed, defineAsyncComponent, defineComponent, ref, toRef } from 'vue';
 import XHeader from './_header_.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/form/input.vue';
-import MkPagination from '@/components/ui/pagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
 import MkTab from '@/components/MkTab.vue';
 import MkSwitch from '@/components/form/switch.vue';
 import FormSplit from '@/components/form/split.vue';
diff --git a/packages/client/src/pages/admin/files.vue b/packages/client/src/pages/admin/files.vue
index f919571cef..2e3a807ba6 100644
--- a/packages/client/src/pages/admin/files.vue
+++ b/packages/client/src/pages/admin/files.vue
@@ -36,7 +36,7 @@
 import { computed, defineAsyncComponent } from 'vue';
 import * as Acct from 'misskey-js/built/acct';
 import XHeader from './_header_.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/form/input.vue';
 import MkSelect from '@/components/form/select.vue';
 import MkFileListForAdmin from '@/components/MkFileListForAdmin.vue';
diff --git a/packages/client/src/pages/admin/index.vue b/packages/client/src/pages/admin/index.vue
index 154b854809..9200b5d547 100644
--- a/packages/client/src/pages/admin/index.vue
+++ b/packages/client/src/pages/admin/index.vue
@@ -25,8 +25,8 @@
 <script lang="ts" setup>
 import { defineAsyncComponent, inject, nextTick, onMounted, onUnmounted, provide, watch } from 'vue';
 import { i18n } from '@/i18n';
-import MkSuperMenu from '@/components/ui/super-menu.vue';
-import MkInfo from '@/components/ui/info.vue';
+import MkSuperMenu from '@/components/MkSuperMenu.vue';
+import MkInfo from '@/components/MkInfo.vue';
 import { scroll } from '@/scripts/scroll';
 import { instance } from '@/instance';
 import * as os from '@/os';
diff --git a/packages/client/src/pages/admin/instance-block.vue b/packages/client/src/pages/admin/instance-block.vue
index 6d479e8f0d..94b740a4d5 100644
--- a/packages/client/src/pages/admin/instance-block.vue
+++ b/packages/client/src/pages/admin/instance-block.vue
@@ -17,7 +17,7 @@
 <script lang="ts" setup>
 import { } from 'vue';
 import XHeader from './_header_.vue';
-import FormButton from '@/components/ui/button.vue';
+import FormButton from '@/components/MkButton.vue';
 import FormTextarea from '@/components/form/textarea.vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import * as os from '@/os';
diff --git a/packages/client/src/pages/admin/integrations.discord.vue b/packages/client/src/pages/admin/integrations.discord.vue
index eff318dc17..0ab6ecbb4f 100644
--- a/packages/client/src/pages/admin/integrations.discord.vue
+++ b/packages/client/src/pages/admin/integrations.discord.vue
@@ -28,8 +28,8 @@
 import { } from 'vue';
 import FormSwitch from '@/components/form/switch.vue';
 import FormInput from '@/components/form/input.vue';
-import FormButton from '@/components/ui/button.vue';
-import FormInfo from '@/components/ui/info.vue';
+import FormButton from '@/components/MkButton.vue';
+import FormInfo from '@/components/MkInfo.vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import * as os from '@/os';
 import { fetchInstance } from '@/instance';
diff --git a/packages/client/src/pages/admin/integrations.github.vue b/packages/client/src/pages/admin/integrations.github.vue
index f6e56e1ed3..34761e9a7b 100644
--- a/packages/client/src/pages/admin/integrations.github.vue
+++ b/packages/client/src/pages/admin/integrations.github.vue
@@ -28,8 +28,8 @@
 import { } from 'vue';
 import FormSwitch from '@/components/form/switch.vue';
 import FormInput from '@/components/form/input.vue';
-import FormButton from '@/components/ui/button.vue';
-import FormInfo from '@/components/ui/info.vue';
+import FormButton from '@/components/MkButton.vue';
+import FormInfo from '@/components/MkInfo.vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import * as os from '@/os';
 import { fetchInstance } from '@/instance';
diff --git a/packages/client/src/pages/admin/integrations.twitter.vue b/packages/client/src/pages/admin/integrations.twitter.vue
index 48da4648f4..a870d76a4d 100644
--- a/packages/client/src/pages/admin/integrations.twitter.vue
+++ b/packages/client/src/pages/admin/integrations.twitter.vue
@@ -28,8 +28,8 @@
 import { defineComponent } from 'vue';
 import FormSwitch from '@/components/form/switch.vue';
 import FormInput from '@/components/form/input.vue';
-import FormButton from '@/components/ui/button.vue';
-import FormInfo from '@/components/ui/info.vue';
+import FormButton from '@/components/MkButton.vue';
+import FormInfo from '@/components/MkInfo.vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import * as os from '@/os';
 import { fetchInstance } from '@/instance';
diff --git a/packages/client/src/pages/admin/metrics.vue b/packages/client/src/pages/admin/metrics.vue
index 7e5f5bb094..e0e47e667f 100644
--- a/packages/client/src/pages/admin/metrics.vue
+++ b/packages/client/src/pages/admin/metrics.vue
@@ -67,11 +67,11 @@ import {
   Tooltip,
   SubTitle
 } from 'chart.js';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import MkSelect from '@/components/form/select.vue';
 import MkInput from '@/components/form/input.vue';
-import MkContainer from '@/components/ui/container.vue';
-import MkFolder from '@/components/ui/folder.vue';
+import MkContainer from '@/components/MkContainer.vue';
+import MkFolder from '@/components/MkFolder.vue';
 import MkwFederation from '../../widgets/federation.vue';
 import { version, url } from '@/config';
 import bytes from '@/filters/bytes';
diff --git a/packages/client/src/pages/admin/proxy-account.vue b/packages/client/src/pages/admin/proxy-account.vue
index 71f35bf0f6..fe61909e80 100644
--- a/packages/client/src/pages/admin/proxy-account.vue
+++ b/packages/client/src/pages/admin/proxy-account.vue
@@ -16,8 +16,8 @@
 <script lang="ts" setup>
 import { } from 'vue';
 import MkKeyValue from '@/components/MkKeyValue.vue';
-import FormButton from '@/components/ui/button.vue';
-import MkInfo from '@/components/ui/info.vue';
+import FormButton from '@/components/MkButton.vue';
+import MkInfo from '@/components/MkInfo.vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import * as os from '@/os';
 import { fetchInstance } from '@/instance';
diff --git a/packages/client/src/pages/admin/queue.vue b/packages/client/src/pages/admin/queue.vue
index 6ccb464d17..a6fc75aea8 100644
--- a/packages/client/src/pages/admin/queue.vue
+++ b/packages/client/src/pages/admin/queue.vue
@@ -12,7 +12,7 @@
 import { markRaw, onMounted, onBeforeUnmount, nextTick } from 'vue';
 import XQueue from './queue.chart.vue';
 import XHeader from './_header_.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os';
 import * as config from '@/config';
 import { i18n } from '@/i18n';
diff --git a/packages/client/src/pages/admin/relays.vue b/packages/client/src/pages/admin/relays.vue
index 42347c0e7d..e6f7f4ead1 100644
--- a/packages/client/src/pages/admin/relays.vue
+++ b/packages/client/src/pages/admin/relays.vue
@@ -19,7 +19,7 @@
 <script lang="ts" setup>
 import { } from 'vue';
 import XHeader from './_header_.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
diff --git a/packages/client/src/pages/admin/security.vue b/packages/client/src/pages/admin/security.vue
index 07ee412f3e..c36cedb312 100644
--- a/packages/client/src/pages/admin/security.vue
+++ b/packages/client/src/pages/admin/security.vue
@@ -107,11 +107,11 @@ import XHeader from './_header_.vue';
 import FormFolder from '@/components/form/folder.vue';
 import FormRadios from '@/components/form/radios.vue';
 import FormSwitch from '@/components/form/switch.vue';
-import FormInfo from '@/components/ui/info.vue';
+import FormInfo from '@/components/MkInfo.vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import FormRange from '@/components/form/range.vue';
 import FormInput from '@/components/form/input.vue';
-import FormButton from '@/components/ui/button.vue';
+import FormButton from '@/components/MkButton.vue';
 import * as os from '@/os';
 import { fetchInstance } from '@/instance';
 import { i18n } from '@/i18n';
diff --git a/packages/client/src/pages/admin/settings.vue b/packages/client/src/pages/admin/settings.vue
index 496eb46ea4..cf6b1f17e9 100644
--- a/packages/client/src/pages/admin/settings.vue
+++ b/packages/client/src/pages/admin/settings.vue
@@ -153,7 +153,7 @@ import XHeader from './_header_.vue';
 import FormSwitch from '@/components/form/switch.vue';
 import FormInput from '@/components/form/input.vue';
 import FormTextarea from '@/components/form/textarea.vue';
-import FormInfo from '@/components/ui/info.vue';
+import FormInfo from '@/components/MkInfo.vue';
 import FormSection from '@/components/form/section.vue';
 import FormSplit from '@/components/form/split.vue';
 import FormSuspense from '@/components/form/suspense.vue';
diff --git a/packages/client/src/pages/admin/users.vue b/packages/client/src/pages/admin/users.vue
index 9cdb6d8be2..eeb335a430 100644
--- a/packages/client/src/pages/admin/users.vue
+++ b/packages/client/src/pages/admin/users.vue
@@ -57,7 +57,7 @@ import { computed } from 'vue';
 import XHeader from './_header_.vue';
 import MkInput from '@/components/form/input.vue';
 import MkSelect from '@/components/form/select.vue';
-import MkPagination from '@/components/ui/pagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
 import * as os from '@/os';
 import { lookupUser } from '@/scripts/lookup-user';
 import { i18n } from '@/i18n';
diff --git a/packages/client/src/pages/announcements.vue b/packages/client/src/pages/announcements.vue
index aeb85b6557..5f66596997 100644
--- a/packages/client/src/pages/announcements.vue
+++ b/packages/client/src/pages/announcements.vue
@@ -20,8 +20,8 @@
 
 <script lang="ts" setup>
 import { } from 'vue';
-import MkPagination from '@/components/ui/pagination.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkPagination from '@/components/MkPagination.vue';
+import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
diff --git a/packages/client/src/pages/api-console.vue b/packages/client/src/pages/api-console.vue
index 2f8eeadff1..0064e4c3f1 100644
--- a/packages/client/src/pages/api-console.vue
+++ b/packages/client/src/pages/api-console.vue
@@ -32,7 +32,7 @@
 import { ref } from 'vue';
 import JSON5 from 'json5';
 import { Endpoints } from 'misskey-js';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/form/input.vue';
 import MkTextarea from '@/components/form/textarea.vue';
 import MkSwitch from '@/components/form/switch.vue';
diff --git a/packages/client/src/pages/auth.form.vue b/packages/client/src/pages/auth.form.vue
index 5feff0149a..024a7a2c5b 100644
--- a/packages/client/src/pages/auth.form.vue
+++ b/packages/client/src/pages/auth.form.vue
@@ -21,7 +21,7 @@
 
 <script lang="ts">
 import { defineComponent } from 'vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os';
 
 export default defineComponent({
diff --git a/packages/client/src/pages/channel-editor.vue b/packages/client/src/pages/channel-editor.vue
index 5b5d0454f9..3e94b5f041 100644
--- a/packages/client/src/pages/channel-editor.vue
+++ b/packages/client/src/pages/channel-editor.vue
@@ -29,7 +29,7 @@
 <script lang="ts" setup>
 import { computed, inject, watch } from 'vue';
 import MkTextarea from '@/components/form/textarea.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/form/input.vue';
 import { selectFile } from '@/scripts/select-file';
 import * as os from '@/os';
diff --git a/packages/client/src/pages/channel.vue b/packages/client/src/pages/channel.vue
index 04c302f4da..380c3efc8e 100644
--- a/packages/client/src/pages/channel.vue
+++ b/packages/client/src/pages/channel.vue
@@ -33,7 +33,7 @@
 
 <script lang="ts" setup>
 import { computed, inject, watch } from 'vue';
-import MkContainer from '@/components/ui/container.vue';
+import MkContainer from '@/components/MkContainer.vue';
 import XPostForm from '@/components/MkPostForm.vue';
 import XTimeline from '@/components/MkTimeline.vue';
 import XChannelFollowButton from '@/components/MkChannelFollowButton.vue';
diff --git a/packages/client/src/pages/channels.vue b/packages/client/src/pages/channels.vue
index fbc17b6892..56ea98d15e 100644
--- a/packages/client/src/pages/channels.vue
+++ b/packages/client/src/pages/channels.vue
@@ -25,8 +25,8 @@
 <script lang="ts" setup>
 import { computed, defineComponent, inject } from 'vue';
 import MkChannelPreview from '@/components/MkChannelPreview.vue';
-import MkPagination from '@/components/ui/pagination.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkPagination from '@/components/MkPagination.vue';
+import MkButton from '@/components/MkButton.vue';
 import { useRouter } from '@/router';
 import { definePageMetadata } from '@/scripts/page-metadata';
 import { i18n } from '@/i18n';
diff --git a/packages/client/src/pages/explore.users.vue b/packages/client/src/pages/explore.users.vue
index f25015620a..e16e40b8ed 100644
--- a/packages/client/src/pages/explore.users.vue
+++ b/packages/client/src/pages/explore.users.vue
@@ -60,7 +60,7 @@
 <script lang="ts" setup>
 import { computed, watch } from 'vue';
 import XUserList from '@/components/MkUserList.vue';
-import MkFolder from '@/components/ui/folder.vue';
+import MkFolder from '@/components/MkFolder.vue';
 import MkTab from '@/components/MkTab.vue';
 import number from '@/filters/number';
 import * as os from '@/os';
diff --git a/packages/client/src/pages/explore.vue b/packages/client/src/pages/explore.vue
index 1fac32c036..279960d139 100644
--- a/packages/client/src/pages/explore.vue
+++ b/packages/client/src/pages/explore.vue
@@ -33,7 +33,7 @@
 import { computed, watch } from 'vue';
 import XFeatured from './explore.featured.vue';
 import XUsers from './explore.users.vue';
-import MkFolder from '@/components/ui/folder.vue';
+import MkFolder from '@/components/MkFolder.vue';
 import MkInput from '@/components/form/input.vue';
 import MkRadios from '@/components/form/radios.vue';
 import number from '@/filters/number';
diff --git a/packages/client/src/pages/favorites.vue b/packages/client/src/pages/favorites.vue
index 1501484cc3..32a1dbf592 100644
--- a/packages/client/src/pages/favorites.vue
+++ b/packages/client/src/pages/favorites.vue
@@ -22,7 +22,7 @@
 
 <script lang="ts" setup>
 import { ref } from 'vue';
-import MkPagination from '@/components/ui/pagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
 import XNote from '@/components/MkNote.vue';
 import XList from '@/components/MkDateSeparatedList.vue';
 import { i18n } from '@/i18n';
diff --git a/packages/client/src/pages/follow-requests.vue b/packages/client/src/pages/follow-requests.vue
index f87bcbf24b..82d7164260 100644
--- a/packages/client/src/pages/follow-requests.vue
+++ b/packages/client/src/pages/follow-requests.vue
@@ -36,7 +36,7 @@
 
 <script lang="ts" setup>
 import { ref, computed } from 'vue';
-import MkPagination from '@/components/ui/pagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
 import { userPage, acct } from '@/filters/user';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
diff --git a/packages/client/src/pages/gallery/edit.vue b/packages/client/src/pages/gallery/edit.vue
index 2e0e5063e9..8f716d9eb3 100644
--- a/packages/client/src/pages/gallery/edit.vue
+++ b/packages/client/src/pages/gallery/edit.vue
@@ -32,7 +32,7 @@
 
 <script lang="ts" setup>
 import { computed, inject, watch } from 'vue';
-import FormButton from '@/components/ui/button.vue';
+import FormButton from '@/components/MkButton.vue';
 import FormInput from '@/components/form/input.vue';
 import FormTextarea from '@/components/form/textarea.vue';
 import FormSwitch from '@/components/form/switch.vue';
diff --git a/packages/client/src/pages/gallery/index.vue b/packages/client/src/pages/gallery/index.vue
index 1dfedb4a67..598383217e 100644
--- a/packages/client/src/pages/gallery/index.vue
+++ b/packages/client/src/pages/gallery/index.vue
@@ -44,11 +44,11 @@
 <script lang="ts" setup>
 import { computed, defineComponent, watch } from 'vue';
 import XUserList from '@/components/MkUserList.vue';
-import MkFolder from '@/components/ui/folder.vue';
+import MkFolder from '@/components/MkFolder.vue';
 import MkInput from '@/components/form/input.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import MkTab from '@/components/MkTab.vue';
-import MkPagination from '@/components/ui/pagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
 import MkGalleryPostPreview from '@/components/MkGalleryPostPreview.vue';
 import number from '@/filters/number';
 import * as os from '@/os';
diff --git a/packages/client/src/pages/gallery/post.vue b/packages/client/src/pages/gallery/post.vue
index 1e7a997167..3804bcdcf5 100644
--- a/packages/client/src/pages/gallery/post.vue
+++ b/packages/client/src/pages/gallery/post.vue
@@ -56,11 +56,11 @@
 
 <script lang="ts" setup>
 import { computed, defineComponent, inject, watch } from 'vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os';
-import MkContainer from '@/components/ui/container.vue';
+import MkContainer from '@/components/MkContainer.vue';
 import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
-import MkPagination from '@/components/ui/pagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
 import MkGalleryPostPreview from '@/components/MkGalleryPostPreview.vue';
 import MkFollowButton from '@/components/MkFollowButton.vue';
 import { url } from '@/config';
diff --git a/packages/client/src/pages/instance-info.vue b/packages/client/src/pages/instance-info.vue
index a4d462f823..6e8560ef42 100644
--- a/packages/client/src/pages/instance-info.vue
+++ b/packages/client/src/pages/instance-info.vue
@@ -121,7 +121,7 @@ import MkChart from '@/components/MkChart.vue';
 import MkObjectView from '@/components/MkObjectView.vue';
 import FormLink from '@/components/form/link.vue';
 import MkLink from '@/components/MkLink.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import FormSection from '@/components/form/section.vue';
 import MkKeyValue from '@/components/MkKeyValue.vue';
 import MkSelect from '@/components/form/select.vue';
@@ -133,7 +133,7 @@ import { iAmModerator } from '@/account';
 import { definePageMetadata } from '@/scripts/page-metadata';
 import { i18n } from '@/i18n';
 import MkUserCardMini from '@/components/MkUserCardMini.vue';
-import MkPagination from '@/components/ui/pagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
 
 const props = defineProps<{
 	host: string;
diff --git a/packages/client/src/pages/messaging/index.vue b/packages/client/src/pages/messaging/index.vue
index 7df4c846fb..56d852fe3d 100644
--- a/packages/client/src/pages/messaging/index.vue
+++ b/packages/client/src/pages/messaging/index.vue
@@ -45,7 +45,7 @@
 <script lang="ts" setup>
 import { defineAsyncComponent, defineComponent, inject, markRaw, onMounted, onUnmounted } from 'vue';
 import * as Acct from 'misskey-js/built/acct';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import { acct } from '@/filters/user';
 import * as os from '@/os';
 import { stream } from '@/stream';
diff --git a/packages/client/src/pages/messaging/messaging-room.vue b/packages/client/src/pages/messaging/messaging-room.vue
index b91cba7ec7..a9da89ed95 100644
--- a/packages/client/src/pages/messaging/messaging-room.vue
+++ b/packages/client/src/pages/messaging/messaging-room.vue
@@ -56,7 +56,7 @@ import * as Acct from 'misskey-js/built/acct';
 import XMessage from './messaging-room.message.vue';
 import XForm from './messaging-room.form.vue';
 import XList from '@/components/MkDateSeparatedList.vue';
-import MkPagination, { Paging } from '@/components/ui/pagination.vue';
+import MkPagination, { Paging } from '@/components/MkPagination.vue';
 import { isBottomVisible, onScrollBottom, scrollToBottom } from '@/scripts/scroll';
 import * as os from '@/os';
 import { stream } from '@/stream';
diff --git a/packages/client/src/pages/miauth.vue b/packages/client/src/pages/miauth.vue
index 5a890c7b39..5de072cbfa 100644
--- a/packages/client/src/pages/miauth.vue
+++ b/packages/client/src/pages/miauth.vue
@@ -41,7 +41,7 @@
 <script lang="ts" setup>
 import { } from 'vue';
 import MkSignin from '@/components/MkSignin.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os';
 import { $i, login } from '@/account';
 import { appendQuery, query } from '@/scripts/url';
diff --git a/packages/client/src/pages/my-antennas/editor.vue b/packages/client/src/pages/my-antennas/editor.vue
index 32027846cc..054053fbfb 100644
--- a/packages/client/src/pages/my-antennas/editor.vue
+++ b/packages/client/src/pages/my-antennas/editor.vue
@@ -47,7 +47,7 @@
 <script lang="ts" setup>
 import { watch } from 'vue';
 import * as Acct from 'misskey-js/built/acct';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/form/input.vue';
 import MkTextarea from '@/components/form/textarea.vue';
 import MkSelect from '@/components/form/select.vue';
diff --git a/packages/client/src/pages/my-antennas/index.vue b/packages/client/src/pages/my-antennas/index.vue
index 70e444da52..dc73ba674e 100644
--- a/packages/client/src/pages/my-antennas/index.vue
+++ b/packages/client/src/pages/my-antennas/index.vue
@@ -17,8 +17,8 @@
 
 <script lang="ts" setup>
 import { } from 'vue';
-import MkPagination from '@/components/ui/pagination.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkPagination from '@/components/MkPagination.vue';
+import MkButton from '@/components/MkButton.vue';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
 
diff --git a/packages/client/src/pages/my-clips/index.vue b/packages/client/src/pages/my-clips/index.vue
index 5e5454b729..68330d6db4 100644
--- a/packages/client/src/pages/my-clips/index.vue
+++ b/packages/client/src/pages/my-clips/index.vue
@@ -18,8 +18,8 @@
 
 <script lang="ts" setup>
 import { } from 'vue';
-import MkPagination from '@/components/ui/pagination.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkPagination from '@/components/MkPagination.vue';
+import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
diff --git a/packages/client/src/pages/my-lists/index.vue b/packages/client/src/pages/my-lists/index.vue
index 7dc5b3e907..9af7c0d105 100644
--- a/packages/client/src/pages/my-lists/index.vue
+++ b/packages/client/src/pages/my-lists/index.vue
@@ -18,8 +18,8 @@
 
 <script lang="ts" setup>
 import { } from 'vue';
-import MkPagination from '@/components/ui/pagination.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkPagination from '@/components/MkPagination.vue';
+import MkButton from '@/components/MkButton.vue';
 import MkAvatars from '@/components/MkAvatars.vue';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
diff --git a/packages/client/src/pages/my-lists/list.vue b/packages/client/src/pages/my-lists/list.vue
index 8b23c667dc..d90453526e 100644
--- a/packages/client/src/pages/my-lists/list.vue
+++ b/packages/client/src/pages/my-lists/list.vue
@@ -39,7 +39,7 @@
 
 <script lang="ts" setup>
 import { computed, watch } from 'vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os';
 import { mainRouter } from '@/router';
 import { definePageMetadata } from '@/scripts/page-metadata';
diff --git a/packages/client/src/pages/note.vue b/packages/client/src/pages/note.vue
index ba05a7b887..6509cb306e 100644
--- a/packages/client/src/pages/note.vue
+++ b/packages/client/src/pages/note.vue
@@ -47,7 +47,7 @@ import XNote from '@/components/MkNote.vue';
 import XNoteDetailed from '@/components/MkNoteDetailed.vue';
 import XNotes from '@/components/MkNotes.vue';
 import MkRemoteCaution from '@/components/MkRemoteCaution.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os';
 import { definePageMetadata } from '@/scripts/page-metadata';
 import { i18n } from '@/i18n';
diff --git a/packages/client/src/pages/page-editor/page-editor.vue b/packages/client/src/pages/page-editor/page-editor.vue
index aaa61e6e36..591d13053a 100644
--- a/packages/client/src/pages/page-editor/page-editor.vue
+++ b/packages/client/src/pages/page-editor/page-editor.vue
@@ -93,7 +93,7 @@ import { v4 as uuid } from 'uuid';
 import XVariable from './page-editor.script-block.vue';
 import XBlocks from './page-editor.blocks.vue';
 import MkTextarea from '@/components/form/textarea.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import MkSelect from '@/components/form/select.vue';
 import MkSwitch from '@/components/form/switch.vue';
 import MkInput from '@/components/form/input.vue';
diff --git a/packages/client/src/pages/page.vue b/packages/client/src/pages/page.vue
index 48d62a9549..fb0e6a4914 100644
--- a/packages/client/src/pages/page.vue
+++ b/packages/client/src/pages/page.vue
@@ -65,12 +65,12 @@
 <script lang="ts" setup>
 import { computed, watch } from 'vue';
 import XPage from '@/components/page/page.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os';
 import { url } from '@/config';
 import MkFollowButton from '@/components/MkFollowButton.vue';
-import MkContainer from '@/components/ui/container.vue';
-import MkPagination from '@/components/ui/pagination.vue';
+import MkContainer from '@/components/MkContainer.vue';
+import MkPagination from '@/components/MkPagination.vue';
 import MkPagePreview from '@/components/MkPagePreview.vue';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
diff --git a/packages/client/src/pages/pages.vue b/packages/client/src/pages/pages.vue
index 6e71d9c836..02b05c57ba 100644
--- a/packages/client/src/pages/pages.vue
+++ b/packages/client/src/pages/pages.vue
@@ -27,8 +27,8 @@
 <script lang="ts" setup>
 import { computed, inject } from 'vue';
 import MkPagePreview from '@/components/MkPagePreview.vue';
-import MkPagination from '@/components/ui/pagination.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkPagination from '@/components/MkPagination.vue';
+import MkButton from '@/components/MkButton.vue';
 import { useRouter } from '@/router';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
diff --git a/packages/client/src/pages/registry.keys.vue b/packages/client/src/pages/registry.keys.vue
index 372b5faa4d..ac586b4e72 100644
--- a/packages/client/src/pages/registry.keys.vue
+++ b/packages/client/src/pages/registry.keys.vue
@@ -33,7 +33,7 @@ import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
 import FormLink from '@/components/form/link.vue';
 import FormSection from '@/components/form/section.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import MkKeyValue from '@/components/MkKeyValue.vue';
 import FormSplit from '@/components/form/split.vue';
 
diff --git a/packages/client/src/pages/registry.value.vue b/packages/client/src/pages/registry.value.vue
index 9fac7f990f..b6f3d73bb6 100644
--- a/packages/client/src/pages/registry.value.vue
+++ b/packages/client/src/pages/registry.value.vue
@@ -45,11 +45,11 @@ import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
 import FormLink from '@/components/form/link.vue';
 import FormSection from '@/components/form/section.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import MkKeyValue from '@/components/MkKeyValue.vue';
 import FormTextarea from '@/components/form/textarea.vue';
 import FormSplit from '@/components/form/split.vue';
-import FormInfo from '@/components/ui/info.vue';
+import FormInfo from '@/components/MkInfo.vue';
 
 const props = defineProps<{
 	path: string;
diff --git a/packages/client/src/pages/registry.vue b/packages/client/src/pages/registry.vue
index f022a0d688..80a44d5589 100644
--- a/packages/client/src/pages/registry.vue
+++ b/packages/client/src/pages/registry.vue
@@ -22,7 +22,7 @@ import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
 import FormLink from '@/components/form/link.vue';
 import FormSection from '@/components/form/section.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 
 let scopes = $ref(null);
 
diff --git a/packages/client/src/pages/reset-password.vue b/packages/client/src/pages/reset-password.vue
index 4c4cdb8103..38f2cf289d 100644
--- a/packages/client/src/pages/reset-password.vue
+++ b/packages/client/src/pages/reset-password.vue
@@ -17,7 +17,7 @@
 <script lang="ts" setup>
 import { defineAsyncComponent, onMounted } from 'vue';
 import FormInput from '@/components/form/input.vue';
-import FormButton from '@/components/ui/button.vue';
+import FormButton from '@/components/MkButton.vue';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
 import { mainRouter } from '@/router';
diff --git a/packages/client/src/pages/scratchpad.vue b/packages/client/src/pages/scratchpad.vue
index d437601475..12b5d78b27 100644
--- a/packages/client/src/pages/scratchpad.vue
+++ b/packages/client/src/pages/scratchpad.vue
@@ -28,8 +28,8 @@ import 'prismjs/themes/prism-okaidia.css';
 import { PrismEditor } from 'vue-prism-editor';
 import 'vue-prism-editor/dist/prismeditor.min.css';
 import { AiScript, parse, utils } from '@syuilo/aiscript';
-import MkContainer from '@/components/ui/container.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkContainer from '@/components/MkContainer.vue';
+import MkButton from '@/components/MkButton.vue';
 import { createAiScriptEnv } from '@/scripts/aiscript/api';
 import * as os from '@/os';
 import { $i } from '@/account';
diff --git a/packages/client/src/pages/settings/2fa.vue b/packages/client/src/pages/settings/2fa.vue
index d72d3e2060..89d8178dc6 100644
--- a/packages/client/src/pages/settings/2fa.vue
+++ b/packages/client/src/pages/settings/2fa.vue
@@ -68,8 +68,8 @@
 import { ref } from 'vue';
 import { hostname } from '@/config';
 import { byteify, hexify, stringify } from '@/scripts/2fa';
-import MkButton from '@/components/ui/button.vue';
-import MkInfo from '@/components/ui/info.vue';
+import MkButton from '@/components/MkButton.vue';
+import MkInfo from '@/components/MkInfo.vue';
 import MkInput from '@/components/form/input.vue';
 import MkSwitch from '@/components/form/switch.vue';
 import * as os from '@/os';
diff --git a/packages/client/src/pages/settings/accounts.vue b/packages/client/src/pages/settings/accounts.vue
index 33a0fcc329..e16931a9ca 100644
--- a/packages/client/src/pages/settings/accounts.vue
+++ b/packages/client/src/pages/settings/accounts.vue
@@ -23,7 +23,7 @@
 <script lang="ts" setup>
 import { defineAsyncComponent, ref } from 'vue';
 import FormSuspense from '@/components/form/suspense.vue';
-import FormButton from '@/components/ui/button.vue';
+import FormButton from '@/components/MkButton.vue';
 import * as os from '@/os';
 import { getAccounts, addAccount as addAccounts, removeAccount as _removeAccount, login, $i } from '@/account';
 import { i18n } from '@/i18n';
diff --git a/packages/client/src/pages/settings/api.vue b/packages/client/src/pages/settings/api.vue
index b0a787fc48..7165089e39 100644
--- a/packages/client/src/pages/settings/api.vue
+++ b/packages/client/src/pages/settings/api.vue
@@ -9,7 +9,7 @@
 <script lang="ts" setup>
 import { defineAsyncComponent, ref } from 'vue';
 import FormLink from '@/components/form/link.vue';
-import FormButton from '@/components/ui/button.vue';
+import FormButton from '@/components/MkButton.vue';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
diff --git a/packages/client/src/pages/settings/apps.vue b/packages/client/src/pages/settings/apps.vue
index 10ecbc795d..8b345c8e9f 100644
--- a/packages/client/src/pages/settings/apps.vue
+++ b/packages/client/src/pages/settings/apps.vue
@@ -39,7 +39,7 @@
 
 <script lang="ts" setup>
 import { ref } from 'vue';
-import FormPagination from '@/components/ui/pagination.vue';
+import FormPagination from '@/components/MkPagination.vue';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
diff --git a/packages/client/src/pages/settings/custom-css.vue b/packages/client/src/pages/settings/custom-css.vue
index d5000d3973..2992906e6d 100644
--- a/packages/client/src/pages/settings/custom-css.vue
+++ b/packages/client/src/pages/settings/custom-css.vue
@@ -11,7 +11,7 @@
 <script lang="ts" setup>
 import { ref, watch } from 'vue';
 import FormTextarea from '@/components/form/textarea.vue';
-import FormInfo from '@/components/ui/info.vue';
+import FormInfo from '@/components/MkInfo.vue';
 import * as os from '@/os';
 import { unisonReload } from '@/scripts/unison-reload';
 import { i18n } from '@/i18n';
diff --git a/packages/client/src/pages/settings/delete-account.vue b/packages/client/src/pages/settings/delete-account.vue
index 3c4ea716ce..851a857fed 100644
--- a/packages/client/src/pages/settings/delete-account.vue
+++ b/packages/client/src/pages/settings/delete-account.vue
@@ -8,8 +8,8 @@
 </template>
 
 <script lang="ts" setup>
-import FormInfo from '@/components/ui/info.vue';
-import FormButton from '@/components/ui/button.vue';
+import FormInfo from '@/components/MkInfo.vue';
+import FormButton from '@/components/MkButton.vue';
 import * as os from '@/os';
 import { signout } from '@/account';
 import { i18n } from '@/i18n';
diff --git a/packages/client/src/pages/settings/import-export.vue b/packages/client/src/pages/settings/import-export.vue
index 13c2b7fc85..d3d155894e 100644
--- a/packages/client/src/pages/settings/import-export.vue
+++ b/packages/client/src/pages/settings/import-export.vue
@@ -71,7 +71,7 @@
 
 <script lang="ts" setup>
 import { ref } from 'vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import FormSection from '@/components/form/section.vue';
 import FormFolder from '@/components/form/folder.vue';
 import FormSwitch from '@/components/form/switch.vue';
diff --git a/packages/client/src/pages/settings/index.vue b/packages/client/src/pages/settings/index.vue
index 862435eb3f..73407ff5fb 100644
--- a/packages/client/src/pages/settings/index.vue
+++ b/packages/client/src/pages/settings/index.vue
@@ -24,8 +24,8 @@
 <script setup lang="ts">
 import { computed, defineAsyncComponent, inject, nextTick, onActivated, onMounted, onUnmounted, provide, ref, watch } from 'vue';
 import { i18n } from '@/i18n';
-import MkInfo from '@/components/ui/info.vue';
-import MkSuperMenu from '@/components/ui/super-menu.vue';
+import MkInfo from '@/components/MkInfo.vue';
+import MkSuperMenu from '@/components/MkSuperMenu.vue';
 import { scroll } from '@/scripts/scroll';
 import { signout , $i } from '@/account';
 import { unisonReload } from '@/scripts/unison-reload';
diff --git a/packages/client/src/pages/settings/instance-mute.vue b/packages/client/src/pages/settings/instance-mute.vue
index d0ca85adca..5a0d48b82e 100644
--- a/packages/client/src/pages/settings/instance-mute.vue
+++ b/packages/client/src/pages/settings/instance-mute.vue
@@ -12,8 +12,8 @@
 <script lang="ts" setup>
 import { ref, watch } from 'vue';
 import FormTextarea from '@/components/form/textarea.vue';
-import MkInfo from '@/components/ui/info.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkInfo from '@/components/MkInfo.vue';
+import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os';
 import { $i } from '@/account';
 import { i18n } from '@/i18n';
diff --git a/packages/client/src/pages/settings/integration.vue b/packages/client/src/pages/settings/integration.vue
index ccb02e08a2..c8219519f8 100644
--- a/packages/client/src/pages/settings/integration.vue
+++ b/packages/client/src/pages/settings/integration.vue
@@ -27,7 +27,7 @@
 import { computed, onMounted, ref, watch } from 'vue';
 import { apiUrl } from '@/config';
 import FormSection from '@/components/form/section.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import { $i } from '@/account';
 import { instance } from '@/instance';
 import { i18n } from '@/i18n';
diff --git a/packages/client/src/pages/settings/mute-block.vue b/packages/client/src/pages/settings/mute-block.vue
index 28c215a04f..3832933cf9 100644
--- a/packages/client/src/pages/settings/mute-block.vue
+++ b/packages/client/src/pages/settings/mute-block.vue
@@ -29,9 +29,9 @@
 
 <script lang="ts" setup>
 import { } from 'vue';
-import MkPagination from '@/components/ui/pagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
 import MkTab from '@/components/MkTab.vue';
-import FormInfo from '@/components/ui/info.vue';
+import FormInfo from '@/components/MkInfo.vue';
 import FormLink from '@/components/form/link.vue';
 import { userPage } from '@/filters/user';
 import * as os from '@/os';
diff --git a/packages/client/src/pages/settings/navbar.vue b/packages/client/src/pages/settings/navbar.vue
index 534112c3e0..6c501e9f2f 100644
--- a/packages/client/src/pages/settings/navbar.vue
+++ b/packages/client/src/pages/settings/navbar.vue
@@ -21,7 +21,7 @@
 import { computed, ref, watch } from 'vue';
 import FormTextarea from '@/components/form/textarea.vue';
 import FormRadios from '@/components/form/radios.vue';
-import FormButton from '@/components/ui/button.vue';
+import FormButton from '@/components/MkButton.vue';
 import * as os from '@/os';
 import { navbarItemDef } from '@/navbar';
 import { defaultStore } from '@/store';
diff --git a/packages/client/src/pages/settings/notifications.vue b/packages/client/src/pages/settings/notifications.vue
index 47c51e600b..5703e0c6b6 100644
--- a/packages/client/src/pages/settings/notifications.vue
+++ b/packages/client/src/pages/settings/notifications.vue
@@ -12,7 +12,7 @@
 <script lang="ts" setup>
 import { defineAsyncComponent } from 'vue';
 import { notificationTypes } from 'misskey-js';
-import FormButton from '@/components/ui/button.vue';
+import FormButton from '@/components/MkButton.vue';
 import FormLink from '@/components/form/link.vue';
 import FormSection from '@/components/form/section.vue';
 import * as os from '@/os';
diff --git a/packages/client/src/pages/settings/plugin.install.vue b/packages/client/src/pages/settings/plugin.install.vue
index 4940f3b5f9..e259bbeb3a 100644
--- a/packages/client/src/pages/settings/plugin.install.vue
+++ b/packages/client/src/pages/settings/plugin.install.vue
@@ -18,8 +18,8 @@ import { AiScript, parse } from '@syuilo/aiscript';
 import { serialize } from '@syuilo/aiscript/built/serializer';
 import { v4 as uuid } from 'uuid';
 import FormTextarea from '@/components/form/textarea.vue';
-import FormButton from '@/components/ui/button.vue';
-import FormInfo from '@/components/ui/info.vue';
+import FormButton from '@/components/MkButton.vue';
+import FormInfo from '@/components/MkInfo.vue';
 import * as os from '@/os';
 import { ColdDeviceStorage } from '@/store';
 import { unisonReload } from '@/scripts/unison-reload';
diff --git a/packages/client/src/pages/settings/plugin.vue b/packages/client/src/pages/settings/plugin.vue
index b557bd538f..8ce6fe4445 100644
--- a/packages/client/src/pages/settings/plugin.vue
+++ b/packages/client/src/pages/settings/plugin.vue
@@ -36,7 +36,7 @@ import { nextTick, ref } from 'vue';
 import FormLink from '@/components/form/link.vue';
 import FormSwitch from '@/components/form/switch.vue';
 import FormSection from '@/components/form/section.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import MkKeyValue from '@/components/MkKeyValue.vue';
 import * as os from '@/os';
 import { ColdDeviceStorage } from '@/store';
diff --git a/packages/client/src/pages/settings/preferences-backups.vue b/packages/client/src/pages/settings/preferences-backups.vue
index 991bb7902d..fac67185bc 100644
--- a/packages/client/src/pages/settings/preferences-backups.vue
+++ b/packages/client/src/pages/settings/preferences-backups.vue
@@ -33,8 +33,8 @@
 import { computed, onMounted, onUnmounted, useCssModule } from 'vue';
 import { v4 as uuid } from 'uuid';
 import FormSection from '@/components/form/section.vue';
-import MkButton from '@/components/ui/button.vue';
-import MkInfo from '@/components/ui/info.vue';
+import MkButton from '@/components/MkButton.vue';
+import MkInfo from '@/components/MkInfo.vue';
 import * as os from '@/os';
 import { ColdDeviceStorage, defaultStore } from '@/store';
 import { unisonReload } from '@/scripts/unison-reload';
diff --git a/packages/client/src/pages/settings/profile.vue b/packages/client/src/pages/settings/profile.vue
index 5bb3273b3f..aaf60c8d55 100644
--- a/packages/client/src/pages/settings/profile.vue
+++ b/packages/client/src/pages/settings/profile.vue
@@ -61,7 +61,7 @@
 
 <script lang="ts" setup>
 import { reactive, watch } from 'vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import FormInput from '@/components/form/input.vue';
 import FormTextarea from '@/components/form/textarea.vue';
 import FormSwitch from '@/components/form/switch.vue';
diff --git a/packages/client/src/pages/settings/reaction.vue b/packages/client/src/pages/settings/reaction.vue
index eae95c1e90..c23c1c2375 100644
--- a/packages/client/src/pages/settings/reaction.vue
+++ b/packages/client/src/pages/settings/reaction.vue
@@ -59,7 +59,7 @@ import XDraggable from 'vuedraggable';
 import FormInput from '@/components/form/input.vue';
 import FormRadios from '@/components/form/radios.vue';
 import FromSlot from '@/components/form/slot.vue';
-import FormButton from '@/components/ui/button.vue';
+import FormButton from '@/components/MkButton.vue';
 import FormSection from '@/components/form/section.vue';
 import FormSwitch from '@/components/form/switch.vue';
 import * as os from '@/os';
diff --git a/packages/client/src/pages/settings/security.vue b/packages/client/src/pages/settings/security.vue
index d926acce5d..d109a4ba7c 100644
--- a/packages/client/src/pages/settings/security.vue
+++ b/packages/client/src/pages/settings/security.vue
@@ -41,8 +41,8 @@
 import X2fa from './2fa.vue';
 import FormSection from '@/components/form/section.vue';
 import FormSlot from '@/components/form/slot.vue';
-import FormButton from '@/components/ui/button.vue';
-import MkPagination from '@/components/ui/pagination.vue';
+import FormButton from '@/components/MkButton.vue';
+import MkPagination from '@/components/MkPagination.vue';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
diff --git a/packages/client/src/pages/settings/sounds.vue b/packages/client/src/pages/settings/sounds.vue
index 2d2b5a0a07..2729609522 100644
--- a/packages/client/src/pages/settings/sounds.vue
+++ b/packages/client/src/pages/settings/sounds.vue
@@ -20,7 +20,7 @@
 <script lang="ts" setup>
 import { computed, ref } from 'vue';
 import FormRange from '@/components/form/range.vue';
-import FormButton from '@/components/ui/button.vue';
+import FormButton from '@/components/MkButton.vue';
 import FormLink from '@/components/form/link.vue';
 import FormSection from '@/components/form/section.vue';
 import * as os from '@/os';
diff --git a/packages/client/src/pages/settings/statusbar.statusbar.vue b/packages/client/src/pages/settings/statusbar.statusbar.vue
index 2f0c6fc1ee..98a1825b95 100644
--- a/packages/client/src/pages/settings/statusbar.statusbar.vue
+++ b/packages/client/src/pages/settings/statusbar.statusbar.vue
@@ -86,7 +86,7 @@ import FormSelect from '@/components/form/select.vue';
 import MkInput from '@/components/form/input.vue';
 import MkSwitch from '@/components/form/switch.vue';
 import FormRadios from '@/components/form/radios.vue';
-import FormButton from '@/components/ui/button.vue';
+import FormButton from '@/components/MkButton.vue';
 import FormRange from '@/components/form/range.vue';
 import * as os from '@/os';
 import { defaultStore } from '@/store';
diff --git a/packages/client/src/pages/settings/statusbar.vue b/packages/client/src/pages/settings/statusbar.vue
index 3f23ed470c..9dbf182142 100644
--- a/packages/client/src/pages/settings/statusbar.vue
+++ b/packages/client/src/pages/settings/statusbar.vue
@@ -15,7 +15,7 @@ import { v4 as uuid } from 'uuid';
 import XStatusbar from './statusbar.statusbar.vue';
 import FormRadios from '@/components/form/radios.vue';
 import FormFolder from '@/components/form/folder.vue';
-import FormButton from '@/components/ui/button.vue';
+import FormButton from '@/components/MkButton.vue';
 import * as os from '@/os';
 import { defaultStore } from '@/store';
 import { unisonReload } from '@/scripts/unison-reload';
diff --git a/packages/client/src/pages/settings/theme.install.vue b/packages/client/src/pages/settings/theme.install.vue
index 2994d8fb1a..34f8384d87 100644
--- a/packages/client/src/pages/settings/theme.install.vue
+++ b/packages/client/src/pages/settings/theme.install.vue
@@ -15,7 +15,7 @@
 import { } from 'vue';
 import JSON5 from 'json5';
 import FormTextarea from '@/components/form/textarea.vue';
-import FormButton from '@/components/ui/button.vue';
+import FormButton from '@/components/MkButton.vue';
 import { applyTheme, validateTheme } from '@/scripts/theme';
 import * as os from '@/os';
 import { addTheme, getThemes } from '@/theme-store';
diff --git a/packages/client/src/pages/settings/theme.manage.vue b/packages/client/src/pages/settings/theme.manage.vue
index 9d28b4a316..792bb15e5d 100644
--- a/packages/client/src/pages/settings/theme.manage.vue
+++ b/packages/client/src/pages/settings/theme.manage.vue
@@ -31,7 +31,7 @@ import JSON5 from 'json5';
 import FormTextarea from '@/components/form/textarea.vue';
 import FormSelect from '@/components/form/select.vue';
 import FormInput from '@/components/form/input.vue';
-import FormButton from '@/components/ui/button.vue';
+import FormButton from '@/components/MkButton.vue';
 import { Theme, getBuiltinThemesRef } from '@/scripts/theme';
 import copyToClipboard from '@/scripts/copy-to-clipboard';
 import * as os from '@/os';
diff --git a/packages/client/src/pages/settings/theme.vue b/packages/client/src/pages/settings/theme.vue
index cc869699a6..6571a881a9 100644
--- a/packages/client/src/pages/settings/theme.vue
+++ b/packages/client/src/pages/settings/theme.vue
@@ -72,7 +72,7 @@ import FormSwitch from '@/components/form/switch.vue';
 import FormSelect from '@/components/form/select.vue';
 import FormSection from '@/components/form/section.vue';
 import FormLink from '@/components/form/link.vue';
-import FormButton from '@/components/ui/button.vue';
+import FormButton from '@/components/MkButton.vue';
 import { getBuiltinThemesRef } from '@/scripts/theme';
 import { selectFile } from '@/scripts/select-file';
 import { isDeviceDarkmode } from '@/scripts/is-device-darkmode';
diff --git a/packages/client/src/pages/settings/webhook.edit.vue b/packages/client/src/pages/settings/webhook.edit.vue
index e36e1d7540..35f3cb1dab 100644
--- a/packages/client/src/pages/settings/webhook.edit.vue
+++ b/packages/client/src/pages/settings/webhook.edit.vue
@@ -38,7 +38,7 @@ import { } from 'vue';
 import FormInput from '@/components/form/input.vue';
 import FormSection from '@/components/form/section.vue';
 import FormSwitch from '@/components/form/switch.vue';
-import FormButton from '@/components/ui/button.vue';
+import FormButton from '@/components/MkButton.vue';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
diff --git a/packages/client/src/pages/settings/webhook.new.vue b/packages/client/src/pages/settings/webhook.new.vue
index fa96c5fa4b..fcf1329ff6 100644
--- a/packages/client/src/pages/settings/webhook.new.vue
+++ b/packages/client/src/pages/settings/webhook.new.vue
@@ -36,7 +36,7 @@ import { } from 'vue';
 import FormInput from '@/components/form/input.vue';
 import FormSection from '@/components/form/section.vue';
 import FormSwitch from '@/components/form/switch.vue';
-import FormButton from '@/components/ui/button.vue';
+import FormButton from '@/components/MkButton.vue';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
diff --git a/packages/client/src/pages/settings/webhook.vue b/packages/client/src/pages/settings/webhook.vue
index 868d273ce4..1a7e73940c 100644
--- a/packages/client/src/pages/settings/webhook.vue
+++ b/packages/client/src/pages/settings/webhook.vue
@@ -29,7 +29,7 @@
 
 <script lang="ts" setup>
 import { } from 'vue';
-import MkPagination from '@/components/ui/pagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
 import FormSection from '@/components/form/section.vue';
 import FormLink from '@/components/form/link.vue';
 import { userPage } from '@/filters/user';
diff --git a/packages/client/src/pages/settings/word-mute.vue b/packages/client/src/pages/settings/word-mute.vue
index daa2a4804b..e297379568 100644
--- a/packages/client/src/pages/settings/word-mute.vue
+++ b/packages/client/src/pages/settings/word-mute.vue
@@ -32,8 +32,8 @@
 import { ref, watch } from 'vue';
 import FormTextarea from '@/components/form/textarea.vue';
 import MkKeyValue from '@/components/MkKeyValue.vue';
-import MkButton from '@/components/ui/button.vue';
-import MkInfo from '@/components/ui/info.vue';
+import MkButton from '@/components/MkButton.vue';
+import MkInfo from '@/components/MkInfo.vue';
 import MkTab from '@/components/MkTab.vue';
 import * as os from '@/os';
 import number from '@/filters/number';
diff --git a/packages/client/src/pages/share.vue b/packages/client/src/pages/share.vue
index d41a701a70..69d22ed632 100644
--- a/packages/client/src/pages/share.vue
+++ b/packages/client/src/pages/share.vue
@@ -28,7 +28,7 @@ import { } from 'vue';
 import { noteVisibilities } from 'misskey-js';
 import * as Acct from 'misskey-js/built/acct';
 import * as Misskey from 'misskey-js';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import XPostForm from '@/components/MkPostForm.vue';
 import * as os from '@/os';
 import { mainRouter } from '@/router';
diff --git a/packages/client/src/pages/theme-editor.vue b/packages/client/src/pages/theme-editor.vue
index 548e60614b..7dfeee16ed 100644
--- a/packages/client/src/pages/theme-editor.vue
+++ b/packages/client/src/pages/theme-editor.vue
@@ -74,7 +74,7 @@ import tinycolor from 'tinycolor2';
 import { v4 as uuid } from 'uuid';
 import JSON5 from 'json5';
 
-import FormButton from '@/components/ui/button.vue';
+import FormButton from '@/components/MkButton.vue';
 import FormTextarea from '@/components/form/textarea.vue';
 import FormFolder from '@/components/form/folder.vue';
 
diff --git a/packages/client/src/pages/timeline.tutorial.vue b/packages/client/src/pages/timeline.tutorial.vue
index f69bf58100..7f08ccc2a1 100644
--- a/packages/client/src/pages/timeline.tutorial.vue
+++ b/packages/client/src/pages/timeline.tutorial.vue
@@ -67,7 +67,7 @@
 
 <script lang="ts" setup>
 import { computed } from 'vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import { defaultStore } from '@/store';
 import { i18n } from '@/i18n';
 
diff --git a/packages/client/src/pages/user-info.vue b/packages/client/src/pages/user-info.vue
index 316a3975c0..d376f11c58 100644
--- a/packages/client/src/pages/user-info.vue
+++ b/packages/client/src/pages/user-info.vue
@@ -160,7 +160,7 @@ import FormTextarea from '@/components/form/textarea.vue';
 import FormSwitch from '@/components/form/switch.vue';
 import FormLink from '@/components/form/link.vue';
 import FormSection from '@/components/form/section.vue';
-import FormButton from '@/components/ui/button.vue';
+import FormButton from '@/components/MkButton.vue';
 import FormInput from '@/components/form/input.vue';
 import FormSplit from '@/components/form/split.vue';
 import FormFolder from '@/components/form/folder.vue';
@@ -168,7 +168,7 @@ import MkKeyValue from '@/components/MkKeyValue.vue';
 import MkSelect from '@/components/form/select.vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import MkFileListForAdmin from '@/components/MkFileListForAdmin.vue';
-import MkInfo from '@/components/ui/info.vue';
+import MkInfo from '@/components/MkInfo.vue';
 import * as os from '@/os';
 import number from '@/filters/number';
 import bytes from '@/filters/bytes';
diff --git a/packages/client/src/pages/user/clips.vue b/packages/client/src/pages/user/clips.vue
index f3fafc6934..50a5d4b818 100644
--- a/packages/client/src/pages/user/clips.vue
+++ b/packages/client/src/pages/user/clips.vue
@@ -12,7 +12,7 @@
 <script lang="ts" setup>
 import { computed } from 'vue';
 import * as misskey from 'misskey-js';
-import MkPagination from '@/components/ui/pagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
 
 const props = defineProps<{
 	user: misskey.entities.User;
diff --git a/packages/client/src/pages/user/follow-list.vue b/packages/client/src/pages/user/follow-list.vue
index 6ee395f28b..d42acd838f 100644
--- a/packages/client/src/pages/user/follow-list.vue
+++ b/packages/client/src/pages/user/follow-list.vue
@@ -12,7 +12,7 @@
 import { computed } from 'vue';
 import * as misskey from 'misskey-js';
 import MkUserInfo from '@/components/MkUserInfo.vue';
-import MkPagination from '@/components/ui/pagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
 
 const props = defineProps<{
 	user: misskey.entities.User;
diff --git a/packages/client/src/pages/user/gallery.vue b/packages/client/src/pages/user/gallery.vue
index 786268497e..3b6768e64a 100644
--- a/packages/client/src/pages/user/gallery.vue
+++ b/packages/client/src/pages/user/gallery.vue
@@ -12,7 +12,7 @@
 import { computed } from 'vue';
 import * as misskey from 'misskey-js';
 import MkGalleryPostPreview from '@/components/MkGalleryPostPreview.vue';
-import MkPagination from '@/components/ui/pagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
 
 const props = withDefaults(defineProps<{
 	user: misskey.entities.User;
diff --git a/packages/client/src/pages/user/home.vue b/packages/client/src/pages/user/home.vue
index 7ab363efee..352db4616e 100644
--- a/packages/client/src/pages/user/home.vue
+++ b/packages/client/src/pages/user/home.vue
@@ -114,11 +114,11 @@ import * as misskey from 'misskey-js';
 import XUserTimeline from './index.timeline.vue';
 import XNote from '@/components/MkNote.vue';
 import MkFollowButton from '@/components/MkFollowButton.vue';
-import MkContainer from '@/components/ui/container.vue';
-import MkFolder from '@/components/ui/folder.vue';
+import MkContainer from '@/components/MkContainer.vue';
+import MkFolder from '@/components/MkFolder.vue';
 import MkRemoteCaution from '@/components/MkRemoteCaution.vue';
 import MkTab from '@/components/MkTab.vue';
-import MkInfo from '@/components/ui/info.vue';
+import MkInfo from '@/components/MkInfo.vue';
 import { getScrollPosition } from '@/scripts/scroll';
 import { getUserMenu } from '@/scripts/get-user-menu';
 import number from '@/filters/number';
diff --git a/packages/client/src/pages/user/index.activity.vue b/packages/client/src/pages/user/index.activity.vue
index 30e5a56f39..43c2ed8b04 100644
--- a/packages/client/src/pages/user/index.activity.vue
+++ b/packages/client/src/pages/user/index.activity.vue
@@ -16,7 +16,7 @@
 <script lang="ts" setup>
 import { } from 'vue';
 import * as misskey from 'misskey-js';
-import MkContainer from '@/components/ui/container.vue';
+import MkContainer from '@/components/MkContainer.vue';
 import MkChart from '@/components/MkChart.vue';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
diff --git a/packages/client/src/pages/user/index.photos.vue b/packages/client/src/pages/user/index.photos.vue
index df733849fd..5c9a73dcb7 100644
--- a/packages/client/src/pages/user/index.photos.vue
+++ b/packages/client/src/pages/user/index.photos.vue
@@ -24,7 +24,7 @@ import * as misskey from 'misskey-js';
 import { getStaticImageUrl } from '@/scripts/get-static-image-url';
 import { notePage } from '@/filters/note';
 import * as os from '@/os';
-import MkContainer from '@/components/ui/container.vue';
+import MkContainer from '@/components/MkContainer.vue';
 import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
 import { defaultStore } from '@/store';
 
diff --git a/packages/client/src/pages/user/pages.vue b/packages/client/src/pages/user/pages.vue
index 69bf926ebe..bd16c46681 100644
--- a/packages/client/src/pages/user/pages.vue
+++ b/packages/client/src/pages/user/pages.vue
@@ -10,7 +10,7 @@
 import { computed } from 'vue';
 import * as misskey from 'misskey-js';
 import MkPagePreview from '@/components/MkPagePreview.vue';
-import MkPagination from '@/components/ui/pagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
 
 const props = defineProps<{
 	user: misskey.entities.User;
diff --git a/packages/client/src/pages/user/reactions.vue b/packages/client/src/pages/user/reactions.vue
index f1e8f61b08..7e84e100a4 100644
--- a/packages/client/src/pages/user/reactions.vue
+++ b/packages/client/src/pages/user/reactions.vue
@@ -16,7 +16,7 @@
 <script lang="ts" setup>
 import { computed } from 'vue';
 import * as misskey from 'misskey-js';
-import MkPagination from '@/components/ui/pagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
 import MkNote from '@/components/MkNote.vue';
 import MkReactionIcon from '@/components/MkReactionIcon.vue';
 
diff --git a/packages/client/src/pages/welcome.entrance.a.vue b/packages/client/src/pages/welcome.entrance.a.vue
index 6452280f5b..827162a0c0 100644
--- a/packages/client/src/pages/welcome.entrance.a.vue
+++ b/packages/client/src/pages/welcome.entrance.a.vue
@@ -52,7 +52,7 @@ import XTimeline from './welcome.timeline.vue';
 import MarqueeText from '@/components/MkMarquee.vue';
 import XSigninDialog from '@/components/MkSigninDialog.vue';
 import XSignupDialog from '@/components/MkSignupDialog.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import XNote from '@/components/MkNote.vue';
 import MkFeaturedPhotos from '@/components/MkFeaturedPhotos.vue';
 import { host, instanceName } from '@/config';
diff --git a/packages/client/src/pages/welcome.entrance.b.vue b/packages/client/src/pages/welcome.entrance.b.vue
index 46cf8c2289..4bf117590a 100644
--- a/packages/client/src/pages/welcome.entrance.b.vue
+++ b/packages/client/src/pages/welcome.entrance.b.vue
@@ -40,7 +40,7 @@ import { defineComponent } from 'vue';
 import { toUnicode } from 'punycode/';
 import XSigninDialog from '@/components/MkSigninDialog.vue';
 import XSignupDialog from '@/components/MkSignupDialog.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import XNote from '@/components/MkNote.vue';
 import MkFeaturedPhotos from '@/components/MkFeaturedPhotos.vue';
 import XTimeline from './welcome.timeline.vue';
diff --git a/packages/client/src/pages/welcome.entrance.c.vue b/packages/client/src/pages/welcome.entrance.c.vue
index 6422d29234..a590834a4c 100644
--- a/packages/client/src/pages/welcome.entrance.c.vue
+++ b/packages/client/src/pages/welcome.entrance.c.vue
@@ -60,7 +60,7 @@ import { defineComponent } from 'vue';
 import { toUnicode } from 'punycode/';
 import XSigninDialog from '@/components/MkSigninDialog.vue';
 import XSignupDialog from '@/components/MkSignupDialog.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import XNote from '@/components/MkNote.vue';
 import MkFeaturedPhotos from '@/components/MkFeaturedPhotos.vue';
 import XTimeline from './welcome.timeline.vue';
diff --git a/packages/client/src/pages/welcome.setup.vue b/packages/client/src/pages/welcome.setup.vue
index eb6e29eb7f..d25651e2a3 100644
--- a/packages/client/src/pages/welcome.setup.vue
+++ b/packages/client/src/pages/welcome.setup.vue
@@ -23,7 +23,7 @@
 
 <script lang="ts" setup>
 import { } from 'vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/form/input.vue';
 import { host } from '@/config';
 import * as os from '@/os';
diff --git a/packages/client/src/ui/classic.header.vue b/packages/client/src/ui/classic.header.vue
index 34248486ce..306d32c597 100644
--- a/packages/client/src/ui/classic.header.vue
+++ b/packages/client/src/ui/classic.header.vue
@@ -45,7 +45,7 @@ import { search } from '@/scripts/search';
 import * as os from '@/os';
 import { navbarItemDef } from '@/navbar';
 import { openAccountMenu } from '@/account';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 
 export default defineComponent({
 	components: {
diff --git a/packages/client/src/ui/classic.sidebar.vue b/packages/client/src/ui/classic.sidebar.vue
index 3d15f5fcb0..7479c1c9c6 100644
--- a/packages/client/src/ui/classic.sidebar.vue
+++ b/packages/client/src/ui/classic.sidebar.vue
@@ -47,7 +47,7 @@ import { search } from '@/scripts/search';
 import * as os from '@/os';
 import { navbarItemDef } from '@/navbar';
 import { openAccountMenu } from '@/account';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import { StickySidebar } from '@/scripts/sticky-sidebar';
 //import MisskeyLogo from '@assets/client/misskey.svg';
 
diff --git a/packages/client/src/ui/deck.vue b/packages/client/src/ui/deck.vue
index f8263b1b9e..3c0c2a44b6 100644
--- a/packages/client/src/ui/deck.vue
+++ b/packages/client/src/ui/deck.vue
@@ -79,7 +79,7 @@ import { deckStore, addColumn as addColumnToStore, loadDeck, getProfiles, delete
 import DeckColumnCore from '@/ui/deck/column-core.vue';
 import XSidebar from '@/ui/_common_/navbar.vue';
 import XDrawerMenu from '@/ui/_common_/navbar-for-mobile.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import { getScrollContainer } from '@/scripts/scroll';
 import * as os from '@/os';
 import { navbarItemDef } from '@/navbar';
diff --git a/packages/client/src/ui/visitor/a.vue b/packages/client/src/ui/visitor/a.vue
index 2473af549a..f8db7a9d09 100644
--- a/packages/client/src/ui/visitor/a.vue
+++ b/packages/client/src/ui/visitor/a.vue
@@ -42,8 +42,8 @@ import XHeader from './header.vue';
 import { host, instanceName } from '@/config';
 import { search } from '@/scripts/search';
 import * as os from '@/os';
-import MkPagination from '@/components/ui/pagination.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkPagination from '@/components/MkPagination.vue';
+import MkButton from '@/components/MkButton.vue';
 import { ColdDeviceStorage } from '@/store';
 import { mainRouter } from '@/router';
 
diff --git a/packages/client/src/ui/visitor/b.vue b/packages/client/src/ui/visitor/b.vue
index 07f105374d..3c308cfe5b 100644
--- a/packages/client/src/ui/visitor/b.vue
+++ b/packages/client/src/ui/visitor/b.vue
@@ -52,10 +52,10 @@ import XKanban from './kanban.vue';
 import { host, instanceName } from '@/config';
 import { search } from '@/scripts/search';
 import * as os from '@/os';
-import MkPagination from '@/components/ui/pagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
 import XSigninDialog from '@/components/MkSigninDialog.vue';
 import XSignupDialog from '@/components/MkSignupDialog.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 import { ColdDeviceStorage, defaultStore } from '@/store';
 import { mainRouter } from '@/router';
 import { PageMetadata, provideMetadataReceiver, setPageMetadata } from '@/scripts/page-metadata';
diff --git a/packages/client/src/ui/visitor/kanban.vue b/packages/client/src/ui/visitor/kanban.vue
index 30c2fbc6c7..51e47f277d 100644
--- a/packages/client/src/ui/visitor/kanban.vue
+++ b/packages/client/src/ui/visitor/kanban.vue
@@ -41,10 +41,10 @@
 import { defineComponent, defineAsyncComponent } from 'vue';
 import { host, instanceName } from '@/config';
 import * as os from '@/os';
-import MkPagination from '@/components/ui/pagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
 import XSigninDialog from '@/components/MkSigninDialog.vue';
 import XSignupDialog from '@/components/MkSignupDialog.vue';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 
 export default defineComponent({
 	components: {
@@ -93,16 +93,16 @@ export default defineComponent({
 	methods: {
 		signin() {
 			os.popup(XSigninDialog, {
-				autoSet: true
+				autoSet: true,
 			}, {}, 'closed');
 		},
 
 		signup() {
 			os.popup(XSignupDialog, {
-				autoSet: true
+				autoSet: true,
 			}, {}, 'closed');
-		}
-	}
+		},
+	},
 });
 </script>
 
diff --git a/packages/client/src/widgets/activity.vue b/packages/client/src/widgets/activity.vue
index 97e354e1bb..acca21bff6 100644
--- a/packages/client/src/widgets/activity.vue
+++ b/packages/client/src/widgets/activity.vue
@@ -20,7 +20,7 @@ import XCalendar from './activity.calendar.vue';
 import XChart from './activity.chart.vue';
 import { GetFormResultType } from '@/scripts/form';
 import * as os from '@/os';
-import MkContainer from '@/components/ui/container.vue';
+import MkContainer from '@/components/MkContainer.vue';
 import { $i } from '@/account';
 import { i18n } from '@/i18n';
 
diff --git a/packages/client/src/widgets/aiscript.vue b/packages/client/src/widgets/aiscript.vue
index 6ae1a9877b..cb6d29cd99 100644
--- a/packages/client/src/widgets/aiscript.vue
+++ b/packages/client/src/widgets/aiscript.vue
@@ -18,7 +18,7 @@ import { AiScript, parse, utils } from '@syuilo/aiscript';
 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 MkContainer from '@/components/MkContainer.vue';
 import { createAiScriptEnv } from '@/scripts/aiscript/api';
 import { $i } from '@/account';
 import { i18n } from '@/i18n';
diff --git a/packages/client/src/widgets/button.vue b/packages/client/src/widgets/button.vue
index ee4e9c6423..f0148d7f4e 100644
--- a/packages/client/src/widgets/button.vue
+++ b/packages/client/src/widgets/button.vue
@@ -8,13 +8,13 @@
 
 <script lang="ts" setup>
 import { onMounted, onUnmounted, ref, watch } from 'vue';
-import { GetFormResultType } from '@/scripts/form';
+import { AiScript, parse, utils } from '@syuilo/aiscript';
 import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
+import { GetFormResultType } from '@/scripts/form';
 import * as os from '@/os';
-import { AiScript, parse, utils } from '@syuilo/aiscript';
 import { createAiScriptEnv } from '@/scripts/aiscript/api';
 import { $i } from '@/account';
-import MkButton from '@/components/ui/button.vue';
+import MkButton from '@/components/MkButton.vue';
 
 const name = 'button';
 
@@ -67,7 +67,7 @@ const run = async () => {
 		},
 		log: (type, params) => {
 			// nop
-		}
+		},
 	});
 
 	let ast;
diff --git a/packages/client/src/widgets/clock.vue b/packages/client/src/widgets/clock.vue
index 51d48b30a6..dc99b6631e 100644
--- a/packages/client/src/widgets/clock.vue
+++ b/packages/client/src/widgets/clock.vue
@@ -21,7 +21,7 @@
 import { } from 'vue';
 import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
-import MkContainer from '@/components/ui/container.vue';
+import MkContainer from '@/components/MkContainer.vue';
 import MkAnalogClock from '@/components/MkAnalogClock.vue';
 import MkDigitalClock from '@/components/MkDigitalClock.vue';
 import { timezones } from '@/scripts/timezones';
diff --git a/packages/client/src/widgets/federation.vue b/packages/client/src/widgets/federation.vue
index ec46a0ccb5..e07cab5bfa 100644
--- a/packages/client/src/widgets/federation.vue
+++ b/packages/client/src/widgets/federation.vue
@@ -22,7 +22,7 @@
 import { onMounted, onUnmounted, ref } from 'vue';
 import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
-import MkContainer from '@/components/ui/container.vue';
+import MkContainer from '@/components/MkContainer.vue';
 import MkMiniChart from '@/components/MkMiniChart.vue';
 import * as os from '@/os';
 import { useInterval } from '@/scripts/use-interval';
diff --git a/packages/client/src/widgets/instance-cloud.vue b/packages/client/src/widgets/instance-cloud.vue
index 03fda4c74a..f8e463ee33 100644
--- a/packages/client/src/widgets/instance-cloud.vue
+++ b/packages/client/src/widgets/instance-cloud.vue
@@ -16,7 +16,7 @@
 import { } from 'vue';
 import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
-import MkContainer from '@/components/ui/container.vue';
+import MkContainer from '@/components/MkContainer.vue';
 import MkTagCloud from '@/components/MkTagCloud.vue';
 import * as os from '@/os';
 import { useInterval } from '@/scripts/use-interval';
diff --git a/packages/client/src/widgets/memo.vue b/packages/client/src/widgets/memo.vue
index 14cc2a4e4a..92c4168fff 100644
--- a/packages/client/src/widgets/memo.vue
+++ b/packages/client/src/widgets/memo.vue
@@ -14,7 +14,7 @@ import { onMounted, onUnmounted, reactive, ref, watch } from 'vue';
 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 MkContainer from '@/components/MkContainer.vue';
 import { defaultStore } from '@/store';
 import { i18n } from '@/i18n';
 
diff --git a/packages/client/src/widgets/notifications.vue b/packages/client/src/widgets/notifications.vue
index a46b7e7397..2729c310a0 100644
--- a/packages/client/src/widgets/notifications.vue
+++ b/packages/client/src/widgets/notifications.vue
@@ -13,7 +13,7 @@
 import { defineAsyncComponent } from 'vue';
 import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
-import MkContainer from '@/components/ui/container.vue';
+import MkContainer from '@/components/MkContainer.vue';
 import XNotifications from '@/components/MkNotifications.vue';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
diff --git a/packages/client/src/widgets/photos.vue b/packages/client/src/widgets/photos.vue
index 41bec5dc56..e891bd6a7d 100644
--- a/packages/client/src/widgets/photos.vue
+++ b/packages/client/src/widgets/photos.vue
@@ -22,7 +22,7 @@ import { GetFormResultType } from '@/scripts/form';
 import { stream } from '@/stream';
 import { getStaticImageUrl } from '@/scripts/get-static-image-url';
 import * as os from '@/os';
-import MkContainer from '@/components/ui/container.vue';
+import MkContainer from '@/components/MkContainer.vue';
 import { defaultStore } from '@/store';
 import { i18n } from '@/i18n';
 
diff --git a/packages/client/src/widgets/rss-ticker.vue b/packages/client/src/widgets/rss-ticker.vue
index 6e5924b6be..58c16983c8 100644
--- a/packages/client/src/widgets/rss-ticker.vue
+++ b/packages/client/src/widgets/rss-ticker.vue
@@ -24,7 +24,7 @@ import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExp
 import MarqueeText from '@/components/MkMarquee.vue';
 import { GetFormResultType } from '@/scripts/form';
 import * as os from '@/os';
-import MkContainer from '@/components/ui/container.vue';
+import MkContainer from '@/components/MkContainer.vue';
 import { useInterval } from '@/scripts/use-interval';
 import { shuffle } from '@/scripts/shuffle';
 
diff --git a/packages/client/src/widgets/rss.vue b/packages/client/src/widgets/rss.vue
index 72f6249820..3258b6c028 100644
--- a/packages/client/src/widgets/rss.vue
+++ b/packages/client/src/widgets/rss.vue
@@ -17,7 +17,7 @@ import { onMounted, onUnmounted, ref, watch } from 'vue';
 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 MkContainer from '@/components/MkContainer.vue';
 import { useInterval } from '@/scripts/use-interval';
 
 const name = 'rss';
diff --git a/packages/client/src/widgets/server-metric/index.vue b/packages/client/src/widgets/server-metric/index.vue
index 3350620ad8..cf4accfa2c 100644
--- a/packages/client/src/widgets/server-metric/index.vue
+++ b/packages/client/src/widgets/server-metric/index.vue
@@ -21,7 +21,7 @@ import XNet from './net.vue';
 import XCpu from './cpu.vue';
 import XMemory from './mem.vue';
 import XDisk from './disk.vue';
-import MkContainer from '@/components/ui/container.vue';
+import MkContainer from '@/components/MkContainer.vue';
 import { GetFormResultType } from '@/scripts/form';
 import * as os from '@/os';
 import { stream } from '@/stream';
diff --git a/packages/client/src/widgets/timeline.vue b/packages/client/src/widgets/timeline.vue
index 5042632cfc..718162667d 100644
--- a/packages/client/src/widgets/timeline.vue
+++ b/packages/client/src/widgets/timeline.vue
@@ -24,7 +24,7 @@ import { onMounted, onUnmounted, reactive, ref, watch } from 'vue';
 import { GetFormResultType } from '@/scripts/form';
 import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import * as os from '@/os';
-import MkContainer from '@/components/ui/container.vue';
+import MkContainer from '@/components/MkContainer.vue';
 import XTimeline from '@/components/MkTimeline.vue';
 import { $i } from '@/account';
 import { i18n } from '@/i18n';
diff --git a/packages/client/src/widgets/trends.vue b/packages/client/src/widgets/trends.vue
index 53ae4019dd..a783c04215 100644
--- a/packages/client/src/widgets/trends.vue
+++ b/packages/client/src/widgets/trends.vue
@@ -21,7 +21,7 @@
 import { onMounted, onUnmounted, ref } from 'vue';
 import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
-import MkContainer from '@/components/ui/container.vue';
+import MkContainer from '@/components/MkContainer.vue';
 import MkMiniChart from '@/components/MkMiniChart.vue';
 import * as os from '@/os';
 import { useInterval } from '@/scripts/use-interval';
-- 
cgit v1.3.1-freya

