diff --git a/packages/frontend/.storybook/fakes.ts b/packages/frontend/.storybook/fakes.ts
index 01f1046609..ab04d3e60c 100644
--- a/packages/frontend/.storybook/fakes.ts
+++ b/packages/frontend/.storybook/fakes.ts
@@ -47,18 +47,7 @@ export function clip(id = 'someclipid', name = 'Some Clip'): entities.Clip {
 		createdAt: '2016-12-28T22:49:51.000Z',
 		lastClippedAt: null,
 		userId: 'someuserid',
-		user: {
-			id: 'someuserid',
-			name: 'Misskey User',
-			username: 'miskist',
-			host: 'misskey-hub.net',
-			avatarUrl: 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/about-icon.png?raw=true',
-			avatarBlurhash: 'eQFRshof5NWBRi},juayfPju53WB?0ofs;s*a{ofjuay^SoMEJR%ay',
-			avatarDecorations: [],
-			emojis: {},
-			badgeRoles: [],
-			onlineStatus: 'unknown',
-		},
+		user: userLite(),
 		notesCount: undefined,
 		name,
 		description: 'Some clip description',
@@ -125,6 +114,15 @@ export function file(isSensitive = false) {
 	};
 }
 
+export function folder(id = 'somefolderid', name = 'Some Folder', parentId: string | null = null): entities.DriveFolder {
+	return {
+		id,
+		createdAt: '2016-12-28T22:49:51.000Z',
+		name,
+		parentId,
+	};
+}
+
 export function federationInstance(): entities.FederationInstance {
 	return {
 		id: 'someinstanceid',
@@ -154,7 +152,27 @@ export function federationInstance(): entities.FederationInstance {
 	};
 }
 
-export function userDetailed(id = 'someuserid', username = 'miskist', host:entities.UserDetailed['host'] = 'misskey-hub.net', name:entities.UserDetailed['name'] = 'Misskey User'): entities.UserDetailed {
+export function note(id = 'somenoteid'): entities.Note {
+	return {
+		id,
+		createdAt: '2016-12-28T22:49:51.000Z',
+		deletedAt: null,
+		text: 'some note',
+		cw: null,
+		userId: 'someuserid',
+		user: userLite(),
+		visibility: 'public',
+		reactionAcceptance: 'nonSensitiveOnly',
+		reactionEmojis: {},
+		reactions: {},
+		myReaction: null,
+		reactionCount: 0,
+		renoteCount: 0,
+		repliesCount: 0,
+	};
+}
+
+export function userLite(id = 'someuserid', username = 'miskist', host: entities.UserDetailed['host'] = 'misskey-hub.net', name: entities.UserDetailed['name'] = 'Misskey User'): entities.UserLite {
 	return {
 		id,
 		username,
@@ -165,6 +183,12 @@ export function userDetailed(id = 'someuserid', username = 'miskist', host:entit
 		avatarBlurhash: 'eQFRshof5NWBRi},juayfPju53WB?0ofs;s*a{ofjuay^SoMEJR%ay',
 		avatarDecorations: [],
 		emojis: {},
+	};
+}
+
+export function userDetailed(id = 'someuserid', username = 'miskist', host: entities.UserDetailed['host'] = 'misskey-hub.net', name: entities.UserDetailed['name'] = 'Misskey User'): entities.UserDetailed {
+	return {
+		...userLite(id, username, host, name),
 		bannerBlurhash: 'eQA^IW^-MH8w9tE8I=S^o{$*R4RikXtSxutRozjEnNR.RQadoyozog',
 		bannerUrl: 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true',
 		birthday: '2014-06-20',
@@ -215,7 +239,7 @@ export function userDetailed(id = 'someuserid', username = 'miskist', host:entit
 		movedTo: null,
 		alsoKnownAs: null,
 		notify: 'none',
-		memo: null
+		memo: null,
 	};
 }
 
diff --git a/packages/frontend/.storybook/generate.tsx b/packages/frontend/.storybook/generate.tsx
index b94dfc53e3..52c01aaf70 100644
--- a/packages/frontend/.storybook/generate.tsx
+++ b/packages/frontend/.storybook/generate.tsx
@@ -397,8 +397,7 @@ function toStories(component: string): Promise<string> {
 	const globs = await Promise.all([
 		glob('src/components/global/Mk*.vue'),
 		glob('src/components/global/RouterView.vue'),
-		glob('src/components/Mk[A-C]*.vue'),
-		glob('src/components/MkDigitalClock.vue'),
+		glob('src/components/Mk[A-E]*.vue'),
 		glob('src/components/MkGalleryPostPreview.vue'),
 		glob('src/components/MkSignupServerRules.vue'),
 		glob('src/components/MkUserSetupDialog.vue'),
diff --git a/packages/frontend/src/components/MkDateSeparatedList.stories.impl.ts b/packages/frontend/src/components/MkDateSeparatedList.stories.impl.ts
new file mode 100644
index 0000000000..0e5635754c
--- /dev/null
+++ b/packages/frontend/src/components/MkDateSeparatedList.stories.impl.ts
@@ -0,0 +1,7 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import MkDateSeparatedList from './MkDateSeparatedList.vue';
+void MkDateSeparatedList;
diff --git a/packages/frontend/src/components/MkDialog.stories.impl.ts b/packages/frontend/src/components/MkDialog.stories.impl.ts
new file mode 100644
index 0000000000..2d8d3661f2
--- /dev/null
+++ b/packages/frontend/src/components/MkDialog.stories.impl.ts
@@ -0,0 +1,159 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { action } from '@storybook/addon-actions';
+import { expect, userEvent, waitFor, within } from '@storybook/test';
+import { StoryObj } from '@storybook/vue3';
+import { i18n } from '@/i18n.js';
+import MkDialog from './MkDialog.vue';
+const Base = {
+	render(args) {
+		return {
+			components: {
+				MkDialog,
+			},
+			setup() {
+				return {
+					args,
+				};
+			},
+			computed: {
+				props() {
+					return {
+						...this.args,
+					};
+				},
+				events() {
+					return {
+						done: action('done'),
+						closed: action('closed'),
+					};
+				},
+			},
+			template: '<MkDialog v-bind="props" v-on="events" />',
+		};
+	},
+	args: {
+		text: 'Hello, world!',
+	},
+	parameters: {
+		layout: 'centered',
+	},
+} satisfies StoryObj<typeof MkDialog>;
+export const Success = {
+	...Base,
+	args: {
+		...Base.args,
+		type: 'success',
+	},
+} satisfies StoryObj<typeof MkDialog>;
+export const Error = {
+	...Base,
+	args: {
+		...Base.args,
+		type: 'error',
+	},
+} satisfies StoryObj<typeof MkDialog>;
+export const Warning = {
+	...Base,
+	args: {
+		...Base.args,
+		type: 'warning',
+	},
+} satisfies StoryObj<typeof MkDialog>;
+export const Info = {
+	...Base,
+	args: {
+		...Base.args,
+		type: 'info',
+	},
+} satisfies StoryObj<typeof MkDialog>;
+export const Question = {
+	...Base,
+	args: {
+		...Base.args,
+		type: 'question',
+	},
+} satisfies StoryObj<typeof MkDialog>;
+export const Waiting = {
+	...Base,
+	args: {
+		...Base.args,
+		type: 'waiting',
+	},
+} satisfies StoryObj<typeof MkDialog>;
+export const DialogWithActions = {
+	...Question,
+	args: {
+		...Question.args,
+		text: i18n.ts.areYouSure,
+		actions: [
+			{
+				text: i18n.ts.yes,
+				primary: true,
+				callback() {
+					action('YES')();
+				},
+			},
+			{
+				text: i18n.ts.no,
+				callback() {
+					action('NO')();
+				},
+			},
+		],
+	},
+} satisfies StoryObj<typeof MkDialog>;
+export const DialogWithDangerActions = {
+	...Warning,
+	args: {
+		...Warning.args,
+		text: i18n.ts.resetAreYouSure,
+		actions: [
+			{
+				text: i18n.ts.yes,
+				danger: true,
+				primary: true,
+				callback() {
+					action('YES')();
+				},
+			},
+			{
+				text: i18n.ts.no,
+				callback() {
+					action('NO')();
+				},
+			},
+		],
+	},
+} satisfies StoryObj<typeof MkDialog>;
+export const DialogWithInput = {
+	...Question,
+	args: {
+		...Question.args,
+		title: 'Hello, world!',
+		text: undefined,
+		input: {
+			placeholder: i18n.ts.inputMessageHere,
+			type: 'text',
+			default: null,
+			minLength: 2,
+			maxLength: 3,
+		},
+	},
+	async play({ canvasElement }) {
+		const canvas = within(canvasElement);
+		await expect(canvasElement).toHaveTextContent(i18n.tsx._dialog.charactersBelow({ current: 0, min: 2 }));
+		const okButton = canvas.getByRole('button', { name: i18n.ts.ok });
+		await expect(okButton).toBeDisabled();
+		const input = canvas.getByRole<HTMLInputElement>('combobox');
+		await waitFor(() => userEvent.hover(input));
+		await waitFor(() => userEvent.click(input));
+		await waitFor(() => userEvent.type(input, 'M'));
+		await expect(canvasElement).toHaveTextContent(i18n.tsx._dialog.charactersBelow({ current: 1, min: 2 }));
+		await waitFor(() => userEvent.type(input, 'i'));
+		await expect(okButton).toBeEnabled();
+	},
+} satisfies StoryObj<typeof MkDialog>;
diff --git a/packages/frontend/src/components/MkDivider.stories.impl.ts b/packages/frontend/src/components/MkDivider.stories.impl.ts
new file mode 100644
index 0000000000..a593111987
--- /dev/null
+++ b/packages/frontend/src/components/MkDivider.stories.impl.ts
@@ -0,0 +1,7 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import MkDivider from './MkDivider.vue';
+void MkDivider;
diff --git a/packages/frontend/src/components/MkDonation.stories.impl.ts b/packages/frontend/src/components/MkDonation.stories.impl.ts
new file mode 100644
index 0000000000..27d6b7df6c
--- /dev/null
+++ b/packages/frontend/src/components/MkDonation.stories.impl.ts
@@ -0,0 +1,54 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { action } from '@storybook/addon-actions';
+import { StoryObj } from '@storybook/vue3';
+import { onBeforeUnmount } from 'vue';
+import MkDonation from './MkDonation.vue';
+import { instance } from '@/instance.js';
+export const Default = {
+	render(args) {
+		return {
+			components: {
+				MkDonation,
+			},
+			setup() {
+				return {
+					args,
+				};
+			},
+			computed: {
+				props() {
+					return {
+						...this.args,
+					};
+				},
+				events() {
+					return {
+						closed: action('closed'),
+					};
+				},
+			},
+			template: '<MkDonation v-bind="props" v-on="events" />',
+		};
+	},
+	args: {
+		// @ts-expect-error name is used for mocking instance
+		name: 'Misskey Hub',
+	},
+	decorators: [
+		(_, { args }) => ({
+			setup() {
+				// @ts-expect-error name is used for mocking instance
+				instance.name = args.name;
+				onBeforeUnmount(() => instance.name = null);
+			},
+			template: '<story/>',
+		}),
+	],
+	parameters: {
+		layout: 'centered',
+	},
+} satisfies StoryObj<typeof MkDonation>;
diff --git a/packages/frontend/src/components/MkDrive.file.stories.impl.ts b/packages/frontend/src/components/MkDrive.file.stories.impl.ts
new file mode 100644
index 0000000000..5f6e6a0667
--- /dev/null
+++ b/packages/frontend/src/components/MkDrive.file.stories.impl.ts
@@ -0,0 +1,48 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { action } from '@storybook/addon-actions';
+import { StoryObj } from '@storybook/vue3';
+import MkDrive_file from './MkDrive.file.vue';
+import { file } from '../../.storybook/fakes.js';
+export const Default = {
+	render(args) {
+		return {
+			components: {
+				MkDrive_file,
+			},
+			setup() {
+				return {
+					args,
+				};
+			},
+			computed: {
+				props() {
+					return {
+						...this.args,
+					};
+				},
+				events() {
+					return {
+						chosen: action('chosen'),
+						dragstart: action('dragstart'),
+						dragend: action('dragend'),
+					};
+				},
+			},
+			template: '<MkDrive_file v-bind="props" v-on="events" />',
+		};
+	},
+	args: {
+		file: file(),
+	},
+	parameters: {
+		chromatic: {
+			// NOTE: ロードが終わるまで待つ
+			delay: 3000,
+		},
+		layout: 'centered',
+	},
+} satisfies StoryObj<typeof MkDrive_file>;
diff --git a/packages/frontend/src/components/MkDrive.folder.stories.impl.ts b/packages/frontend/src/components/MkDrive.folder.stories.impl.ts
new file mode 100644
index 0000000000..5f8ef48520
--- /dev/null
+++ b/packages/frontend/src/components/MkDrive.folder.stories.impl.ts
@@ -0,0 +1,70 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { action } from '@storybook/addon-actions';
+import { StoryObj } from '@storybook/vue3';
+import { http, HttpResponse } from 'msw';
+import * as Misskey from 'misskey-js';
+import MkDrive_folder from './MkDrive.folder.vue';
+import { folder } from '../../.storybook/fakes.js';
+import { commonHandlers } from '../../.storybook/mocks.js';
+export const Default = {
+	render(args) {
+		return {
+			components: {
+				MkDrive_folder,
+			},
+			setup() {
+				return {
+					args,
+				};
+			},
+			computed: {
+				props() {
+					return {
+						...this.args,
+					};
+				},
+				events() {
+					return {
+						chosen: action('chosen'),
+						move: action('move'),
+						upload: action('upload'),
+						removeFile: action('removeFile'),
+						removeFolder: action('removeFolder'),
+						dragstart: action('dragstart'),
+						dragend: action('dragend'),
+					};
+				},
+			},
+			template: '<MkDrive_folder v-bind="props" v-on="events" />',
+		};
+	},
+	args: {
+		folder: folder(),
+	},
+	parameters: {
+		layout: 'centered',
+		msw: {
+			handlers: [
+				...commonHandlers,
+				http.post('/api/drive/folders/delete', async ({ request }) => {
+					action('POST /api/drive/folders/delete')(await request.json());
+					return HttpResponse.json(undefined, { status: 204 });
+				}),
+				http.post('/api/drive/folders/update', async ({ request }) => {
+					const req = await request.json() as Misskey.entities.DriveFoldersUpdateRequest;
+					action('POST /api/drive/folders/update')(req);
+					return HttpResponse.json({
+						...folder(),
+						id: req.folderId,
+						name: req.name ?? folder().name,
+						parentId: req.parentId ?? folder().parentId,
+					});
+				}),
+			],
+		},
+	},
+} satisfies StoryObj<typeof MkDrive_folder>;
diff --git a/packages/frontend/src/components/MkDrive.navFolder.stories.impl.ts b/packages/frontend/src/components/MkDrive.navFolder.stories.impl.ts
new file mode 100644
index 0000000000..9d49f24fa4
--- /dev/null
+++ b/packages/frontend/src/components/MkDrive.navFolder.stories.impl.ts
@@ -0,0 +1,7 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import MkDrive_navFolder from './MkDrive.navFolder.vue';
+void MkDrive_navFolder;
diff --git a/packages/frontend/src/components/MkDrive.stories.impl.ts b/packages/frontend/src/components/MkDrive.stories.impl.ts
new file mode 100644
index 0000000000..fe20e61415
--- /dev/null
+++ b/packages/frontend/src/components/MkDrive.stories.impl.ts
@@ -0,0 +1,82 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { action } from '@storybook/addon-actions';
+import { StoryObj } from '@storybook/vue3';
+import { http, HttpResponse } from 'msw';
+import * as Misskey from 'misskey-js';
+import MkDrive from './MkDrive.vue';
+import { file, folder } from '../../.storybook/fakes.js';
+import { commonHandlers } from '../../.storybook/mocks.js';
+export const Default = {
+	render(args) {
+		return {
+			components: {
+				MkDrive,
+			},
+			setup() {
+				return {
+					args,
+				};
+			},
+			computed: {
+				props() {
+					return {
+						...this.args,
+					};
+				},
+				events() {
+					return {
+						selected: action('selected'),
+						'change-selection': action('change-selection'),
+						'move-root': action('move-root'),
+						cd: action('cd'),
+						'open-folder': action('open-folder'),
+					};
+				},
+			},
+			template: '<MkDrive v-bind="props" v-on="events" />',
+		};
+	},
+	parameters: {
+		chromatic: {
+			// NOTE: ロードが終わるまで待つ
+			delay: 3000,
+		},
+		layout: 'centered',
+		msw: {
+			handlers: [
+				...commonHandlers,
+				http.post('/api/drive/files', async ({ request }) => {
+					action('POST /api/drive/files')(await request.json());
+					return HttpResponse.json([file()]);
+				}),
+				http.post('/api/drive/folders', async ({ request }) => {
+					action('POST /api/drive/folders')(await request.json());
+					return HttpResponse.json([folder(crypto.randomUUID())]);
+				}),
+				http.post('/api/drive/folders/create', async ({ request }) => {
+					const req = await request.json() as Misskey.entities.DriveFoldersCreateRequest;
+					action('POST /api/drive/folders/create')(req);
+					return HttpResponse.json(folder(crypto.randomUUID(), req.name, req.parentId));
+				}),
+				http.post('/api/drive/folders/delete', async ({ request }) => {
+					action('POST /api/drive/folders/delete')(await request.json());
+					return HttpResponse.json(undefined, { status: 204 });
+				}),
+				http.post('/api/drive/folders/update', async ({ request }) => {
+					const req = await request.json() as Misskey.entities.DriveFoldersUpdateRequest;
+					action('POST /api/drive/folders/update')(req);
+					return HttpResponse.json({
+						...folder(),
+						id: req.folderId,
+						name: req.name ?? folder().name,
+						parentId: req.parentId ?? folder().parentId,
+					});
+				}),
+			]
+		},
+	},
+} satisfies StoryObj<typeof MkDrive>;
diff --git a/packages/frontend/src/components/MkDriveFileThumbnail.stories.impl.ts b/packages/frontend/src/components/MkDriveFileThumbnail.stories.impl.ts
new file mode 100644
index 0000000000..3fa24d7edb
--- /dev/null
+++ b/packages/frontend/src/components/MkDriveFileThumbnail.stories.impl.ts
@@ -0,0 +1,41 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { StoryObj } from '@storybook/vue3';
+import MkDriveFileThumbnail from './MkDriveFileThumbnail.vue';
+import { file } from '../../.storybook/fakes.js';
+export const Default = {
+	render(args) {
+		return {
+			components: {
+				MkDriveFileThumbnail,
+			},
+			setup() {
+				return {
+					args,
+				};
+			},
+			computed: {
+				props() {
+					return {
+						...this.args,
+					};
+				},
+			},
+			template: '<MkDriveFileThumbnail v-bind="props" />',
+		};
+	},
+	args: {
+		file: file(),
+		fit: 'contain',
+	},
+	parameters: {
+		chromatic: {
+			// NOTE: ロードが終わるまで待つ
+			delay: 3000,
+		},
+		layout: 'centered',
+	},
+} satisfies StoryObj<typeof MkDriveFileThumbnail>;
diff --git a/packages/frontend/src/components/MkDriveFileThumbnail.vue b/packages/frontend/src/components/MkDriveFileThumbnail.vue
index 706c9a55c7..2c47a70970 100644
--- a/packages/frontend/src/components/MkDriveFileThumbnail.vue
+++ b/packages/frontend/src/components/MkDriveFileThumbnail.vue
@@ -26,7 +26,7 @@ import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
 
 const props = defineProps<{
 	file: Misskey.entities.DriveFile;
-	fit: string;
+	fit: 'cover' | 'contain';
 }>();
 
 const is = computed(() => {
diff --git a/packages/frontend/src/components/MkDriveSelectDialog.stories.impl.ts b/packages/frontend/src/components/MkDriveSelectDialog.stories.impl.ts
new file mode 100644
index 0000000000..fe8f705165
--- /dev/null
+++ b/packages/frontend/src/components/MkDriveSelectDialog.stories.impl.ts
@@ -0,0 +1,7 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import MkDriveSelectDialog from './MkDriveSelectDialog.vue';
+void MkDriveSelectDialog;
diff --git a/packages/frontend/src/components/MkDriveWindow.stories.impl.ts b/packages/frontend/src/components/MkDriveWindow.stories.impl.ts
new file mode 100644
index 0000000000..faa1f7fd5f
--- /dev/null
+++ b/packages/frontend/src/components/MkDriveWindow.stories.impl.ts
@@ -0,0 +1,7 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import MkDriveWindow from './MkDriveWindow.vue';
+void MkDriveWindow;
diff --git a/packages/frontend/src/components/MkEmojiPicker.section.stories.impl.ts b/packages/frontend/src/components/MkEmojiPicker.section.stories.impl.ts
new file mode 100644
index 0000000000..69aef577de
--- /dev/null
+++ b/packages/frontend/src/components/MkEmojiPicker.section.stories.impl.ts
@@ -0,0 +1,7 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import MkEmojiPicker_section from './MkEmojiPicker.section.vue';
+void MkEmojiPicker_section;
diff --git a/packages/frontend/src/components/MkEmojiPicker.stories.impl.ts b/packages/frontend/src/components/MkEmojiPicker.stories.impl.ts
new file mode 100644
index 0000000000..d38d8de808
--- /dev/null
+++ b/packages/frontend/src/components/MkEmojiPicker.stories.impl.ts
@@ -0,0 +1,54 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { action } from '@storybook/addon-actions';
+import { expect, userEvent, waitFor, within } from '@storybook/test';
+import { StoryObj } from '@storybook/vue3';
+import { i18n } from '@/i18n.js';
+import MkEmojiPicker from './MkEmojiPicker.vue';
+export const Default = {
+	render(args) {
+		return {
+			components: {
+				MkEmojiPicker,
+			},
+			setup() {
+				return {
+					args,
+				};
+			},
+			computed: {
+				props() {
+					return {
+						...this.args,
+					};
+				},
+				events() {
+					return {
+						chosen: action('chosen'),
+					};
+				},
+			},
+			template: '<MkEmojiPicker v-bind="props" v-on="events" />',
+		};
+	},
+	async play({ canvasElement }) {
+		const canvas = within(canvasElement);
+		const faceSection = canvas.getByText(/face/i);
+		await waitFor(() => userEvent.click(faceSection));
+		const grinning = canvasElement.querySelector('[data-emoji="😀"]');
+		await expect(grinning).toBeInTheDocument();
+		if (grinning == null) throw new Error(); // NOTE: not called
+		await waitFor(() => userEvent.click(grinning));
+		const recentUsedSection = canvas.getByText(new RegExp(i18n.ts.recentUsed)).parentElement;
+		await expect(recentUsedSection).toBeInTheDocument();
+		if (recentUsedSection == null) throw new Error(); // NOTE: not called
+		await expect(within(recentUsedSection).getByAltText('😀')).toBeInTheDocument();
+		await expect(within(recentUsedSection).queryByAltText('😬')).toEqual(null);
+	},
+	parameters: {
+		layout: 'centered',
+	},
+} satisfies StoryObj<typeof MkEmojiPicker>;
diff --git a/packages/frontend/src/components/MkEmojiPickerDialog.stories.impl.ts b/packages/frontend/src/components/MkEmojiPickerDialog.stories.impl.ts
new file mode 100644
index 0000000000..131087ad45
--- /dev/null
+++ b/packages/frontend/src/components/MkEmojiPickerDialog.stories.impl.ts
@@ -0,0 +1,7 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import MkEmojiPickerDialog from './MkEmojiPickerDialog.vue';
+void MkEmojiPickerDialog;