diff --git a/src/client/components/emoji-picker.section.vue b/src/client/components/emoji-picker.section.vue
new file mode 100644
index 0000000000..a12493ce0d
--- /dev/null
+++ b/src/client/components/emoji-picker.section.vue
@@ -0,0 +1,52 @@
+<template>
+<section>
+	<header class="_acrylic" @click="shown = !shown">
+		<Fa :icon="shown ? faChevronDown : faChevronUp" :key="shown" fixed-width class="toggle"/> <slot></slot> ({{ emojis.length }})
+	</header>
+	<div v-if="shown">
+		<button v-for="emoji in emojis"
+			class="_button"
+			@click="chosen(emoji, $event)"
+			:key="emoji"
+		>
+			<MkEmoji :emoji="emoji" :normal="true"/>
+		</button>
+	</div>
+</section>
+</template>
+
+<script lang="ts">
+import { defineComponent, markRaw } from 'vue';
+import { faChevronUp, faChevronDown } from '@fortawesome/free-solid-svg-icons';
+import { getStaticImageUrl } from '@/scripts/get-static-image-url';
+
+export default defineComponent({
+	props: {
+		emojis: {
+			required: true,
+		},
+		initialShown: {
+			required: false
+		}
+	},
+
+	emits: ['chosen'],
+
+	data() {
+		return {
+			getStaticImageUrl,
+			shown: this.initialShown,
+			faChevronUp, faChevronDown,
+		};
+	},
+
+	methods: {
+		chosen(emoji: any, ev) {
+			this.$parent.chosen(emoji, ev);
+		},
+	}
+});
+</script>
+
+<style lang="scss" scoped>
+</style>
diff --git a/src/client/components/emoji-picker.vue b/src/client/components/emoji-picker.vue
index 93530e16c8..b11f0a62fa 100644
--- a/src/client/components/emoji-picker.vue
+++ b/src/client/components/emoji-picker.vue
@@ -28,7 +28,7 @@
 			</div>
 		</section>
 
-		<div class="index">
+		<div class="index" v-if="tab === 'index'">
 			<section v-if="showPinned">
 				<div>
 					<button v-for="emoji in pinned"
@@ -53,37 +53,31 @@
 					</button>
 				</div>
 			</section>
-
-			<div class="arrow"><Fa :icon="faChevronDown"/></div>
 		</div>
-
-		<section v-for="category in customEmojiCategories" :key="'custom:' + category" class="custom">
-			<header class="_acrylic" v-appear="() => visibleCategories[category] = true">{{ category || $ts.other }}</header>
-			<div v-if="visibleCategories[category]">
-				<button v-for="emoji in customEmojis.filter(e => e.category === category)"
-					class="_button"
-					:title="emoji.name"
-					@click="chosen(emoji, $event)"
-					:key="emoji.name"
-				>
-					<img :src="$store.state.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/>
-				</button>
-			</div>
-		</section>
-
-		<section v-for="category in categories" :key="category.name" class="unicode">
-			<header class="_acrylic" v-appear="() => category.isActive = true"><Fa :icon="category.icon" fixed-width/> {{ category.name }}</header>
-			<div v-if="category.isActive">
-				<button v-for="emoji in emojilist.filter(e => e.category === category.name)"
-					class="_button"
-					:title="emoji.name"
-					@click="chosen(emoji, $event)"
-					:key="emoji.name"
-				>
-					<MkEmoji :emoji="emoji.char"/>
-				</button>
-			</div>
-		</section>
+		<div v-appear="() => showingCustomEmojis = true">
+			<header class="_acrylic">{{ $ts.customEmojis }}</header>
+			<template v-if="showingCustomEmojis">
+				<XSection v-for="category in customEmojiCategories" :key="'custom:' + category" :initial-shown="false" :emojis="customEmojis.filter(e => e.category === category).map(e => ':' + e.name + ':')">{{ category || $ts.other }}</XSection>
+			</template>
+		</div>
+		<div v-appear="() => showingEmojis = true">
+			<header class="_acrylic">{{ $ts.emoji }}</header>
+			<template v-if="showingEmojis">
+				<XSection v-for="category in categories" :emojis="emojilist.filter(e => e.category === category).map(e => e.char)">{{ category }}</XSection>
+			</template>
+		</div>
+		<div v-appear="() => showingTags = true">
+			<header class="_acrylic">{{ $ts.tags }}</header>
+			<template v-if="showingTags">
+				<XSection v-for="tag in emojiTags" :emojis="customEmojis.filter(e => e.aliases.includes(tag)).map(e => ':' + e.name + ':')">{{ tag }}</XSection>
+			</template>
+		</div>
+	</div>
+	<div class="tabs">
+		<button class="_button tab" :class="{ active: tab === 'index' }" @click="tab = 'index'"><Fa :icon="faAsterisk" fixed-width/></button>
+		<button class="_button tab" :class="{ active: tab === 'custom' }" @click="tab = 'custom'"><Fa :icon="faLaugh" fixed-width/></button>
+		<button class="_button tab" :class="{ active: tab === 'unicode' }" @click="tab = 'unicode'"><Fa :icon="faLeaf" fixed-width/></button>
+		<button class="_button tab" :class="{ active: tab === 'tags' }" @click="tab = 'tags'"><Fa :icon="faHashtag" fixed-width/></button>
 	</div>
 </div>
 </template>
@@ -92,15 +86,20 @@
 import { defineComponent, markRaw } from 'vue';
 import { emojilist } from '../../misc/emojilist';
 import { getStaticImageUrl } from '@/scripts/get-static-image-url';
-import { faAsterisk, faLeaf, faUtensils, faFutbol, faCity, faDice, faGlobe, faClock, faUser, faChevronDown } from '@fortawesome/free-solid-svg-icons';
+import { faAsterisk, faLeaf, faUtensils, faFutbol, faCity, faDice, faGlobe, faClock, faUser, faChevronDown, faShapes, faBicycle, faHashtag } from '@fortawesome/free-solid-svg-icons';
 import { faHeart, faFlag, faLaugh } from '@fortawesome/free-regular-svg-icons';
 import Particle from '@/components/particle.vue';
 import * as os from '@/os';
 import { isDeviceTouch } from '@/scripts/is-device-touch';
 import { isMobile } from '@/scripts/is-mobile';
-import { emojiCategories } from '@/instance';
+import { emojiCategories, emojiTags } from '@/instance';
+import XSection from './emoji-picker.section.vue';
 
 export default defineComponent({
+	components: {
+		XSection
+	},
+
 	props: {
 		showPinned: {
 			required: false,
@@ -122,50 +121,17 @@ export default defineComponent({
 			height: this.asReactionPicker ? this.$store.state.reactionPickerHeight : 2,
 			big: this.asReactionPicker ? isDeviceTouch : false,
 			customEmojiCategories: emojiCategories,
+			emojiTags,
 			customEmojis: this.$instance.emojis,
-			visibleCategories: {},
 			q: null,
 			searchResultCustom: [],
 			searchResultUnicode: [],
-			faGlobe, faClock, faChevronDown,
-			categories: [{
-				name: 'face',
-				icon: faLaugh,
-				isActive: false
-			}, {
-				name: 'people',
-				icon: faUser,
-				isActive: false
-			}, {
-				name: 'animals_and_nature',
-				icon: faLeaf,
-				isActive: false
-			}, {
-				name: 'food_and_drink',
-				icon: faUtensils,
-				isActive: false
-			}, {
-				name: 'activity',
-				icon: faFutbol,
-				isActive: false
-			}, {
-				name: 'travel_and_places',
-				icon: faCity,
-				isActive: false
-			}, {
-				name: 'objects',
-				icon: faDice,
-				isActive: false
-			}, {
-				name: 'symbols',
-				icon: faHeart,
-				isActive: false
-			}, {
-				name: 'flags',
-				icon: faFlag,
-				isActive: false
-			}],
-			faAsterisk
+			tab: 'index',
+			showingCustomEmojis: false,
+			showingEmojis: false,
+			showingTags: false,
+			categories: ['face', 'people', 'animals_and_nature', 'food_and_drink', 'activity', 'travel_and_places', 'objects', 'symbols', 'flags'],
+			faGlobe, faClock, faChevronDown, faAsterisk, faLaugh, faUtensils, faLeaf, faShapes, faBicycle, faHashtag,
 		};
 	},
 
@@ -342,7 +308,7 @@ export default defineComponent({
 				let recents = this.$store.state.recentlyUsedEmojis;
 				recents = recents.filter((e: any) => e !== key);
 				recents.unshift(key);
-				this.$store.set('recentlyUsedEmojis', recents.splice(0, 16));
+				this.$store.set('recentlyUsedEmojis', recents.splice(0, 32));
 			}
 		},
 
@@ -434,6 +400,22 @@ export default defineComponent({
 		}
 	}
 
+	> .tabs {
+		display: flex;
+		display: none;
+
+		> .tab {
+			flex: 1;
+			height: 38px;
+			border-top: solid 1px var(--divider);
+
+			&.active {
+				border-top: solid 1px var(--accent);
+				color: var(--accent);
+			}
+		}
+	}
+
 	> .emojis {
 		height: var(--height);
 		overflow-y: auto;
@@ -445,34 +427,43 @@ export default defineComponent({
 			display: none;
 		}
 
-		> .index {
-			min-height: var(--height);
-			position: relative;
-			border-bottom: solid 1px var(--divider);
-				
-			> .arrow {
-				position: absolute;
-				bottom: 0;
-				left: 0;
-				width: 100%;
-				padding: 16px 0;
-				text-align: center;
-				opacity: 0.5;
-				pointer-events: none;
+		> div {
+			&:not(.index) {
+				padding: 4px 0 8px 0;
+				border-top: solid 1px var(--divider);
+			}
+
+			> header {
+				/*position: sticky;
+				top: 0;
+				left: 0;*/
+				height: 32px;
+				line-height: 32px;
+				z-index: 2;
+				padding: 0 8px;
+				font-size: 12px;
 			}
 		}
 
-		section {
+		::v-deep(section) {
 			> header {
 				position: sticky;
 				top: 0;
 				left: 0;
+				height: 32px;
+				line-height: 32px;
 				z-index: 1;
-				padding: 8px;
+				padding: 0 8px;
 				font-size: 12px;
+				cursor: pointer;
+
+				&:hover {
+					color: var(--accent);
+				}
 			}
 
 			> div {
+				position: relative;
 				padding: $pad;
 
 				> button {
@@ -512,14 +503,6 @@ export default defineComponent({
 					display: none;
 				}
 			}
-
-			&.unicode {
-				min-height: 384px;
-			}
-
-			&.custom {
-				min-height: 64px;
-			}
 		}
 	}
 }
diff --git a/src/client/instance.ts b/src/client/instance.ts
index 89c0368599..bd6b1bd571 100644
--- a/src/client/instance.ts
+++ b/src/client/instance.ts
@@ -37,6 +37,16 @@ export const emojiCategories = computed(() => {
 	return Array.from(categories);
 });
 
+export const emojiTags = computed(() => {
+	const tags = new Set();
+	for (const emoji of instance.emojis) {
+		for (const tag of emoji.aliases) {
+			tags.add(tag);
+		}
+	}
+	return Array.from(tags);
+});
+
 // このファイルに書きたくないけどここに書かないと何故かVeturが認識しない
 declare module '@vue/runtime-core' {
 	interface ComponentCustomProperties {