diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index eed17edb14598fa27b09a800a1b7c9ac1f25a6b4..e3d0afcefbcf508496d2875bfdd64280581c457a 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -768,6 +768,7 @@ squareAvatars: "アイコンを四角形で表示"
 sent: "送信"
 received: "受信"
 searchResult: "検索結果"
+hashtags: "ハッシュタグ"
 
 _docs: 
   continueReading: "続きを読む"
diff --git a/src/client/components/post-form.vue b/src/client/components/post-form.vue
index 13bbb3f9e57eb3322a5f5843a498b2bce622ab9b..ed2a934c263f9fb7f11fb355703018adc7295fcc 100644
--- a/src/client/components/post-form.vue
+++ b/src/client/components/post-form.vue
@@ -37,6 +37,7 @@
 		<MkInfo warn v-if="hasNotSpecifiedMentions" class="hasNotSpecifiedMentions">{{ $ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ $ts.add }}</button></MkInfo>
 		<input v-show="useCw" ref="cw" class="cw" v-model="cw" :placeholder="$ts.annotation" @keydown="onKeydown">
 		<textarea v-model="text" class="text" :class="{ withCw: useCw }" ref="text" :disabled="posting" :placeholder="placeholder" @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd" />
+		<input v-show="withHashtags" ref="hashtags" class="hashtags" v-model="hashtags" :placeholder="$ts.hashtags" list="hashtags">
 		<XPostFormAttaches class="attaches" :files="files" @updated="updateFiles" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/>
 		<XPollEditor v-if="poll" :poll="poll" @destroyed="poll = null" @updated="onPollUpdate"/>
 		<footer>
@@ -44,9 +45,13 @@
 			<button class="_button" @click="togglePoll" :class="{ active: poll }" v-tooltip="$ts.poll"><i class="fas fa-poll-h"></i></button>
 			<button class="_button" @click="useCw = !useCw" :class="{ active: useCw }" v-tooltip="$ts.useCw"><i class="fas fa-eye-slash"></i></button>
 			<button class="_button" @click="insertMention" v-tooltip="$ts.mention"><i class="fas fa-at"></i></button>
+			<button class="_button" @click="withHashtags = !withHashtags" v-tooltip="$ts.hashtags"><i class="fas fa-hashtag"></i></button>
 			<button class="_button" @click="insertEmoji" v-tooltip="$ts.emoji"><i class="fas fa-laugh-squint"></i></button>
 			<button class="_button" @click="showActions" v-tooltip="$ts.plugin" v-if="postFormActions.length > 0"><i class="fas fa-plug"></i></button>
 		</footer>
+		<datalist id="hashtags">
+			<option v-for="hashtag in recentHashtags" :value="hashtag" :key="hashtag"/>
+		</datalist>
 	</div>
 </div>
 </template>
@@ -67,10 +72,11 @@ import { Autocomplete } from '@client/scripts/autocomplete';
 import { noteVisibilities } from '../../types';
 import * as os from '@client/os';
 import { selectFile } from '@client/scripts/select-file';
-import { notePostInterruptors, postFormActions } from '@client/store';
+import { defaultStore, notePostInterruptors, postFormActions } from '@client/store';
 import { isMobile } from '@client/scripts/is-mobile';
 import { throttle } from 'throttle-debounce';
 import MkInfo from '@client/components/ui/info.vue';
+import { defaultStore } from '@client/store';
 
 export default defineComponent({
 	components: {
@@ -212,7 +218,10 @@ export default defineComponent({
 
 		max(): number {
 			return this.$instance ? this.$instance.maxNoteTextLength : 1000;
-		}
+		},
+
+		withHashtags: defaultStore.makeGetterSetter('postFormWithHashtags'),
+		hashtags: defaultStore.makeGetterSetter('postFormHashtags'),
 	},
 
 	watch: {
@@ -303,6 +312,7 @@ export default defineComponent({
 		// TODO: detach when unmount
 		new Autocomplete(this.$refs.text, this, { model: 'text' });
 		new Autocomplete(this.$refs.cw, this, { model: 'cw' });
+		new Autocomplete(this.$refs.hashtags, this, { model: 'hashtags' });
 
 		this.$nextTick(() => {
 			// 書きかけの投稿を復元
@@ -605,6 +615,11 @@ export default defineComponent({
 				viaMobile: isMobile
 			};
 
+			if (this.withHashtags) {
+				const hashtags = this.hashtags.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' ');
+				data.text = data.text ? `${data.text} ${hashtags}` : hashtags;
+			}
+
 			// plugin
 			if (notePostInterruptors.length > 0) {
 				for (const interruptor of notePostInterruptors) {
@@ -618,8 +633,8 @@ export default defineComponent({
 				this.$nextTick(() => {
 					this.deleteDraft();
 					this.$emit('posted');
-					if (this.text && this.text != '') {
-						const hashtags = mfm.parse(this.text).filter(x => x.type === 'hashtag').map(x => x.props.hashtag);
+					if (data.text && data.text != '') {
+						const hashtags = mfm.parse(data.text).filter(x => x.type === 'hashtag').map(x => x.props.hashtag);
 						const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[];
 						localStorage.setItem('hashtags', JSON.stringify(unique(hashtags.concat(history))));
 					}
@@ -785,6 +800,7 @@ export default defineComponent({
 		}
 
 		> .cw,
+		> .hashtags,
 		> .text {
 			display: block;
 			box-sizing: border-box;
@@ -813,6 +829,13 @@ export default defineComponent({
 			border-bottom: solid 0.5px var(--divider);
 		}
 
+		> .hashtags {
+			z-index: 1;
+			padding-top: 8px;
+			padding-bottom: 8px;
+			border-top: solid 0.5px var(--divider);
+		}
+
 		> .text {
 			max-width: 100%;
 			min-width: 100%;
@@ -872,6 +895,7 @@ export default defineComponent({
 			}
 
 			> .cw,
+			> .hashtags,
 			> .text {
 				padding: 0 16px;
 			}
diff --git a/src/client/store.ts b/src/client/store.ts
index 6ca431e05943992dd24e6d471de6e7864b98a15f..364d8afd93626b48448d5737fff43a98bbec78ed 100644
--- a/src/client/store.ts
+++ b/src/client/store.ts
@@ -198,6 +198,14 @@ export const defaultStore = markRaw(new Storage('base', {
 		where: 'device',
 		default: false
 	},
+	postFormWithHashtags: {
+		where: 'device',
+		default: false
+	},
+	postFormHashtags: {
+		where: 'device',
+		default: ''
+	},
 }));
 
 // TODO: 他のタブと永続化されたstateを同期