diff --git a/CHANGELOG.md b/CHANGELOG.md
index c64a8ea04d..2669bfee74 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@ ChangeLog (Release Notes)
 =========================
 主に notable な changes を書いていきます
 
+unreleased
+----------
+* New: 投稿のピン留め (#746)
+
 2508 (2017/08/30)
 -----------------
 * New: モバイル版のユーザーページのアクティビティチャートを変更
diff --git a/locales/en.yml b/locales/en.yml
index 29a6a764d3..15d278c370 100644
--- a/locales/en.yml
+++ b/locales/en.yml
@@ -77,6 +77,10 @@ common:
       show-result: "Show result"
       voted: "Voted"
 
+    mk-post-menu:
+      pin: "Pin"
+      pinned: "Pinned"
+
     mk-reaction-picker:
       choose-reaction: "Pick your reaction"
 
diff --git a/locales/ja.yml b/locales/ja.yml
index 21a07640d0..7ed4262f1c 100644
--- a/locales/ja.yml
+++ b/locales/ja.yml
@@ -77,6 +77,10 @@ common:
       show-result: "結果を見る"
       voted: "投票済み"
 
+    mk-post-menu:
+      pin: "ピン留め"
+      pinned: "ピン留めしました"
+
     mk-reaction-picker:
       choose-reaction: "リアクションを選択"
 
diff --git a/src/api/endpoints.ts b/src/api/endpoints.ts
index c6661533e8..e5be68c096 100644
--- a/src/api/endpoints.ts
+++ b/src/api/endpoints.ts
@@ -167,6 +167,10 @@ const endpoints: Endpoint[] = [
 		name: 'i/regenerate_token',
 		withCredential: true
 	},
+	{
+		name: 'i/pin',
+		kind: 'account-write'
+	},
 	{
 		name: 'i/appdata/get',
 		withCredential: true
diff --git a/src/api/endpoints/i/pin.ts b/src/api/endpoints/i/pin.ts
new file mode 100644
index 0000000000..a94950d22b
--- /dev/null
+++ b/src/api/endpoints/i/pin.ts
@@ -0,0 +1,44 @@
+/**
+ * Module dependencies
+ */
+import $ from 'cafy';
+import User from '../../models/user';
+import Post from '../../models/post';
+import serialize from '../../serializers/user';
+
+/**
+ * Pin post
+ *
+ * @param {any} params
+ * @param {any} user
+ * @return {Promise<any>}
+ */
+module.exports = async (params, user) => new Promise(async (res, rej) => {
+	// Get 'post_id' parameter
+	const [postId, postIdErr] = $(params.post_id).id().$;
+	if (postIdErr) return rej('invalid post_id param');
+
+	// Fetch pinee
+	const post = await Post.findOne({
+		_id: postId,
+		user_id: user._id
+	});
+
+	if (post === null) {
+		return rej('post not found');
+	}
+
+	await User.update(user._id, {
+		$set: {
+			pinned_post_id: post._id
+		}
+	});
+
+	// Serialize
+	const iObj = await serialize(user, user, {
+		detail: true
+	});
+
+	// Send response
+	res(iObj);
+});
diff --git a/src/api/serializers/user.ts b/src/api/serializers/user.ts
index bdbc749589..c9189d9034 100644
--- a/src/api/serializers/user.ts
+++ b/src/api/serializers/user.ts
@@ -4,6 +4,7 @@
 import * as mongo from 'mongodb';
 import deepcopy = require('deepcopy');
 import User from '../models/user';
+import serializePost from './post';
 import Following from '../models/following';
 import getFriends from '../common/get-friends';
 import config from '../../conf';
@@ -116,24 +117,32 @@ export default (
 		_user.is_followed = follow2 !== null;
 	}
 
-	if (me && !me.equals(_user.id) && opts.detail) {
-		const myFollowingIds = await getFriends(me);
+	if (opts.detail) {
+		if (_user.pinned_post_id) {
+			_user.pinned_post = await serializePost(_user.pinned_post_id, me, {
+				detail: true
+			});
+		}
 
-		// Get following you know count
-		const followingYouKnowCount = await Following.count({
-			followee_id: { $in: myFollowingIds },
-			follower_id: _user.id,
-			deleted_at: { $exists: false }
-		});
-		_user.following_you_know_count = followingYouKnowCount;
+		if (me && !me.equals(_user.id)) {
+			const myFollowingIds = await getFriends(me);
 
-		// Get followers you know count
-		const followersYouKnowCount = await Following.count({
-			followee_id: _user.id,
-			follower_id: { $in: myFollowingIds },
-			deleted_at: { $exists: false }
-		});
-		_user.followers_you_know_count = followersYouKnowCount;
+			// Get following you know count
+			const followingYouKnowCount = await Following.count({
+				followee_id: { $in: myFollowingIds },
+				follower_id: _user.id,
+				deleted_at: { $exists: false }
+			});
+			_user.following_you_know_count = followingYouKnowCount;
+
+			// Get followers you know count
+			const followersYouKnowCount = await Following.count({
+				followee_id: _user.id,
+				follower_id: { $in: myFollowingIds },
+				deleted_at: { $exists: false }
+			});
+			_user.followers_you_know_count = followersYouKnowCount;
+		}
 	}
 
 	resolve(_user);
diff --git a/src/web/app/common/tags/index.js b/src/web/app/common/tags/index.js
index dd6ba75d7a..6e6081da9b 100644
--- a/src/web/app/common/tags/index.js
+++ b/src/web/app/common/tags/index.js
@@ -28,3 +28,4 @@ require('./reaction-picker.tag');
 require('./reactions-viewer.tag');
 require('./reaction-icon.tag');
 require('./weekly-activity-chart.tag');
+require('./post-menu.tag');
diff --git a/src/web/app/common/tags/post-menu.tag b/src/web/app/common/tags/post-menu.tag
new file mode 100644
index 0000000000..33895212bc
--- /dev/null
+++ b/src/web/app/common/tags/post-menu.tag
@@ -0,0 +1,134 @@
+<mk-post-menu>
+	<div class="backdrop" ref="backdrop" onclick={ close }></div>
+	<div class="popover { compact: opts.compact }" ref="popover">
+		<button if={ post.user_id === I.id } onclick={ pin }>%i18n:common.tags.mk-post-menu.pin%</button>
+	</div>
+	<style>
+		$border-color = rgba(27, 31, 35, 0.15)
+
+		:scope
+			display block
+			position initial
+
+			> .backdrop
+				position fixed
+				top 0
+				left 0
+				z-index 10000
+				width 100%
+				height 100%
+				background rgba(0, 0, 0, 0.1)
+				opacity 0
+
+			> .popover
+				position absolute
+				z-index 10001
+				background #fff
+				border 1px solid $border-color
+				border-radius 4px
+				box-shadow 0 3px 12px rgba(27, 31, 35, 0.15)
+				transform scale(0.5)
+				opacity 0
+
+				$balloon-size = 16px
+
+				&:not(.compact)
+					margin-top $balloon-size
+					transform-origin center -($balloon-size)
+
+					&:before
+						content ""
+						display block
+						position absolute
+						top -($balloon-size * 2)
+						left s('calc(50% - %s)', $balloon-size)
+						border-top solid $balloon-size transparent
+						border-left solid $balloon-size transparent
+						border-right solid $balloon-size transparent
+						border-bottom solid $balloon-size $border-color
+
+					&:after
+						content ""
+						display block
+						position absolute
+						top -($balloon-size * 2) + 1.5px
+						left s('calc(50% - %s)', $balloon-size)
+						border-top solid $balloon-size transparent
+						border-left solid $balloon-size transparent
+						border-right solid $balloon-size transparent
+						border-bottom solid $balloon-size #fff
+
+				> button
+					display block
+
+	</style>
+	<script>
+		import anime from 'animejs';
+
+		this.mixin('i');
+		this.mixin('api');
+
+		this.post = this.opts.post;
+		this.source = this.opts.source;
+
+		this.on('mount', () => {
+			const rect = this.source.getBoundingClientRect();
+			const width = this.refs.popover.offsetWidth;
+			const height = this.refs.popover.offsetHeight;
+			if (this.opts.compact) {
+				const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2);
+				const y = rect.top + window.pageYOffset + (this.source.offsetHeight / 2);
+				this.refs.popover.style.left = (x - (width / 2)) + 'px';
+				this.refs.popover.style.top = (y - (height / 2)) + 'px';
+			} else {
+				const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2);
+				const y = rect.top + window.pageYOffset + this.source.offsetHeight;
+				this.refs.popover.style.left = (x - (width / 2)) + 'px';
+				this.refs.popover.style.top = y + 'px';
+			}
+
+			anime({
+				targets: this.refs.backdrop,
+				opacity: 1,
+				duration: 100,
+				easing: 'linear'
+			});
+
+			anime({
+				targets: this.refs.popover,
+				opacity: 1,
+				scale: [0.5, 1],
+				duration: 500
+			});
+		});
+
+		this.pin = () => {
+			this.api('i/pin', {
+				post_id: this.post.id
+			}).then(() => {
+				if (this.opts.cb) this.opts.cb('pinned', '%i18n:common.tags.mk-post-menu.pinned%');
+				this.unmount();
+			});
+		};
+
+		this.close = () => {
+			this.refs.backdrop.style.pointerEvents = 'none';
+			anime({
+				targets: this.refs.backdrop,
+				opacity: 0,
+				duration: 200,
+				easing: 'linear'
+			});
+
+			this.refs.popover.style.pointerEvents = 'none';
+			anime({
+				targets: this.refs.popover,
+				opacity: 0,
+				scale: 0.5,
+				duration: 200,
+				easing: 'easeInBack',
+				complete: () => this.unmount()
+			});
+		};
+	</script>
+</mk-post-menu>
diff --git a/src/web/app/desktop/tags/post-detail.tag b/src/web/app/desktop/tags/post-detail.tag
index 7a90dccf39..58343482d0 100644
--- a/src/web/app/desktop/tags/post-detail.tag
+++ b/src/web/app/desktop/tags/post-detail.tag
@@ -43,16 +43,18 @@
 			</div>
 			<footer>
 				<mk-reactions-viewer post={ p }/>
-				<button onclick={ reply } title="返信"><i class="fa fa-reply"></i>
-					<p class="count" if={ p.replies_count > 0 }>{ p.replies_count }</p>
+				<button onclick={ reply } title="返信">
+					<i class="fa fa-reply"></i><p class="count" if={ p.replies_count > 0 }>{ p.replies_count }</p>
 				</button>
-				<button onclick={ repost } title="Repost"><i class="fa fa-retweet"></i>
-					<p class="count" if={ p.repost_count > 0 }>{ p.repost_count }</p>
+				<button onclick={ repost } title="Repost">
+					<i class="fa fa-retweet"></i><p class="count" if={ p.repost_count > 0 }>{ p.repost_count }</p>
 				</button>
-				<button class={ reacted: p.my_reaction != null } onclick={ react } ref="reactButton" title="リアクション"><i class="fa fa-plus"></i>
-					<p class="count" if={ p.reactions_count > 0 }>{ p.reactions_count }</p>
+				<button class={ reacted: p.my_reaction != null } onclick={ react } ref="reactButton" title="リアクション">
+					<i class="fa fa-plus"></i><p class="count" if={ p.reactions_count > 0 }>{ p.reactions_count }</p>
+				</button>
+				<button onclick={ menu } ref="menuButton">
+					<i class="fa fa-ellipsis-h"></i>
 				</button>
-				<button><i class="fa fa-ellipsis-h"></i></button>
 			</footer>
 		</article>
 		<div class="replies">
@@ -315,6 +317,13 @@
 			});
 		};
 
+		this.menu = () => {
+			riot.mount(document.body.appendChild(document.createElement('mk-post-menu')), {
+				source: this.refs.menuButton,
+				post: this.p
+			});
+		};
+
 		this.loadContext = () => {
 			this.contextFetching = true;
 
diff --git a/src/web/app/desktop/tags/timeline.tag b/src/web/app/desktop/tags/timeline.tag
index bce27cd7f3..cd7ac7d207 100644
--- a/src/web/app/desktop/tags/timeline.tag
+++ b/src/web/app/desktop/tags/timeline.tag
@@ -128,16 +128,16 @@
 			</div>
 			<footer>
 				<mk-reactions-viewer post={ p } ref="reactionsViewer"/>
-				<button onclick={ reply } title="%i18n:desktop.tags.mk-timeline-post.reply%"><i class="fa fa-reply"></i>
-					<p class="count" if={ p.replies_count > 0 }>{ p.replies_count }</p>
+				<button onclick={ reply } title="%i18n:desktop.tags.mk-timeline-post.reply%">
+					<i class="fa fa-reply"></i><p class="count" if={ p.replies_count > 0 }>{ p.replies_count }</p>
 				</button>
-				<button onclick={ repost } title="%i18n:desktop.tags.mk-timeline-post.repost%"><i class="fa fa-retweet"></i>
-					<p class="count" if={ p.repost_count > 0 }>{ p.repost_count }</p>
+				<button onclick={ repost } title="%i18n:desktop.tags.mk-timeline-post.repost%">
+					<i class="fa fa-retweet"></i><p class="count" if={ p.repost_count > 0 }>{ p.repost_count }</p>
 				</button>
-				<button class={ reacted: p.my_reaction != null } onclick={ react } ref="reactButton" title="%i18n:desktop.tags.mk-timeline-post.add-reaction%"><i class="fa fa-plus"></i>
-					<p class="count" if={ p.reactions_count > 0 }>{ p.reactions_count }</p>
+				<button class={ reacted: p.my_reaction != null } onclick={ react } ref="reactButton" title="%i18n:desktop.tags.mk-timeline-post.add-reaction%">
+					<i class="fa fa-plus"></i><p class="count" if={ p.reactions_count > 0 }>{ p.reactions_count }</p>
 				</button>
-				<button>
+				<button onclick={ menu } ref="menuButton">
 					<i class="fa fa-ellipsis-h"></i>
 				</button>
 				<button onclick={ toggleDetail } title="%i18n:desktop.tags.mk-timeline-post.detail">
@@ -525,6 +525,13 @@
 			});
 		};
 
+		this.menu = () => {
+			riot.mount(document.body.appendChild(document.createElement('mk-post-menu')), {
+				source: this.refs.menuButton,
+				post: this.p
+			});
+		};
+
 		this.toggleDetail = () => {
 			this.update({
 				isDetailOpened: !this.isDetailOpened
diff --git a/src/web/app/mobile/tags/page/post.tag b/src/web/app/mobile/tags/page/post.tag
index 198acf1798..6888229f89 100644
--- a/src/web/app/mobile/tags/page/post.tag
+++ b/src/web/app/mobile/tags/page/post.tag
@@ -2,7 +2,9 @@
 	<mk-ui ref="ui">
 		<main if={ !parent.fetching }>
 			<a if={ parent.post.next } href={ parent.post.next }><i class="fa fa-angle-up"></i>%i18n:mobile.tags.mk-post-page.next%</a>
-			<mk-post-detail ref="post" post={ parent.post }/>
+			<div>
+				<mk-post-detail ref="post" post={ parent.post }/>
+			</div>
 			<a if={ parent.post.prev } href={ parent.post.prev }><i class="fa fa-angle-down"></i>%i18n:mobile.tags.mk-post-page.prev%</a>
 		</main>
 	</mk-ui>
@@ -13,6 +15,16 @@
 			main
 				text-align center
 
+				> div
+					margin 8px auto
+					padding 0
+					max-width 500px
+					width calc(100% - 16px)
+
+					@media (min-width 500px)
+						margin 16px auto
+						width calc(100% - 32px)
+
 				> a
 					display inline-block
 
diff --git a/src/web/app/mobile/tags/post-detail.tag b/src/web/app/mobile/tags/post-detail.tag
index 9215bafdbc..cf09434400 100644
--- a/src/web/app/mobile/tags/post-detail.tag
+++ b/src/web/app/mobile/tags/post-detail.tag
@@ -38,24 +38,26 @@
 			</div>
 			<mk-poll if={ p.poll } post={ p }/>
 		</div>
-		<a class="time" href={ url }>
+		<a class="time" href={ '/' + p.user.username + '/' + p.id }>
 			<mk-time time={ p.created_at } mode="detail"/>
 		</a>
 		<footer>
 			<mk-reactions-viewer post={ p }/>
-			<button onclick={ reply } title="%i18n:mobile.tags.mk-post-detail.reply%"><i class="fa fa-reply"></i>
-				<p class="count" if={ p.replies_count > 0 }>{ p.replies_count }</p>
+			<button onclick={ reply } title="%i18n:mobile.tags.mk-post-detail.reply%">
+				<i class="fa fa-reply"></i><p class="count" if={ p.replies_count > 0 }>{ p.replies_count }</p>
 			</button>
-			<button onclick={ repost } title="Repost"><i class="fa fa-retweet"></i>
-				<p class="count" if={ p.repost_count > 0 }>{ p.repost_count }</p>
+			<button onclick={ repost } title="Repost">
+				<i class="fa fa-retweet"></i><p class="count" if={ p.repost_count > 0 }>{ p.repost_count }</p>
 			</button>
-			<button class={ reacted: p.my_reaction != null } onclick={ react } ref="reactButton" title="%i18n:mobile.tags.mk-post-detail.reaction%"><i class="fa fa-plus"></i>
-				<p class="count" if={ p.reactions_count > 0 }>{ p.reactions_count }</p>
+			<button class={ reacted: p.my_reaction != null } onclick={ react } ref="reactButton" title="%i18n:mobile.tags.mk-post-detail.reaction%">
+				<i class="fa fa-plus"></i><p class="count" if={ p.reactions_count > 0 }>{ p.reactions_count }</p>
+			</button>
+			<button onclick={ menu } ref="menuButton">
+				<i class="fa fa-ellipsis-h"></i>
 			</button>
-			<button><i class="fa fa-ellipsis-h"></i></button>
 		</footer>
 	</article>
-	<div class="replies">
+	<div class="replies" if={ !compact }>
 		<virtual each={ post in replies }>
 			<mk-post-detail-sub post={ post }/>
 		</virtual>
@@ -64,19 +66,14 @@
 		:scope
 			display block
 			overflow hidden
-			margin 8px auto
+			margin 0 auto
 			padding 0
-			max-width 500px
-			width calc(100% - 16px)
+			width 100%
 			text-align left
 			background #fff
 			border-radius 8px
 			box-shadow 0 0 0 1px rgba(0, 0, 0, 0.2)
 
-			@media (min-width 500px)
-				margin 16px auto
-				width calc(100% - 32px)
-
 			> .fetching
 				padding 64px 0
 
@@ -269,6 +266,7 @@
 
 		this.mixin('api');
 
+		this.compact = this.opts.compact;
 		this.post = this.opts.post;
 		this.isRepost = this.post.repost != null;
 		this.p = this.isRepost ? this.post.repost : this.post;
@@ -299,14 +297,16 @@
 			}
 
 			// Get replies
-			this.api('posts/replies', {
-				post_id: this.p.id,
-				limit: 8
-			}).then(replies => {
-				this.update({
-					replies: replies
+			if (!this.compact) {
+				this.api('posts/replies', {
+					post_id: this.p.id,
+					limit: 8
+				}).then(replies => {
+					this.update({
+						replies: replies
+					});
 				});
-			});
+			}
 		});
 
 		this.reply = () => {
@@ -332,6 +332,14 @@
 			});
 		};
 
+		this.menu = () => {
+			riot.mount(document.body.appendChild(document.createElement('mk-post-menu')), {
+				source: this.refs.menuButton,
+				post: this.p,
+				compact: true
+			});
+		};
+
 		this.loadContext = () => {
 			this.contextFetching = true;
 
diff --git a/src/web/app/mobile/tags/timeline.tag b/src/web/app/mobile/tags/timeline.tag
index 43470d197e..d8df8b2663 100644
--- a/src/web/app/mobile/tags/timeline.tag
+++ b/src/web/app/mobile/tags/timeline.tag
@@ -181,14 +181,17 @@
 			</div>
 			<footer>
 				<mk-reactions-viewer post={ p } ref="reactionsViewer"/>
-				<button onclick={ reply }><i class="fa fa-reply"></i>
-					<p class="count" if={ p.replies_count > 0 }>{ p.replies_count }</p>
+				<button onclick={ reply }>
+					<i class="fa fa-reply"></i><p class="count" if={ p.replies_count > 0 }>{ p.replies_count }</p>
 				</button>
-				<button onclick={ repost } title="Repost"><i class="fa fa-retweet"></i>
-					<p class="count" if={ p.repost_count > 0 }>{ p.repost_count }</p>
+				<button onclick={ repost } title="Repost">
+					<i class="fa fa-retweet"></i><p class="count" if={ p.repost_count > 0 }>{ p.repost_count }</p>
 				</button>
-				<button class={ reacted: p.my_reaction != null } onclick={ react } ref="reactButton"><i class="fa fa-plus"></i>
-					<p class="count" if={ p.reactions_count > 0 }>{ p.reactions_count }</p>
+				<button class={ reacted: p.my_reaction != null } onclick={ react } ref="reactButton">
+					<i class="fa fa-plus"></i><p class="count" if={ p.reactions_count > 0 }>{ p.reactions_count }</p>
+				</button>
+				<button class={ reacted: p.my_reaction != null } onclick={ react } ref="reactButton">
+					<i class="fa fa-ellipsis-h"></i>
 				</button>
 			</footer>
 		</div>
@@ -558,6 +561,14 @@
 				compact: true
 			});
 		};
+
+		this.menu = () => {
+			riot.mount(document.body.appendChild(document.createElement('mk-post-menu')), {
+				source: this.refs.menuButton,
+				post: this.p,
+				compact: true
+			});
+		};
 	</script>
 </mk-timeline-post>
 
diff --git a/src/web/app/mobile/tags/user.tag b/src/web/app/mobile/tags/user.tag
index d85e3b51fd..0fe4055cf0 100644
--- a/src/web/app/mobile/tags/user.tag
+++ b/src/web/app/mobile/tags/user.tag
@@ -215,6 +215,7 @@
 </mk-user>
 
 <mk-user-overview>
+	<mk-post-detail if={ user.pinned_post } post={ user.pinned_post } compact={ true }/>
 	<section class="recent-posts">
 		<h2><i class="fa fa-comments-o"></i>%i18n:mobile.tags.mk-user-overview.recent-posts%</h2>
 		<div>
@@ -240,6 +241,9 @@
 			max-width 600px
 			margin 0 auto
 
+			> mk-post-detail
+				margin 0 0 8px 0
+
 			> section
 				background #eee
 				border-radius 8px