diff --git a/src/client/app/common/views/components/ui/card.vue b/src/client/app/common/views/components/ui/card.vue
index 5ba15dad7..97f06ca65 100644
--- a/src/client/app/common/views/components/ui/card.vue
+++ b/src/client/app/common/views/components/ui/card.vue
@@ -10,17 +10,23 @@
 
 <script lang="ts">
 import Vue from 'vue';
-export default Vue.extend({});
+export default Vue.extend({
+	provide() {
+		return {
+			isCardChild: true
+		};
+	}
+});
 </script>
 
 <style lang="stylus" scoped>
 @import '~const.styl'
 
 .ui-card
-	margin 16px 0
-	padding 32px
+	margin 16px
+	padding 16px
 	background #fff
-	//box-shadow 0 3px 1px -2px rgba(#000, 0.2), 0 2px 2px 0 rgba(#000, 0.14), 0 1px 5px 0 rgba(#000, 0.12)
+	box-shadow 0 3px 1px -2px rgba(#000, 0.2), 0 2px 2px 0 rgba(#000, 0.14), 0 1px 5px 0 rgba(#000, 0.12)
 
 	> header
 		font-weight bold
diff --git a/src/client/app/common/views/components/ui/input.vue b/src/client/app/common/views/components/ui/input.vue
index 7461aac7f..3a474f024 100644
--- a/src/client/app/common/views/components/ui/input.vue
+++ b/src/client/app/common/views/components/ui/input.vue
@@ -1,21 +1,35 @@
 <template>
-<div class="ui-input" :class="{ focused, filled }">
+<div class="ui-input" :class="[{ focused, filled }, styl]">
+	<div class="icon" ref="icon"><slot name="icon"></slot></div>
 	<div class="input" @click="focus">
 		<div class="password-meter" v-if="withPasswordMeter" v-show="passwordStrength != ''" :data-strength="passwordStrength">
 			<div class="value" ref="passwordMetar"></div>
 		</div>
 		<span class="label" ref="label"><slot></slot></span>
 		<div class="prefix" ref="prefix"><slot name="prefix"></slot></div>
-		<input ref="input"
-				:type="type"
-				:value="value"
-				:required="required"
-				:readonly="readonly"
-				:pattern="pattern"
-				:autocomplete="autocomplete"
-				@input="$emit('input', $event.target.value)"
-				@focus="focused = true"
-				@blur="focused = false">
+		<template v-if="type != 'file'">
+			<input ref="input"
+					:type="type"
+					:value="v"
+					:required="required"
+					:readonly="readonly"
+					:pattern="pattern"
+					:autocomplete="autocomplete"
+					@input="$emit('input', $event.target.value)"
+					@focus="focused = true"
+					@blur="focused = false">
+		</template>
+		<template v-else>
+			<input ref="input"
+					type="text"
+					:value="placeholder"
+					readonly
+					@click="chooseFile">
+			<input ref="file"
+					type="file"
+					:value="value"
+					@change="onChangeFile">
+		</template>
 		<div class="suffix"><slot name="suffix"></slot></div>
 	</div>
 	<div class="text"><slot name="text"></slot></div>
@@ -59,17 +73,34 @@ export default Vue.extend({
 	},
 	data() {
 		return {
+			v: this.value,
 			focused: false,
-			passwordStrength: ''
-		}
+			passwordStrength: '',
+			styl: 'fill'
+		};
 	},
 	computed: {
 		filled(): boolean {
-			return this.value != '' && this.value != null;
+			return this.v != '' && this.v != null;
+		},
+		placeholder(): string {
+			if (this.type != 'file') return null;
+			if (this.v == null) return null;
+
+			if (typeof this.v == 'string') return this.v;
+
+			if (Array.isArray(this.v)) {
+				return this.v.map(file => file.name).join(', ');
+			} else {
+				return this.v.name;
+			}
 		}
 	},
 	watch: {
 		value(v) {
+			this.v = v;
+		},
+		v(v) {
 			if (this.withPasswordMeter) {
 				if (v == '') {
 					this.passwordStrength = '';
@@ -82,6 +113,12 @@ export default Vue.extend({
 			}
 		}
 	},
+	inject: ['isCardChild'],
+	created() {
+		if (this.isCardChild) {
+			this.styl = 'line';
+		}
+	},
 	mounted() {
 		if (this.$refs.prefix) {
 			this.$refs.label.style.left = (this.$refs.prefix.offsetLeft + this.$refs.prefix.offsetWidth) + 'px';
@@ -90,6 +127,14 @@ export default Vue.extend({
 	methods: {
 		focus() {
 			this.$refs.input.focus();
+		},
+		chooseFile() {
+			this.$refs.file.click();
+		},
+		onChangeFile() {
+			this.v = Array.from((this.$refs.file as any).files);
+			this.$emit('input', this.v);
+			this.$emit('change', this.v);
 		}
 	}
 });
@@ -98,14 +143,52 @@ export default Vue.extend({
 <style lang="stylus" scoped>
 @import '~const.styl'
 
-.ui-input
+root(isDark, fill)
 	margin 32px 0
 
+	> .icon
+		position absolute
+		top 0
+		left 0
+		width 24px
+		text-align center
+		line-height 32px
+		color rgba(#000, 0.54)
+
+		&:not(:empty) + .input
+			margin-left 28px
+
 	> .input
 		display flex
-		padding 6px 12px
-		background rgba(#000, 0.035)
-		border-radius 6px
+
+		if fill
+			padding 6px 12px
+			background rgba(#000, 0.035)
+			border-radius 6px
+		else
+			&:before
+				content ''
+				display block
+				position absolute
+				bottom 0
+				left 0
+				right 0
+				height 1px
+				background rgba(#000, 0.42)
+
+			&:after
+				content ''
+				display block
+				position absolute
+				bottom 0
+				left 0
+				right 0
+				height 2px
+				background $theme-color
+				opacity 0
+				transform scaleX(0.12)
+				transition border 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)
+				will-change border opacity transform
 
 		> .password-meter
 			position absolute
@@ -142,7 +225,7 @@ export default Vue.extend({
 
 		> .label
 			position absolute
-			top 6px
+			top fill ? 6px : 0
 			left 0
 			pointer-events none
 			transition 0.4s cubic-bezier(0.25, 0.8, 0.25, 1)
@@ -161,7 +244,7 @@ export default Vue.extend({
 			width 100%
 			padding 0
 			font inherit
-			font-weight bold
+			font-weight fill ? bold : normal
 			font-size 16px
 			line-height 32px
 			background transparent
@@ -170,6 +253,9 @@ export default Vue.extend({
 			outline none
 			box-shadow none
 
+			&[type='file']
+				display none
+
 		> .prefix
 		> .suffix
 			display block
@@ -199,7 +285,12 @@ export default Vue.extend({
 
 	&.focused
 		> .input
-			background rgba(#000, 0.05)
+			if fill
+				background rgba(#000, 0.05)
+			else
+				&:after
+					opacity 1
+					transform scaleX(1)
 
 			> .label
 				color $theme-color
@@ -208,8 +299,20 @@ export default Vue.extend({
 	&.filled
 		> .input
 			> .label
-				top -24px
+				top fill ? -24px : -16px
 				left 0 !important
 				transform scale(0.8)
 
+.ui-input[data-darkmode]
+	&.fill
+		root(true, true)
+	&:not(.fill)
+		root(true, false)
+
+.ui-input:not([data-darkmode])
+	&.fill
+		root(false, true)
+	&:not(.fill)
+		root(false, false)
+
 </style>
diff --git a/src/client/app/common/views/components/ui/switch.vue b/src/client/app/common/views/components/ui/switch.vue
index e78951a35..f86030986 100644
--- a/src/client/app/common/views/components/ui/switch.vue
+++ b/src/client/app/common/views/components/ui/switch.vue
@@ -143,7 +143,6 @@ root(isDark)
 		> span
 			display block
 			line-height 20px
-			font-weight bold
 			color isDark ? #c4ccd2 : rgba(#000, 0.75)
 			transition inherit
 
diff --git a/src/client/app/common/views/components/ui/textarea.vue b/src/client/app/common/views/components/ui/textarea.vue
index 0a9f60f1b..d5e2b1562 100644
--- a/src/client/app/common/views/components/ui/textarea.vue
+++ b/src/client/app/common/views/components/ui/textarea.vue
@@ -65,13 +65,43 @@ export default Vue.extend({
 <style lang="stylus" scoped>
 @import '~const.styl'
 
-.ui-textarea
+root(isDark, fill)
 	margin 32px 0
 
 	> .input
 		padding 12px
-		background rgba(#000, 0.035)
-		border-radius 6px
+
+		if fill
+			background rgba(#000, 0.035)
+			border-radius 6px
+		else
+			&:before
+				content ''
+				display block
+				position absolute
+				top 0
+				bottom 0
+				left 0
+				right 0
+				background none
+				border solid 1px rgba(#000, 0.42)
+				border-radius 3px
+				pointer-events none
+
+			&:after
+				content ''
+				display block
+				position absolute
+				top 0
+				bottom 0
+				left 0
+				right 0
+				background none
+				border solid 2px $theme-color
+				border-radius 3px
+				opacity 0
+				transition opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1)
+				pointer-events none
 
 		> .label
 			position absolute
@@ -94,7 +124,7 @@ export default Vue.extend({
 			min-height 100px
 			padding 0
 			font inherit
-			font-weight bold
+			font-weight fill ? bold : normal
 			font-size 16px
 			background transparent
 			border none
@@ -111,7 +141,11 @@ export default Vue.extend({
 
 	&.focused
 		> .input
-			background rgba(#000, 0.05)
+			if fill
+				background rgba(#000, 0.05)
+			else
+				&:after
+					opacity 1
 
 			> .label
 				color $theme-color
@@ -124,4 +158,16 @@ export default Vue.extend({
 				left 0 !important
 				transform scale(0.8)
 
+.ui-textarea[data-darkmode]
+	&.fill
+		root(true, true)
+	&:not(.fill)
+		root(true, false)
+
+.ui-textarea:not([data-darkmode])
+	&.fill
+		root(false, true)
+	&:not(.fill)
+		root(false, false)
+
 </style>
diff --git a/src/client/app/mobile/views/pages/settings/settings.profile.vue b/src/client/app/mobile/views/pages/settings/settings.profile.vue
index de891b573..64adac01e 100644
--- a/src/client/app/mobile/views/pages/settings/settings.profile.vue
+++ b/src/client/app/mobile/views/pages/settings/settings.profile.vue
@@ -29,12 +29,12 @@
 
 		<ui-input type="file" @change="onAvatarChange">
 			<span>%i18n:@avatar%</span>
-			<span slot="prefix">%fa:picture-o%</span>
+			<span slot="icon">%fa:image%</span>
 		</ui-input>
 
 		<ui-input type="file" @change="onBannerChange">
 			<span>%i18n:@banner%</span>
-			<span slot="prefix">%fa:picture-o%</span>
+			<span slot="icon">%fa:image%</span>
 		</ui-input>
 
 		<ui-switch v-model="isCat">%i18n:@is-cat%</ui-switch>