diff --git a/src/web/app/desktop/-tags/user-followers.tag b/src/web/app/desktop/-tags/user-followers.tag
deleted file mode 100644
index 3a5430d37a..0000000000
--- a/src/web/app/desktop/-tags/user-followers.tag
+++ /dev/null
@@ -1,23 +0,0 @@
-<mk-user-followers>
-	<mk-users-list fetch={ fetch } count={ user.followers_count } you-know-count={ user.followers_you_know_count } no-users={ 'フォロワーはいないようです。' }/>
-	<style lang="stylus" scoped>
-		:scope
-			display block
-			height 100%
-
-	</style>
-	<script lang="typescript">
-		this.mixin('api');
-
-		this.user = this.opts.user;
-
-		this.fetch = (iknow, limit, cursor, cb) => {
-			this.$root.$data.os.api('users/followers', {
-				user_id: this.user.id,
-				iknow: iknow,
-				limit: limit,
-				cursor: cursor ? cursor : undefined
-			}).then(cb);
-		};
-	</script>
-</mk-user-followers>
diff --git a/src/web/app/desktop/-tags/user-following.tag b/src/web/app/desktop/-tags/user-following.tag
deleted file mode 100644
index 42ad5f88a8..0000000000
--- a/src/web/app/desktop/-tags/user-following.tag
+++ /dev/null
@@ -1,23 +0,0 @@
-<mk-user-following>
-	<mk-users-list fetch={ fetch } count={ user.following_count } you-know-count={ user.following_you_know_count } no-users={ 'フォロー中のユーザーはいないようです。' }/>
-	<style lang="stylus" scoped>
-		:scope
-			display block
-			height 100%
-
-	</style>
-	<script lang="typescript">
-		this.mixin('api');
-
-		this.user = this.opts.user;
-
-		this.fetch = (iknow, limit, cursor, cb) => {
-			this.$root.$data.os.api('users/following', {
-				user_id: this.user.id,
-				iknow: iknow,
-				limit: limit,
-				cursor: cursor ? cursor : undefined
-			}).then(cb);
-		};
-	</script>
-</mk-user-following>
diff --git a/src/web/app/desktop/-tags/users-list.tag b/src/web/app/desktop/-tags/users-list.tag
deleted file mode 100644
index 03c527109b..0000000000
--- a/src/web/app/desktop/-tags/users-list.tag
+++ /dev/null
@@ -1,138 +0,0 @@
-<mk-users-list>
-	<nav>
-		<div>
-			<span data-is-active={ mode == 'all' } @click="setMode.bind(this, 'all')">すべて<span>{ opts.count }</span></span>
-			<span v-if="$root.$data.os.isSignedIn && opts.youKnowCount" data-is-active={ mode == 'iknow' } @click="setMode.bind(this, 'iknow')">知り合い<span>{ opts.youKnowCount }</span></span>
-		</div>
-	</nav>
-	<div class="users" v-if="!fetching && users.length != 0">
-		<div each={ users }>
-			<mk-list-user user={ this }/>
-		</div>
-	</div>
-	<button class="more" v-if="!fetching && next != null" @click="more" disabled={ moreFetching }>
-		<span v-if="!moreFetching">もっと</span>
-		<span v-if="moreFetching">読み込み中<mk-ellipsis/></span>
-	</button>
-	<p class="no" v-if="!fetching && users.length == 0">{ opts.noUsers }</p>
-	<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%読み込んでいます<mk-ellipsis/></p>
-	<style lang="stylus" scoped>
-		:scope
-			display block
-			height 100%
-			background #fff
-
-			> nav
-				z-index 1
-				box-shadow 0 1px 0 rgba(#000, 0.1)
-
-				> div
-					display flex
-					justify-content center
-					margin 0 auto
-					max-width 600px
-
-					> span
-						display block
-						flex 1 1
-						text-align center
-						line-height 52px
-						font-size 14px
-						color #657786
-						border-bottom solid 2px transparent
-						cursor pointer
-
-						*
-							pointer-events none
-
-						&[data-is-active]
-							font-weight bold
-							color $theme-color
-							border-color $theme-color
-							cursor default
-
-						> span
-							display inline-block
-							margin-left 4px
-							padding 2px 5px
-							font-size 12px
-							line-height 1
-							color #888
-							background #eee
-							border-radius 20px
-
-			> .users
-				height calc(100% - 54px)
-				overflow auto
-
-				> *
-					border-bottom solid 1px rgba(0, 0, 0, 0.05)
-
-					> *
-						max-width 600px
-						margin 0 auto
-
-			> .no
-				margin 0
-				padding 16px
-				text-align center
-				color #aaa
-
-			> .fetching
-				margin 0
-				padding 16px
-				text-align center
-				color #aaa
-
-				> [data-fa]
-					margin-right 4px
-
-	</style>
-	<script lang="typescript">
-		this.mixin('i');
-
-		this.limit = 30;
-		this.mode = 'all';
-
-		this.fetching = true;
-		this.moreFetching = false;
-
-		this.on('mount', () => {
-			this.fetch(() => this.$emit('loaded'));
-		});
-
-		this.fetch = cb => {
-			this.update({
-				fetching: true
-			});
-			this.opts.fetch(this.mode == 'iknow', this.limit, null, obj => {
-				this.update({
-					fetching: false,
-					users: obj.users,
-					next: obj.next
-				});
-				if (cb) cb();
-			});
-		};
-
-		this.more = () => {
-			this.update({
-				moreFetching: true
-			});
-			this.opts.fetch(this.mode == 'iknow', this.limit, this.cursor, obj => {
-				this.update({
-					moreFetching: false,
-					users: this.users.concat(obj.users),
-					next: obj.next
-				});
-			});
-		};
-
-		this.setMode = mode => {
-			this.update({
-				mode: mode
-			});
-			this.fetch();
-		};
-	</script>
-</mk-users-list>
diff --git a/src/web/app/desktop/views/components/user-followers.vue b/src/web/app/desktop/views/components/user-followers.vue
new file mode 100644
index 0000000000..67e694cf41
--- /dev/null
+++ b/src/web/app/desktop/views/components/user-followers.vue
@@ -0,0 +1,26 @@
+<template>
+<mk-users-list
+	:fetch="fetch"
+	:count="user.followers_count"
+	:you-know-count="user.followers_you_know_count"
+>
+	フォロワーはいないようです。
+</mk-users-list>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+export default Vue.extend({
+	props: ['user'],
+	methods: {
+		fetch(iknow, limit, cursor, cb) {
+			this.$root.$data.os.api('users/followers', {
+				user_id: this.user.id,
+				iknow: iknow,
+				limit: limit,
+				cursor: cursor ? cursor : undefined
+			}).then(cb);
+		}
+	}
+});
+</script>
diff --git a/src/web/app/desktop/views/components/user-following.vue b/src/web/app/desktop/views/components/user-following.vue
new file mode 100644
index 0000000000..16cc3c42f1
--- /dev/null
+++ b/src/web/app/desktop/views/components/user-following.vue
@@ -0,0 +1,26 @@
+<template>
+<mk-users-list
+	:fetch="fetch"
+	:count="user.following_count"
+	:you-know-count="user.following_you_know_count"
+>
+	フォロー中のユーザーはいないようです。
+</mk-users-list>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+export default Vue.extend({
+	props: ['user'],
+	methods: {
+		fetch(iknow, limit, cursor, cb) {
+			this.$root.$data.os.api('users/following', {
+				user_id: this.user.id,
+				iknow: iknow,
+				limit: limit,
+				cursor: cursor ? cursor : undefined
+			}).then(cb);
+		}
+	}
+});
+</script>
diff --git a/src/web/app/desktop/views/components/users-list.vue b/src/web/app/desktop/views/components/users-list.vue
new file mode 100644
index 0000000000..268fac4ec1
--- /dev/null
+++ b/src/web/app/desktop/views/components/users-list.vue
@@ -0,0 +1,136 @@
+<template>
+<div class="mk-users-list">
+	<nav>
+		<div>
+			<span :data-is-active="mode == 'all'" @click="mode = 'all'">すべて<span>{{ count }}</span></span>
+			<span v-if="$root.$data.os.isSignedIn && youKnowCount" :data-is-active="mode == 'iknow'" @click="mode = 'iknow'">知り合い<span>{{ youKnowCount }}</span></span>
+		</div>
+	</nav>
+	<div class="users" v-if="!fetching && users.length != 0">
+		<div v-for="u in users" :key="u.id">
+			<mk-list-user :user="u"/>
+		</div>
+	</div>
+	<button class="more" v-if="!fetching && next != null" @click="more" :disabled="moreFetching">
+		<span v-if="!moreFetching">もっと</span>
+		<span v-if="moreFetching">読み込み中<mk-ellipsis/></span>
+	</button>
+	<p class="no" v-if="!fetching && users.length == 0">
+		<slot></slot>
+	</p>
+	<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%読み込んでいます<mk-ellipsis/></p>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+export default Vue.extend({
+	props: ['fetch', 'count', 'youKnowCount'],
+	data() {
+		return {
+			limit: 30,
+			mode: 'all',
+			fetching: true,
+			moreFetching: false,
+			users: [],
+			next: null
+		};
+	},
+	mounted() {
+		this._fetch(() => {
+			this.$emit('loaded');
+		});
+	},
+	methods: {
+		_fetch(cb) {
+			this.fetching = true;
+			this.fetch(this.mode == 'iknow', this.limit, null, obj => {
+				this.fetching = false;
+				this.users = obj.users;
+				this.next = obj.next;
+				if (cb) cb();
+			});
+		},
+		more() {
+			this.moreFetching = true;
+			this.fetch(this.mode == 'iknow', this.limit, this.next, obj => {
+				this.moreFetching = false;
+				this.users = this.users.concat(obj.users);
+				this.next = obj.next;
+			});
+		}
+	}
+});
+</script>
+
+<style lang="stylus" scoped>
+.mk-users-list
+	height 100%
+	background #fff
+
+	> nav
+		z-index 1
+		box-shadow 0 1px 0 rgba(#000, 0.1)
+
+		> div
+			display flex
+			justify-content center
+			margin 0 auto
+			max-width 600px
+
+			> span
+				display block
+				flex 1 1
+				text-align center
+				line-height 52px
+				font-size 14px
+				color #657786
+				border-bottom solid 2px transparent
+				cursor pointer
+
+				*
+					pointer-events none
+
+				&[data-is-active]
+					font-weight bold
+					color $theme-color
+					border-color $theme-color
+					cursor default
+
+				> span
+					display inline-block
+					margin-left 4px
+					padding 2px 5px
+					font-size 12px
+					line-height 1
+					color #888
+					background #eee
+					border-radius 20px
+
+	> .users
+		height calc(100% - 54px)
+		overflow auto
+
+		> *
+			border-bottom solid 1px rgba(0, 0, 0, 0.05)
+
+			> *
+				max-width 600px
+				margin 0 auto
+
+	> .no
+		margin 0
+		padding 16px
+		text-align center
+		color #aaa
+
+	> .fetching
+		margin 0
+		padding 16px
+		text-align center
+		color #aaa
+
+		> [data-fa]
+			margin-right 4px
+
+</style>