diff --git a/apps/settings/css/settings.scss b/apps/settings/css/settings.scss
index 80c431008fc0e57832042ba8f30ac4b74f37eb80..47a035016add020b42941f00cb0c9da499ac5a97 100644
--- a/apps/settings/css/settings.scss
+++ b/apps/settings/css/settings.scss
@@ -524,7 +524,6 @@ td, th {
 		visibility: hidden;
 	}
 	&.password,
-	&.displayName,
 	&.mailAddress {
 		min-width: 5em;
 		max-width: 12em;
@@ -705,6 +704,7 @@ span.version {
 	#searchresults {
 		display: none;
 	}
+
 }
 #apps-list.store {
 	.section {
@@ -1351,8 +1351,8 @@ doesnotexist:-o-prefocus, .strengthify-wrapper {
 
 /* USERS LIST -------------------------------------------------------------- */
 #body-settings {
-	$grid-row-height: 46px;
-	$grid-col-min-width: 120px;
+	$grid-row-height: 60px;
+	$grid-col-min-width: 150px;
 	#app-content.user-list-grid {
 		display: grid;
 		grid-auto-columns: 1fr;
@@ -1376,7 +1376,6 @@ doesnotexist:-o-prefocus, .strengthify-wrapper {
 
 			/* grid col width */
 			.name,
-			.displayName,
 			.password,
 			.mailAddress,
 			.languages,
@@ -1384,12 +1383,17 @@ doesnotexist:-o-prefocus, .strengthify-wrapper {
 			.userBackend,
 			.lastLogin {
 				min-width: $grid-col-min-width;
+				display: flex;
+				color:  var(--color-text-dark);
+				vertical-align: baseline;
 			}
 			.groups,
 			.subadmins,
 			.quota {
 				.multiselect {
 					min-width: $grid-col-min-width;
+					color: var(--color-text-dark);
+					vertical-align: baseline;
 				}
 			}
 			.obfuscated {
@@ -1399,6 +1403,10 @@ doesnotexist:-o-prefocus, .strengthify-wrapper {
 			.userActions {
 				min-width: 44px;
 			}
+			.subtitle {
+				color: var(--color-text-maxcontrast);
+				vertical-align: baseline;
+			}
 
 			/* various */
 			&#grid-header,
@@ -1427,16 +1435,23 @@ doesnotexist:-o-prefocus, .strengthify-wrapper {
 			&#grid-header {
 				color: var(--color-text-maxcontrast);
 				z-index: 60; /* above new-user */
+				border-bottom-width: thin;
 
 				#headerDisplayName,
 				#headerPassword,
 				#headerAddress,
 				#headerGroups,
 				#headerSubAdmins,
+				#theHeaderUserBackend,
+				#theHeaderLastLogin,
 				#headerQuota,
+				#theHeaderStorageLocation,
 				#headerLanguages {
 					/* Line up header text with column content for when there’s inputs */
 					padding-left: 7px;
+					text-transform: none;
+					color: var(--color-text-maxcontrast);
+					vertical-align: baseline;
 				}
 			}
 			&:hover {
@@ -1451,8 +1466,7 @@ doesnotexist:-o-prefocus, .strengthify-wrapper {
 			> form {
 				grid-row: 1;
 				display: inline-flex;
-				align-items: center;
-				color: var(--color-text);
+				color: var(--color-text-lighter);
 				position: relative;
 				> input:not(:focus):not(:active) {
 					border-color: transparent;
@@ -1478,7 +1492,7 @@ doesnotexist:-o-prefocus, .strengthify-wrapper {
 					}
 				}
 				&.name,
-				&.storageLocation {
+				&.userBackend {
 					/* better multi-line visual */
 					line-height: 1.3em;
 					max-height: 100%;
@@ -1492,16 +1506,14 @@ doesnotexist:-o-prefocus, .strengthify-wrapper {
 					-webkit-box-orient: vertical;
 				}
 				&.quota {
-                    .multiselect--active + progress {
-                        display: none;
-                    }
+					height: 44px;
+					display: flex;
+					align-items: center;
+					justify-content: center;
 					progress {
-						position: absolute;
-						width: calc(100% - 4px); /* minus left and right */
-						left: 2px;
-						bottom: 2px;
+						width: 100%;
+						margin: 0 10px;
 						height: 3px;
-						z-index: 5; /* above multiselect */
 					}
 				}
 				.icon-confirm {
@@ -1520,16 +1532,22 @@ doesnotexist:-o-prefocus, .strengthify-wrapper {
 					}
 				}
 				&.userActions {
+				  .action-item {
+					position: absolute;
+				  }
 					#newsubmit {
 						width: 100%;
 					}
 					.toggleUserActions {
 						position: relative;
+						display: block;
+						align-items: center;
 						.icon-more {
 							width: 44px;
 							height: 44px;
 							opacity: .5;
 							cursor: pointer;
+							margin-left: 40px;
 							&:hover {
 								opacity: .7;
 							}
diff --git a/apps/settings/js/vue-1.js b/apps/settings/js/vue-1.js
new file mode 100644
index 0000000000000000000000000000000000000000..0b135343ce877aa2cf3a7826c5cc67334729d685
Binary files /dev/null and b/apps/settings/js/vue-1.js differ
diff --git a/apps/settings/js/vue-1.js.map b/apps/settings/js/vue-1.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..6b1d19d75f0c3fca35601271261ba195f54f1761
Binary files /dev/null and b/apps/settings/js/vue-1.js.map differ
diff --git a/apps/settings/js/vue-2.js b/apps/settings/js/vue-2.js
new file mode 100644
index 0000000000000000000000000000000000000000..4dce54646a6b10a67d417c2d5c2e0e436f081587
Binary files /dev/null and b/apps/settings/js/vue-2.js differ
diff --git a/apps/settings/js/vue-2.js.map b/apps/settings/js/vue-2.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..28bf656ae49aff4c91617b8e6a231380bf9bb1f7
Binary files /dev/null and b/apps/settings/js/vue-2.js.map differ
diff --git a/apps/settings/js/vue-3.js b/apps/settings/js/vue-3.js
new file mode 100644
index 0000000000000000000000000000000000000000..e22c08ce9ceb3dd25021369f1d6e71e0a2ad8369
Binary files /dev/null and b/apps/settings/js/vue-3.js differ
diff --git a/apps/settings/js/vue-3.js.map b/apps/settings/js/vue-3.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..ae47034de33a07d5b3ae70737620f7eb7446ac7e
Binary files /dev/null and b/apps/settings/js/vue-3.js.map differ
diff --git a/apps/settings/js/vue-4.js b/apps/settings/js/vue-4.js
index be21d10b35e1c81cbd6ac945dcb729e6a53222e0..00d70b2c14c613bda95bed2a5c65cc3c769cccd9 100644
Binary files a/apps/settings/js/vue-4.js and b/apps/settings/js/vue-4.js differ
diff --git a/apps/settings/js/vue-4.js.map b/apps/settings/js/vue-4.js.map
index d78ea55117994640840113ac4e5f206d6defba58..0cea8d852da4e5e40b72da1575e17dfa3863446f 100644
Binary files a/apps/settings/js/vue-4.js.map and b/apps/settings/js/vue-4.js.map differ
diff --git a/apps/settings/js/vue-6.js b/apps/settings/js/vue-6.js
index 65699a0042d33f37eb2284e9bcbde9f061f29165..71d385dca0e56fdd6e3584ecf98b0f3f286f5f61 100644
Binary files a/apps/settings/js/vue-6.js and b/apps/settings/js/vue-6.js differ
diff --git a/apps/settings/js/vue-6.js.map b/apps/settings/js/vue-6.js.map
index 587c3cee5c90881b453f64b61b884364cd0e7dd3..6849a40965d2108dda64174399538fb87d259d87 100644
Binary files a/apps/settings/js/vue-6.js.map and b/apps/settings/js/vue-6.js.map differ
diff --git a/apps/settings/js/vue-settings-admin-security.js b/apps/settings/js/vue-settings-admin-security.js
index 9cbce132bec2ae54cd43f9f5ccad4466c42b814c..6aff2b05c171f2b15c3403b11bbf6aecba122c8d 100644
Binary files a/apps/settings/js/vue-settings-admin-security.js and b/apps/settings/js/vue-settings-admin-security.js differ
diff --git a/apps/settings/js/vue-settings-admin-security.js.map b/apps/settings/js/vue-settings-admin-security.js.map
index c0f90eeba04f70fc7d68b0820dd826d42d625bc4..fe38171929b12d0e827d8b4c6d49f225477e3f94 100644
Binary files a/apps/settings/js/vue-settings-admin-security.js.map and b/apps/settings/js/vue-settings-admin-security.js.map differ
diff --git a/apps/settings/js/vue-settings-apps-users-management.js b/apps/settings/js/vue-settings-apps-users-management.js
index 5b9b5beee627f3c98d599d12ecea191327c52951..8946490a3f9d730cb014578330015d55884f3d36 100644
Binary files a/apps/settings/js/vue-settings-apps-users-management.js and b/apps/settings/js/vue-settings-apps-users-management.js differ
diff --git a/apps/settings/js/vue-settings-apps-users-management.js.map b/apps/settings/js/vue-settings-apps-users-management.js.map
index bfaa3be2d04ab982c6dfa9451f2db0cf62ea42d5..e7a8b7f840e2a07a4c664d777ae411c6573515d9 100644
Binary files a/apps/settings/js/vue-settings-apps-users-management.js.map and b/apps/settings/js/vue-settings-apps-users-management.js.map differ
diff --git a/apps/settings/js/vue-settings-personal-security.js b/apps/settings/js/vue-settings-personal-security.js
index f56c5c3ce42e87e14cee1ce82b6165d3435bb609..7279cf340e8864ab243d116718c6968cbf732545 100644
Binary files a/apps/settings/js/vue-settings-personal-security.js and b/apps/settings/js/vue-settings-personal-security.js differ
diff --git a/apps/settings/js/vue-settings-personal-security.js.map b/apps/settings/js/vue-settings-personal-security.js.map
index ed9d9486f95e434f71b8b59edb713f1cf97d60d6..0d8b0f2daed2e3c6172c1ef84e0e3bceee28b974 100644
Binary files a/apps/settings/js/vue-settings-personal-security.js.map and b/apps/settings/js/vue-settings-personal-security.js.map differ
diff --git a/apps/settings/src/components/AppList.vue b/apps/settings/src/components/AppList.vue
index a406f6b8ff63e81d44551a6a86118d9598e5e576..3259011497aa99b4d54c421d5d99f1e2aab73340 100644
--- a/apps/settings/src/components/AppList.vue
+++ b/apps/settings/src/components/AppList.vue
@@ -29,7 +29,9 @@
 					<button v-if="showUpdateAll"
 						id="app-list-update-all"
 						class="primary"
-						@click="updateAll">{{t('settings', 'Update all')}}</button>
+						@click="updateAll">
+						{{ t('settings', 'Update all') }}
+					</button>
 				</div>
 				<transition-group name="app-list" tag="div" class="apps-list-container">
 					<AppItem v-for="app in apps"
diff --git a/apps/settings/src/components/UserList.vue b/apps/settings/src/components/UserList.vue
index 57e40b9eb0be4b7acd3b09d4f2933be5754a604c..fc3ee47065a79c46fef116c5d2bc3c3001a5ba89 100644
--- a/apps/settings/src/components/UserList.vue
+++ b/apps/settings/src/components/UserList.vue
@@ -22,13 +22,16 @@
 
 <template>
 	<div id="app-content" class="user-list-grid" @scroll.passive="onScroll">
-		<div id="grid-header" class="row" :class="{'sticky': scrolled && !showConfig.showNewUserForm}">
+		<div id="grid-header"
+			:class="{'sticky': scrolled && !showConfig.showNewUserForm}"
+			class="row">
 			<div id="headerAvatar" class="avatar" />
 			<div id="headerName" class="name">
 				{{ t('settings', 'Username') }}
-			</div>
-			<div id="headerDisplayName" class="displayName">
-				{{ t('settings', 'Display name') }}
+
+				<div class="subtitle">
+					{{ t('settings', 'Display name') }}
+				</div>
 			</div>
 			<div id="headerPassword" class="password">
 				{{ t('settings', 'Password') }}
@@ -52,99 +55,103 @@
 				class="languages">
 				{{ t('settings', 'Language') }}
 			</div>
-			<div v-if="showConfig.showStoragePath"
-				class="headerStorageLocation storageLocation">
-				{{ t('settings', 'Storage location') }}
-			</div>
-			<div v-if="showConfig.showUserBackend"
+
+			<div v-if="showConfig.showUserBackend || showConfig.showStoragePath"
 				class="headerUserBackend userBackend">
-				{{ t('settings', 'User backend') }}
+				<div v-if="showConfig.showUserBackend" class="userBackend">
+					{{ t('settings', 'User backend') }}
+				</div>
+				<div v-if="showConfig.showStoragePath"
+					class="subtitle storageLocation">
+					{{ t('settings', 'Storage location') }}
+				</div>
 			</div>
 			<div v-if="showConfig.showLastLogin"
 				class="headerLastLogin lastLogin">
 				{{ t('settings', 'Last login') }}
 			</div>
+
 			<div class="userActions" />
 		</div>
 
 		<form v-show="showConfig.showNewUserForm"
 			id="new-user"
-			class="row"
-			:disabled="loading.all"
 			:class="{'sticky': scrolled && showConfig.showNewUserForm}"
+			:disabled="loading.all"
+			class="row"
 			@submit.prevent="createUser">
 			<div :class="loading.all?'icon-loading-small':'icon-add'" />
 			<div class="name">
 				<input id="newusername"
 					ref="newusername"
 					v-model="newUser.id"
-					type="text"
-					required
+					:disabled="settings.newUserGenerateUserID"
 					:placeholder="settings.newUserGenerateUserID
 						? t('settings', 'Will be autogenerated')
 						: t('settings', 'Username')"
-					name="username"
-					autocomplete="off"
 					autocapitalize="none"
+					autocomplete="off"
 					autocorrect="off"
+					name="username"
 					pattern="[a-zA-Z0-9 _\.@\-']+"
-					:disabled="settings.newUserGenerateUserID">
+					required
+					type="text">
 			</div>
 			<div class="displayName">
 				<input id="newdisplayname"
 					v-model="newUser.displayName"
-					type="text"
 					:placeholder="t('settings', 'Display name')"
-					name="displayname"
-					autocomplete="off"
 					autocapitalize="none"
-					autocorrect="off">
+					autocomplete="off"
+					autocorrect="off"
+					name="displayname"
+					type="text">
 			</div>
 			<div class="password">
 				<input id="newuserpassword"
 					ref="newuserpassword"
 					v-model="newUser.password"
-					type="password"
-					:required="newUser.mailAddress===''"
+					:minlength="minPasswordLength"
 					:placeholder="t('settings', 'Password')"
-					name="password"
-					autocomplete="new-password"
+					:required="newUser.mailAddress===''"
 					autocapitalize="none"
+					autocomplete="new-password"
 					autocorrect="off"
-					:minlength="minPasswordLength">
+					name="password"
+					type="password">
 			</div>
 			<div class="mailAddress">
 				<input id="newemail"
 					v-model="newUser.mailAddress"
-					type="email"
-					:required="newUser.password==='' || settings.newUserRequireEmail"
 					:placeholder="t('settings', 'Email')"
-					name="email"
-					autocomplete="off"
+					:required="newUser.password==='' || settings.newUserRequireEmail"
 					autocapitalize="none"
-					autocorrect="off">
+					autocomplete="off"
+					autocorrect="off"
+					name="email"
+					type="email">
 			</div>
 			<div class="groups">
 				<!-- hidden input trick for vanilla html5 form validation -->
 				<input v-if="!settings.isAdmin"
 					id="newgroups"
-					type="text"
+					:class="{'icon-loading-small': loading.groups}"
+					:required="!settings.isAdmin"
 					:value="newUser.groups"
 					tabindex="-1"
-					:required="!settings.isAdmin"
-					:class="{'icon-loading-small': loading.groups}">
+					type="text">
 				<Multiselect v-model="newUser.groups"
-					:options="canAddGroups"
+					:close-on-select="false"
 					:disabled="loading.groups||loading.all"
-					tag-placeholder="create"
+					:multiple="true"
+					:options="canAddGroups"
 					:placeholder="t('settings', 'Add user in group')"
+					:tag-width="60"
+					:taggable="true"
+					class="multiselect-vue"
 					label="name"
+					tag-placeholder="create"
 					track-by="id"
-					class="multiselect-vue"
-					:multiple="true"
-					:taggable="true"
-					:close-on-select="false"
-					:tag-width="60"
 					@tag="createGroup">
 					<!-- If user is not admin, he is a subadmin.
 						Subadmins can't create users outside their groups
@@ -152,63 +159,64 @@
 					<span slot="noResult">{{ t('settings', 'No results') }}</span>
 				</Multiselect>
 			</div>
-			<div v-if="subAdminsGroups.length>0 && settings.isAdmin" class="subadmins">
+			<div v-if="subAdminsGroups.length>0 && settings.isAdmin"
+				class="subadmins">
 				<Multiselect v-model="newUser.subAdminsGroups"
+					:close-on-select="false"
+					:multiple="true"
 					:options="subAdminsGroups"
 					:placeholder="t('settings', 'Set user as admin for')"
-					label="name"
-					track-by="id"
+					:tag-width="60"
 					class="multiselect-vue"
-					:multiple="true"
-					:close-on-select="false"
-					:tag-width="60">
+					label="name"
+					track-by="id">
 					<span slot="noResult">{{ t('settings', 'No results') }}</span>
 				</Multiselect>
 			</div>
 			<div class="quota">
 				<Multiselect v-model="newUser.quota"
+					:allow-empty="false"
 					:options="quotaOptions"
 					:placeholder="t('settings', 'Select user quota')"
+					:taggable="true"
+					class="multiselect-vue"
 					label="label"
 					track-by="id"
-					class="multiselect-vue"
-					:allow-empty="false"
-					:taggable="true"
 					@tag="validateQuota" />
 			</div>
 			<div v-if="showConfig.showLanguages" class="languages">
 				<Multiselect v-model="newUser.language"
+					:allow-empty="false"
 					:options="languages"
 					:placeholder="t('settings', 'Default language')"
-					label="name"
-					track-by="code"
 					class="multiselect-vue"
-					:allow-empty="false"
+					group-label="label"
 					group-values="languages"
-					group-label="label" />
+					label="name"
+					track-by="code" />
 			</div>
 			<div v-if="showConfig.showStoragePath" class="storageLocation" />
 			<div v-if="showConfig.showUserBackend" class="userBackend" />
 			<div v-if="showConfig.showLastLogin" class="lastLogin" />
 			<div class="userActions">
 				<input id="newsubmit"
-					type="submit"
+					:title="t('settings', 'Add a new user')"
 					class="button primary icon-checkmark-white has-tooltip"
-					value=""
-					:title="t('settings', 'Add a new user')">
+					type="submit"
+					value="">
 			</div>
 		</form>
 
 		<user-row v-for="(user, key) in filteredUsers"
 			:key="key"
-			:user="user"
+			:external-actions="externalActions"
+			:groups="groups"
+			:languages="languages"
+			:quota-options="quotaOptions"
 			:settings="settings"
 			:show-config="showConfig"
-			:groups="groups"
 			:sub-admins-groups="subAdminsGroups"
-			:quota-options="quotaOptions"
-			:languages="languages"
-			:external-actions="externalActions" />
+			:user="user" />
 		<InfiniteLoading ref="infiniteLoading" @infinite="infiniteHandler">
 			<div slot="spinner">
 				<div class="users-icon-loading icon-loading" />
@@ -328,7 +336,10 @@ export default {
 		},
 		quotaOptions() {
 			// convert the preset array into objects
-			let quotaPreset = this.settings.quotaPreset.reduce((acc, cur) => acc.concat({ id: cur, label: cur }), [])
+			let quotaPreset = this.settings.quotaPreset.reduce((acc, cur) => acc.concat({
+				id: cur,
+				label: cur
+			}), [])
 			// add default presets
 			quotaPreset.unshift(this.unlimitedQuota)
 			quotaPreset.unshift(this.defaultQuota)
@@ -377,9 +388,9 @@ export default {
 			// deleting the last user, reset the list
 			if (val === 0 && old === 1) {
 				this.$refs.infiniteLoading.stateChanger.reset()
-			// adding the first user, warn the infiniteLoader that
-			// the list is not empty anymore (we don't fetch the newly
-			// added user as we already have all the info we need)
+				// adding the first user, warn the infiniteLoader that
+				// the list is not empty anymore (we don't fetch the newly
+				// added user as we already have all the info we need)
 			} else if (val === 1 && old === 0) {
 				this.$refs.infiniteLoading.stateChanger.loaded()
 			}
@@ -437,7 +448,9 @@ export default {
 				group: this.selectedGroup !== 'disabled' ? this.selectedGroup : '',
 				search: this.searchQuery
 			})
-				.then((response) => { response ? $state.loaded() : $state.complete() })
+				.then((response) => {
+					response ? $state.loaded() : $state.complete()
+				})
 		},
 
 		/* SEARCH */
@@ -492,10 +505,10 @@ export default {
 					if (error.response && error.response.data && error.response.data.ocs && error.response.data.ocs.meta) {
 						const statuscode = error.response.data.ocs.meta.statuscode
 						if (statuscode === 102) {
-						// wrong username
+							// wrong username
 							this.$refs.newusername.focus()
 						} else if (statuscode === 107) {
-						// wrong password
+							// wrong password
 							this.$refs.newuserpassword.focus()
 						}
 					}
@@ -542,7 +555,7 @@ export default {
 		redirectIfDisabled() {
 			const allGroups = this.$store.getters.getGroups
 			if (this.selectedGroup === 'disabled'
-				&& allGroups.findIndex(group => group.id === 'disabled' && group.usercount === 0) > -1) {
+						&& allGroups.findIndex(group => group.id === 'disabled' && group.usercount === 0) > -1) {
 				// disabled group is empty, redirection to all users
 				this.$router.push({ name: 'users' })
 				this.$refs.infiniteLoading.stateChanger.reset()
diff --git a/apps/settings/src/components/UserList/UserRow.vue b/apps/settings/src/components/UserList/UserRow.vue
index 435f1b8bb7c1d75d17cb4db23d281c1db77a7e1d..c818cc3e73382220ad47246735fe1b420a031f08 100644
--- a/apps/settings/src/components/UserList/UserRow.vue
+++ b/apps/settings/src/components/UserList/UserRow.vue
@@ -24,14 +24,15 @@
 
 <template>
 	<!-- Obfuscated user: Logged in user does not have permissions to see all of the data -->
-	<div v-if="Object.keys(user).length ===1" class="row" :data-id="user.id">
-		<div class="avatar" :class="{'icon-loading-small': loading.delete || loading.disable || loading.wipe}">
+	<div v-if="Object.keys(user).length ===1" :data-id="user.id" class="row">
+		<div :class="{'icon-loading-small': loading.delete || loading.disable || loading.wipe}"
+			class="avatar">
 			<img v-if="!loading.delete && !loading.disable && !loading.wipe"
+				:src="generateAvatar(user.id, 32)"
+				:srcset="generateAvatar(user.id, 64)+' 2x, '+generateAvatar(user.id, 128)+' 4x'"
 				alt=""
-				width="32"
 				height="32"
-				:src="generateAvatar(user.id, 32)"
-				:srcset="generateAvatar(user.id, 64)+' 2x, '+generateAvatar(user.id, 128)+' 4x'">
+				width="32">
 		</div>
 		<div class="name">
 			{{ user.id }}
@@ -42,163 +43,189 @@
 	</div>
 
 	<!-- User full data -->
+	<UserRowSimple
+		v-else-if="!editing"
+		:editing.sync="editing"
+		:feedback-message="feedbackMessage"
+		:groups="groups"
+		:languages="languages"
+		:loading="loading"
+		:opened-menu="openedMenu"
+		:settings="settings"
+		:show-config="showConfig"
+		:sub-admins-groups="subAdminsGroups"
+		:user-actions="userActions"
+		:user="user"
+		@hideMenu="hideMenu"
+		@toggleMenu="toggleMenu" />
 	<div v-else
-		class="row"
 		:class="{'disabled': loading.delete || loading.disable}"
-		:data-id="user.id">
-		<div class="avatar" :class="{'icon-loading-small': loading.delete || loading.disable || loading.wipe}">
+		:data-id="user.id"
+		class="row row--editable">
+		<div :class="{'icon-loading-small': loading.delete || loading.disable || loading.wipe}"
+			class="avatar">
 			<img v-if="!loading.delete && !loading.disable && !loading.wipe"
+				:src="generateAvatar(user.id, 32)"
+				:srcset="generateAvatar(user.id, 64)+' 2x, '+generateAvatar(user.id, 128)+' 4x'"
 				alt=""
-				width="32"
 				height="32"
-				:src="generateAvatar(user.id, 32)"
-				:srcset="generateAvatar(user.id, 64)+' 2x, '+generateAvatar(user.id, 128)+' 4x'">
+				width="32">
 		</div>
 		<!-- dirty hack to ellipsis on two lines -->
-		<div class="name">
-			{{ user.id }}
+		<div class="displayName">
+			<form
+				:class="{'icon-loading-small': loading.displayName}"
+				class="displayName"
+				@submit.prevent="updateDisplayName">
+				<template v-if="user.backendCapabilities.setDisplayName">
+					<input v-if="user.backendCapabilities.setDisplayName"
+						:id="'displayName'+user.id+rand"
+						ref="displayName"
+						:disabled="loading.displayName||loading.all"
+						:value="user.displayname"
+						autocapitalize="off"
+						autocomplete="new-password"
+						autocorrect="off"
+						spellcheck="false"
+						type="text">
+					<input v-if="user.backendCapabilities.setDisplayName"
+						class="icon-confirm"
+						type="submit"
+						value="">
+				</template>
+				<div v-else
+					v-tooltip.auto="t('settings', 'The backend does not support changing the display name')"
+					class="name" />
+			</form>
 		</div>
-		<form class="displayName" :class="{'icon-loading-small': loading.displayName}" @submit.prevent="updateDisplayName">
-			<template v-if="user.backendCapabilities.setDisplayName">
-				<input v-if="user.backendCapabilities.setDisplayName"
-					:id="'displayName'+user.id+rand"
-					ref="displayName"
-					type="text"
-					:disabled="loading.displayName||loading.all"
-					:value="user.displayname"
-					autocomplete="new-password"
-					autocorrect="off"
-					autocapitalize="off"
-					spellcheck="false">
-				<input v-if="user.backendCapabilities.setDisplayName"
-					type="submit"
-					class="icon-confirm"
-					value="">
-			</template>
-			<div v-else v-tooltip.auto="t('settings', 'The backend does not support changing the display name')" class="name">
-				{{ user.displayname }}
-			</div>
-		</form>
 		<form v-if="settings.canChangePassword && user.backendCapabilities.setPassword"
-			class="password"
 			:class="{'icon-loading-small': loading.password}"
+			class="password"
 			@submit.prevent="updatePassword">
 			<input :id="'password'+user.id+rand"
 				ref="password"
-				type="password"
-				required
-				:disabled="loading.password||loading.all"
+				:disabled="loading.password || loading.all"
 				:minlength="minPasswordLength"
-				value=""
-				:placeholder="t('settings', 'New password')"
+				:placeholder="t('settings', 'Add new password')"
+				autocapitalize="off"
 				autocomplete="new-password"
 				autocorrect="off"
-				autocapitalize="off"
-				spellcheck="false">
-			<input type="submit" class="icon-confirm" value="">
+				required
+				spellcheck="false"
+				type="password"
+				value="">
+			<input class="icon-confirm" type="submit" value="">
 		</form>
 		<div v-else />
-		<form class="mailAddress" :class="{'icon-loading-small': loading.mailAddress}" @submit.prevent="updateEmail">
+		<form :class="{'icon-loading-small': loading.mailAddress}"
+			class="mailAddress"
+			@submit.prevent="updateEmail">
 			<input :id="'mailAddress'+user.id+rand"
 				ref="mailAddress"
-				type="email"
 				:disabled="loading.mailAddress||loading.all"
+				:placeholder="t('settings', 'Add new email address')"
 				:value="user.email"
+				autocapitalize="off"
 				autocomplete="new-password"
 				autocorrect="off"
-				autocapitalize="off"
-				spellcheck="false">
-			<input type="submit" class="icon-confirm" value="">
+				spellcheck="false"
+				type="email">
+			<input class="icon-confirm" type="submit" value="">
 		</form>
-		<div class="groups" :class="{'icon-loading-small': loading.groups}">
-			<Multiselect :value="userGroups"
-				:options="availableGroups"
+		<div :class="{'icon-loading-small': loading.groups}" class="groups">
+			<Multiselect :close-on-select="false"
 				:disabled="loading.groups||loading.all"
-				tag-placeholder="create"
-				:placeholder="t('settings', 'Add user in group')"
-				label="name"
-				track-by="id"
-				class="multiselect-vue"
 				:limit="2"
 				:multiple="true"
-				:taggable="settings.isAdmin"
-				:close-on-select="false"
+				:options="availableGroups"
+				:placeholder="t('settings', 'Add user in group')"
 				:tag-width="60"
-				@tag="createGroup"
+				:taggable="settings.isAdmin"
+				:value="userGroups"
+				class="multiselect-vue"
+				label="name"
+				tag-placeholder="create"
+				track-by="id"
+				@remove="removeUserGroup"
 				@select="addUserGroup"
-				@remove="removeUserGroup">
-				<span slot="limit" v-tooltip.auto="formatGroupsTitle(userGroups)" class="multiselect__limit">+{{ userGroups.length-2 }}</span>
+				@tag="createGroup">
 				<span slot="noResult">{{ t('settings', 'No results') }}</span>
 			</Multiselect>
 		</div>
-		<div v-if="subAdminsGroups.length>0 && settings.isAdmin" class="subadmins" :class="{'icon-loading-small': loading.subadmins}">
-			<Multiselect :value="userSubAdminsGroups"
-				:options="subAdminsGroups"
+		<div v-if="subAdminsGroups.length>0 && settings.isAdmin"
+			:class="{'icon-loading-small': loading.subadmins}"
+			class="subadmins">
+			<Multiselect :close-on-select="false"
 				:disabled="loading.subadmins||loading.all"
-				:placeholder="t('settings', 'Set user as admin for')"
-				label="name"
-				track-by="id"
-				class="multiselect-vue"
 				:limit="2"
 				:multiple="true"
-				:close-on-select="false"
+				:options="subAdminsGroups"
+				:placeholder="t('settings', 'Set user as admin for')"
 				:tag-width="60"
-				@select="addUserSubAdmin"
-				@remove="removeUserSubAdmin">
-				<span slot="limit" v-tooltip.auto="formatGroupsTitle(userSubAdminsGroups)" class="multiselect__limit">+{{ userSubAdminsGroups.length-2 }}</span>
+				:value="userSubAdminsGroups"
+				class="multiselect-vue"
+				label="name"
+				track-by="id"
+				@remove="removeUserSubAdmin"
+				@select="addUserSubAdmin">
 				<span slot="noResult">{{ t('settings', 'No results') }}</span>
 			</Multiselect>
 		</div>
-		<div v-tooltip.auto="usedSpace" class="quota" :class="{'icon-loading-small': loading.quota}">
-			<Multiselect :value="userQuota"
-				:options="quotaOptions"
+		<div v-tooltip.auto="usedSpace"
+			:class="{'icon-loading-small': loading.quota}"
+			class="quota">
+			<Multiselect :allow-empty="false"
 				:disabled="loading.quota||loading.all"
-				tag-placeholder="create"
+				:options="quotaOptions"
 				:placeholder="t('settings', 'Select user quota')"
+				:taggable="true"
+				:value="userQuota"
+				class="multiselect-vue"
 				label="label"
+				tag-placeholder="create"
 				track-by="id"
-				class="multiselect-vue"
-				:allow-empty="false"
-				:taggable="true"
-				@tag="validateQuota"
-				@input="setUserQuota" />
-			<progress class="quota-user-progress"
-				:class="{'warn':usedQuota>80}"
-				:value="usedQuota"
-				max="100" />
+				@input="setUserQuota"
+				@tag="validateQuota" />
 		</div>
 		<div v-if="showConfig.showLanguages"
-			class="languages"
-			:class="{'icon-loading-small': loading.languages}">
-			<Multiselect :value="userLanguage"
-				:options="languages"
+			:class="{'icon-loading-small': loading.languages}"
+			class="languages">
+			<Multiselect :allow-empty="false"
 				:disabled="loading.languages||loading.all"
+				:options="languages"
 				:placeholder="t('settings', 'No language set')"
-				label="name"
-				track-by="code"
+				:value="userLanguage"
 				class="multiselect-vue"
-				:allow-empty="false"
-				group-values="languages"
 				group-label="label"
+				group-values="languages"
+				label="name"
+				track-by="code"
 				@input="setUserLanguage" />
 		</div>
-		<div v-if="showConfig.showStoragePath" class="storageLocation">
-			{{ user.storageLocation }}
-		</div>
-		<div v-if="showConfig.showUserBackend" class="userBackend">
-			{{ user.backend }}
-		</div>
-		<div v-if="showConfig.showLastLogin" v-tooltip.auto="user.lastLogin>0 ? OC.Util.formatDate(user.lastLogin) : ''" class="lastLogin">
-			{{ user.lastLogin>0 ? OC.Util.relativeModifiedDate(user.lastLogin) : t('settings','Never') }}
-		</div>
+
+		<!-- don't show this on edit mode -->
+		<div v-if="showConfig.showStoragePath || showConfig.showUserBackend"
+			class="storageLocation" />
+		<div v-if="showConfig.showLastLogin" />
+
 		<div class="userActions">
-			<div v-if="OC.currentUser !== user.id && user.id !== 'admin' && !loading.all" class="toggleUserActions">
-				<div v-click-outside="hideMenu" class="icon-more" @click="toggleMenu" />
-				<div class="popovermenu" :class="{ 'open': openedMenu }">
+			<div v-if="OC.currentUser !== user.id && user.id !== 'admin' && !loading.all"
+				class="toggleUserActions">
+				<Actions>
+					<ActionButton icon="icon-checkmark"
+						@click="editing = false">
+						{{ t('settings', 'Done') }}
+					</ActionButton>
+				</Actions>
+				<div v-click-outside="hideMenu"
+					class="icon-more"
+					@click="toggleMenu" />
+				<div :class="{ 'open': openedMenu }" class="popovermenu">
 					<PopoverMenu :menu="userActions" />
 				</div>
 			</div>
-			<div class="feedback" :style="{opacity: feedbackMessage !== '' ? 1 : 0}">
+			<div :style="{opacity: feedbackMessage !== '' ? 1 : 0}"
+				class="feedback">
 				<div class="icon-checkmark" />
 				{{ feedbackMessage }}
 			</div>
@@ -210,19 +237,30 @@
 import ClickOutside from 'vue-click-outside'
 import Vue from 'vue'
 import VTooltip from 'v-tooltip'
-import { PopoverMenu, Multiselect } from 'nextcloud-vue'
+import {
+	PopoverMenu,
+	Multiselect,
+	Actions,
+	ActionButton
+} from 'nextcloud-vue'
+import UserRowSimple from './UserRowSimple'
+import UserRowMixin from '../../mixins/UserRowMixin'
 
 Vue.use(VTooltip)
 
 export default {
 	name: 'UserRow',
 	components: {
+		UserRowSimple,
 		PopoverMenu,
+		Actions,
+		ActionButton,
 		Multiselect
 	},
 	directives: {
 		ClickOutside
 	},
+	mixins: [UserRowMixin],
 	props: {
 		user: {
 			type: Object,
@@ -262,6 +300,7 @@ export default {
 			rand: parseInt(Math.random() * 1000),
 			openedMenu: false,
 			feedbackMessage: '',
+			editing: false,
 			loading: {
 				all: false,
 				displayName: false,
@@ -305,92 +344,9 @@ export default {
 				})
 			}
 			return actions.concat(this.externalActions)
-		},
-
-		/* GROUPS MANAGEMENT */
-		userGroups() {
-			let userGroups = this.groups.filter(group => this.user.groups.includes(group.id))
-			return userGroups
-		},
-		userSubAdminsGroups() {
-			let userSubAdminsGroups = this.subAdminsGroups.filter(group => this.user.subadmin.includes(group.id))
-			return userSubAdminsGroups
-		},
-		availableGroups() {
-			return this.groups.map((group) => {
-				// clone object because we don't want
-				// to edit the original groups
-				let groupClone = Object.assign({}, group)
-
-				// two settings here:
-				// 1. user NOT in group but no permission to add
-				// 2. user is in group but no permission to remove
-				groupClone.$isDisabled
-					= (group.canAdd === false
-						&& !this.user.groups.includes(group.id))
-					|| (group.canRemove === false
-						&& this.user.groups.includes(group.id))
-				return groupClone
-			})
-		},
-
-		/* QUOTA MANAGEMENT */
-		usedSpace() {
-			if (this.user.quota.used) {
-				return t('settings', '{size} used', { size: OC.Util.humanFileSize(this.user.quota.used) })
-			}
-			return t('settings', '{size} used', { size: OC.Util.humanFileSize(0) })
-		},
-		usedQuota() {
-			let quota = this.user.quota.quota
-			if (quota > 0) {
-				quota = Math.min(100, Math.round(this.user.quota.used / quota * 100))
-			} else {
-				var usedInGB = this.user.quota.used / (10 * Math.pow(2, 30))
-				// asymptotic curve approaching 50% at 10GB to visualize used stace with infinite quota
-				quota = 95 * (1 - (1 / (usedInGB + 1)))
-			}
-			return isNaN(quota) ? 0 : quota
-		},
-		// Mapping saved values to objects
-		userQuota() {
-			if (this.user.quota.quota >= 0) {
-				// if value is valid, let's map the quotaOptions or return custom quota
-				let humanQuota = OC.Util.humanFileSize(this.user.quota.quota)
-				let userQuota = this.quotaOptions.find(quota => quota.id === humanQuota)
-				return userQuota || { id: humanQuota, label: humanQuota }
-			} else if (this.user.quota.quota === 'default') {
-				// default quota is replaced by the proper value on load
-				return this.quotaOptions[0]
-			}
-			return this.quotaOptions[1] // unlimited
-		},
-
-		/* PASSWORD POLICY? */
-		minPasswordLength() {
-			return this.$store.getters.getPasswordPolicyMinLength
-		},
-
-		/* LANGUAGE */
-		userLanguage() {
-			let availableLanguages = this.languages[0].languages.concat(this.languages[1].languages)
-			let userLang = availableLanguages.find(lang => lang.code === this.user.language)
-			if (typeof userLang !== 'object' && this.user.language !== '') {
-				return {
-					code: this.user.language,
-					name: this.user.language
-				}
-			} else if (this.user.language === '') {
-				return false
-			}
-			return userLang
 		}
 	},
-	mounted() {
-		// required if popup needs to stay opened after menu click
-		// since we only have disable/delete actions, let's close it directly
-		// this.popupItem = this.$el;
-	},
+
 	methods: {
 		/* MENU HANDLING */
 		toggleMenu() {
@@ -400,35 +356,6 @@ export default {
 			this.openedMenu = false
 		},
 
-		/**
-		 * Generate avatar url
-		 *
-		 * @param {string} user The user name
-		 * @param {int} size Size integer, default 32
-		 * @returns {string}
-		 */
-		generateAvatar(user, size = 32) {
-			return OC.generateUrl(
-				'/avatar/{user}/{size}?v={version}',
-				{
-					user: user,
-					size: size,
-					version: oc_userconfig.avatar.version
-				}
-			)
-		},
-
-		/**
-		 * Format array of groups objects to a string for the popup
-		 *
-		 * @param {array} groups The groups
-		 * @returns {string}
-		 */
-		formatGroupsTitle(groups) {
-			let names = groups.map(group => group.name)
-			return names.slice(2).join(', ')
-		},
-
 		wipeUserDevices() {
 			let userid = this.user.id
 			OC.dialogs.confirmDestructive(
@@ -486,7 +413,10 @@ export default {
 			this.loading.all = true
 			let userid = this.user.id
 			let enabled = !this.user.enabled
-			return this.$store.dispatch('enableDisableUser', { userid, enabled })
+			return this.$store.dispatch('enableDisableUser', {
+				userid,
+				enabled
+			})
 				.then(() => {
 					this.loading.delete = false
 					this.loading.all = false
@@ -494,10 +424,10 @@ export default {
 		},
 
 		/**
-		 * Set user displayName
-		 *
-		 * @param {string} displayName The display name
-		 */
+			 * Set user displayName
+			 *
+			 * @param {string} displayName The display name
+			 */
 		updateDisplayName() {
 			let displayName = this.$refs.displayName.value
 			this.loading.displayName = true
@@ -512,10 +442,10 @@ export default {
 		},
 
 		/**
-		 * Set user password
-		 *
-		 * @param {string} password The email adress
-		 */
+			 * Set user password
+			 *
+			 * @param {string} password The email adress
+			 */
 		updatePassword() {
 			let password = this.$refs.password.value
 			this.loading.password = true
@@ -530,10 +460,10 @@ export default {
 		},
 
 		/**
-		 * Set user mailAddress
-		 *
-		 * @param {string} mailAddress The email adress
-		 */
+			 * Set user mailAddress
+			 *
+			 * @param {string} mailAddress The email adress
+			 */
 		updateEmail() {
 			let mailAddress = this.$refs.mailAddress.value
 			this.loading.mailAddress = true
@@ -548,10 +478,10 @@ export default {
 		},
 
 		/**
-		 * Create a new group and add user to it
-		 *
-		 * @param {string} gid Group id
-		 */
+			 * Create a new group and add user to it
+			 *
+			 * @param {string} gid Group id
+			 */
 		async createGroup(gid) {
 			this.loading = { groups: true, subadmins: true }
 			try {
@@ -567,10 +497,10 @@ export default {
 		},
 
 		/**
-		 * Add user to group
-		 *
-		 * @param {object} group Group object
-		 */
+			 * Add user to group
+			 *
+			 * @param {object} group Group object
+			 */
 		async addUserGroup(group) {
 			if (group.canAdd === false) {
 				return false
@@ -588,10 +518,10 @@ export default {
 		},
 
 		/**
-		 * Remove user from group
-		 *
-		 * @param {object} group Group object
-		 */
+			 * Remove user from group
+			 *
+			 * @param {object} group Group object
+			 */
 		async removeUserGroup(group) {
 			if (group.canRemove === false) {
 				return false
@@ -602,7 +532,10 @@ export default {
 			let gid = group.id
 
 			try {
-				await this.$store.dispatch('removeUserGroup', { userid, gid })
+				await this.$store.dispatch('removeUserGroup', {
+					userid,
+					gid
+				})
 				this.loading.groups = false
 				// remove user from current list if current list is the removed group
 				if (this.$route.params.selectedGroup === gid) {
@@ -614,17 +547,20 @@ export default {
 		},
 
 		/**
-		 * Add user to group
-		 *
-		 * @param {object} group Group object
-		 */
+			 * Add user to group
+			 *
+			 * @param {object} group Group object
+			 */
 		async addUserSubAdmin(group) {
 			this.loading.subadmins = true
 			let userid = this.user.id
 			let gid = group.id
 
 			try {
-				await this.$store.dispatch('addUserSubAdmin', { userid, gid })
+				await this.$store.dispatch('addUserSubAdmin', {
+					userid,
+					gid
+				})
 				this.loading.subadmins = false
 			} catch (error) {
 				console.error(error)
@@ -632,17 +568,20 @@ export default {
 		},
 
 		/**
-		 * Remove user from group
-		 *
-		 * @param {object} group Group object
-		 */
+			 * Remove user from group
+			 *
+			 * @param {object} group Group object
+			 */
 		async removeUserSubAdmin(group) {
 			this.loading.subadmins = true
 			let userid = this.user.id
 			let gid = group.id
 
 			try {
-				await this.$store.dispatch('removeUserSubAdmin', { userid, gid })
+				await this.$store.dispatch('removeUserSubAdmin', {
+					userid,
+					gid
+				})
 			} catch (error) {
 				console.error(error)
 			} finally {
@@ -651,11 +590,11 @@ export default {
 		},
 
 		/**
-		 * Dispatch quota set request
-		 *
-		 * @param {string|Object} quota Quota in readable format '5 GB' or Object {id: '5 GB', label: '5GB'}
-		 * @returns {string}
-		 */
+			 * Dispatch quota set request
+			 *
+			 * @param {string|Object} quota Quota in readable format '5 GB' or Object {id: '5 GB', label: '5GB'}
+			 * @returns {string}
+			 */
 		async setUserQuota(quota = 'none') {
 			this.loading.quota = true
 			// ensure we only send the preset id
@@ -676,11 +615,11 @@ export default {
 		},
 
 		/**
-		 * Validate quota string to make sure it's a valid human file size
-		 *
-		 * @param {string} quota Quota in readable format '5 GB'
-		 * @returns {Promise|boolean}
-		 */
+			 * Validate quota string to make sure it's a valid human file size
+			 *
+			 * @param {string} quota Quota in readable format '5 GB'
+			 * @returns {Promise|boolean}
+			 */
 		validateQuota(quota) {
 			// only used for new presets sent through @Tag
 			let validQuota = OC.Util.computerFileSize(quota)
@@ -693,11 +632,11 @@ export default {
 		},
 
 		/**
-		 * Dispatch language set request
-		 *
-		 * @param {Object} lang language object {code:'en', name:'English'}
-		 * @returns {Object}
-		 */
+			 * Dispatch language set request
+			 *
+			 * @param {Object} lang language object {code:'en', name:'English'}
+			 * @returns {Object}
+			 */
 		async setUserLanguage(lang) {
 			this.loading.languages = true
 			// ensure we only send the preset id
@@ -716,8 +655,8 @@ export default {
 		},
 
 		/**
-		 * Dispatch new welcome mail request
-		 */
+			 * Dispatch new welcome mail request
+			 */
 		sendWelcomeMail() {
 			this.loading.all = true
 			this.$store.dispatch('sendWelcomeMail', this.user.id)
diff --git a/apps/settings/src/components/UserList/UserRowSimple.vue b/apps/settings/src/components/UserList/UserRowSimple.vue
new file mode 100644
index 0000000000000000000000000000000000000000..247bfb063cebdf0188a063bb13f8570491a9f348
--- /dev/null
+++ b/apps/settings/src/components/UserList/UserRowSimple.vue
@@ -0,0 +1,159 @@
+<template>
+	<div
+		class="row"
+		:class="{'disabled': loading.delete || loading.disable}"
+		:data-id="user.id">
+		<div class="avatar" :class="{'icon-loading-small': loading.delete || loading.disable || loading.wipe}">
+			<img v-if="!loading.delete && !loading.disable && !loading.wipe"
+				alt=""
+				width="32"
+				height="32"
+				:src="generateAvatar(user.id, 32)"
+				:srcset="generateAvatar(user.id, 64)+' 2x, '+generateAvatar(user.id, 128)+' 4x'">
+		</div>
+		<!-- dirty hack to ellipsis on two lines -->
+		<div class="name">
+			{{ user.id }}
+			<div class="displayName subtitle">
+				{{ user.displayname }}
+			</div>
+		</div>
+		<div />
+		<div class="mailAddress">
+			{{ user.email }}
+		</div>
+		<div class="groups">
+			{{ userGroupsLabels }}
+		</div>
+		<div v-if="subAdminsGroups.length > 0 && settings.isAdmin" class="subAdminsGroups">
+			{{ userSubAdminsGroupsLabels }}
+		</div>
+		<div v-tooltip.auto="usedSpace" class="quota">
+			<progress
+				class="quota-user-progress"
+				:class="{'warn': usedQuota > 80}"
+				:value="usedQuota"
+				max="100" />
+		</div>
+		<div v-if="showConfig.showLanguages" class="languages">
+			{{ userLanguage.name }}
+		</div>
+		<div v-if="showConfig.showUserBackend || showConfig.showStoragePath" class="userBackend">
+			<div v-if="showConfig.showUserBackend" class="userBackend">
+				{{ user.backend }}
+			</div>
+			<div v-if="showConfig.showStoragePath" class="storageLocation subtitle">
+				{{ user.storageLocation }}
+			</div>
+		</div>
+		<div v-if="showConfig.showLastLogin" v-tooltip.auto="userLastLoginTooltip" class="lastLogin">
+			{{ userLastLogin }}
+		</div>
+
+		<div class="userActions">
+			<div v-if="canEdit && !loading.all" class="toggleUserActions">
+				<Actions>
+					<ActionButton icon="icon-rename" @click="toggleEdit">
+						{{ t('settings', 'Edit User') }}
+					</ActionButton>
+				</Actions>
+				<div v-click-outside="hideMenu" class="icon-more" @click="$emit('toggleMenu')" />
+				<div class="popovermenu" :class="{ 'open': openedMenu }">
+					<PopoverMenu :menu="userActions" />
+				</div>
+			</div>
+			<div class="feedback" :style="{opacity: feedbackMessage !== '' ? 1 : 0}">
+				<div class="icon-checkmark" />
+				{{ feedbackMessage }}
+			</div>
+		</div>
+	</div>
+</template>
+
+<script>
+import { PopoverMenu, Actions, ActionButton } from 'nextcloud-vue'
+import ClickOutside from 'vue-click-outside'
+import { getCurrentUser } from '@nextcloud/auth'
+
+import UserRowMixin from '../../mixins/UserRowMixin'
+export default {
+	name: 'UserRowSimple',
+	components: {
+		PopoverMenu,
+		ActionButton,
+		Actions
+	},
+	directives: {
+		ClickOutside
+	},
+	mixins: [UserRowMixin],
+	props: {
+		user: {
+			type: Object,
+			required: true
+		},
+		loading: {
+			type: Object,
+			required: true
+		},
+		showConfig: {
+			type: Object,
+			required: true
+		},
+		userActions: {
+			type: Array,
+			required: true
+		},
+		openedMenu: {
+			type: Boolean,
+			required: true
+		},
+		feedbackMessage: {
+			type: String,
+			required: true
+		},
+		subAdminsGroups: {
+			type: Array,
+			required: true
+		},
+		settings: {
+			type: Object,
+			required: true
+		}
+	},
+	computed: {
+		userGroupsLabels() {
+			return this.userGroups
+				.map(group => group.name)
+				.join(', ')
+		},
+		userSubAdminsGroupsLabels() {
+			return this.userSubAdminsGroups
+				.map(group => group.name)
+				.join(', ')
+		},
+		usedSpace() {
+			if (this.user.quota.used) {
+				return t('settings', '{size} used', { size: OC.Util.humanFileSize(this.user.quota.used) })
+			}
+			return t('settings', '{size} used', { size: OC.Util.humanFileSize(0) })
+		},
+		canEdit() {
+			return getCurrentUser().uid !== this.user.id && this.user.id !== 'admin'
+		}
+
+	},
+	methods: {
+		hideMenu() {
+			this.$emit('hideMenu')
+		},
+		toggleEdit() {
+			this.$emit('update:editing', true)
+		}
+	}
+}
+</script>
+
+<style scoped>
+
+</style>
diff --git a/apps/settings/src/mixins/UserRowMixin.js b/apps/settings/src/mixins/UserRowMixin.js
new file mode 100644
index 0000000000000000000000000000000000000000..ff1276fdd1599adc4ae53def8c38a284b0b82585
--- /dev/null
+++ b/apps/settings/src/mixins/UserRowMixin.js
@@ -0,0 +1,171 @@
+/**
+ * @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+export default {
+	props: {
+		user: {
+			type: Object,
+			required: true
+		},
+		settings: {
+			type: Object,
+			default: () => ({})
+		},
+		groups: {
+			type: Array,
+			default: () => []
+		},
+		subAdminsGroups: {
+			type: Array,
+			default: () => []
+		},
+		quotaOptions: {
+			type: Array,
+			default: () => []
+		},
+		showConfig: {
+			type: Object,
+			default: () => ({})
+		},
+		languages: {
+			type: Array,
+			required: true
+		},
+		externalActions: {
+			type: Array,
+			default: () => []
+		}
+	},
+	computed: {
+		/* GROUPS MANAGEMENT */
+		userGroups() {
+			const userGroups = this.groups.filter(group => this.user.groups.includes(group.id))
+			return userGroups
+		},
+		userSubAdminsGroups() {
+			const userSubAdminsGroups = this.subAdminsGroups.filter(group => this.user.subadmin.includes(group.id))
+			return userSubAdminsGroups
+		},
+		availableGroups() {
+			return this.groups.map((group) => {
+				// clone object because we don't want
+				// to edit the original groups
+				let groupClone = Object.assign({}, group)
+
+				// two settings here:
+				// 1. user NOT in group but no permission to add
+				// 2. user is in group but no permission to remove
+				groupClone.$isDisabled
+					= (group.canAdd === false
+						&& !this.user.groups.includes(group.id))
+					|| (group.canRemove === false
+						&& this.user.groups.includes(group.id))
+				return groupClone
+			})
+		},
+
+		/* QUOTA MANAGEMENT */
+		usedSpace() {
+			if (this.user.quota.used) {
+				return t('settings', '{size} used', { size: OC.Util.humanFileSize(this.user.quota.used) })
+			}
+			return t('settings', '{size} used', { size: OC.Util.humanFileSize(0) })
+		},
+		usedQuota() {
+			let quota = this.user.quota.quota
+			if (quota > 0) {
+				quota = Math.min(100, Math.round(this.user.quota.used / quota * 100))
+			} else {
+				var usedInGB = this.user.quota.used / (10 * Math.pow(2, 30))
+				// asymptotic curve approaching 50% at 10GB to visualize used stace with infinite quota
+				quota = 95 * (1 - (1 / (usedInGB + 1)))
+			}
+			return isNaN(quota) ? 0 : quota
+		},
+		// Mapping saved values to objects
+		userQuota() {
+			if (this.user.quota.quota >= 0) {
+				// if value is valid, let's map the quotaOptions or return custom quota
+				let humanQuota = OC.Util.humanFileSize(this.user.quota.quota)
+				let userQuota = this.quotaOptions.find(quota => quota.id === humanQuota)
+				return userQuota || { id: humanQuota, label: humanQuota }
+			} else if (this.user.quota.quota === 'default') {
+				// default quota is replaced by the proper value on load
+				return this.quotaOptions[0]
+			}
+			return this.quotaOptions[1] // unlimited
+		},
+
+		/* PASSWORD POLICY? */
+		minPasswordLength() {
+			return this.$store.getters.getPasswordPolicyMinLength
+		},
+
+		/* LANGUAGE */
+		userLanguage() {
+			let availableLanguages = this.languages[0].languages.concat(this.languages[1].languages)
+			let userLang = availableLanguages.find(lang => lang.code === this.user.language)
+			if (typeof userLang !== 'object' && this.user.language !== '') {
+				return {
+					code: this.user.language,
+					name: this.user.language
+				}
+			} else if (this.user.language === '') {
+				return false
+			}
+			return userLang
+		},
+
+		/* LAST LOGIN */
+		userLastLoginTooltip() {
+			if (this.user.lastLogin > 0) {
+				return OC.Util.formatDate(this.user.lastLogin)
+			}
+			return ''
+		},
+		userLastLogin() {
+			if (this.user.lastLogin > 0) {
+				return OC.Util.relativeModifiedDate(this.user.lastLogin)
+			}
+			return t('settings', 'Never')
+		}
+	},
+	methods: {
+		/**
+		 * Generate avatar url
+		 *
+		 * @param {string} user The user name
+		 * @param {int} size Size integer, default 32
+		 * @returns {string}
+		 */
+		generateAvatar(user, size = 32) {
+			return OC.generateUrl(
+				'/avatar/{user}/{size}?v={version}',
+				{
+					user: user,
+					size: size,
+					version: oc_userconfig.avatar.version
+				}
+			)
+		}
+	}
+}
diff --git a/tests/acceptance/features/bootstrap/UsersSettingsContext.php b/tests/acceptance/features/bootstrap/UsersSettingsContext.php
index 56dce82235986fa74bb21f410d819c48bb16ee72..d42b49cbf2dde425ace779ac12a47aaa131c0f72 100644
--- a/tests/acceptance/features/bootstrap/UsersSettingsContext.php
+++ b/tests/acceptance/features/bootstrap/UsersSettingsContext.php
@@ -1,9 +1,10 @@
 <?php
 
 /**
- * 
+ *
  * @copyright Copyright (c) 2017, Daniel Calviño Sánchez (danxuliu@gmail.com)
  * @copyright Copyright (c) 2018, John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
+ * @copyright Copyright (c) 2019, Greta Doci <gretadoci@gmail.com>
  *
  * @license GNU AGPL version 3 or any later version
  *
@@ -33,7 +34,7 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
 	 */
 	public static function newUserForm() {
 		return Locator::forThe()->id("new-user")->
-				describedAs("New user form in Users Settings");
+			describedAs("New user form in Users Settings");
 	}
 
 	/**
@@ -41,7 +42,7 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
 	 */
 	public static function userNameFieldForNewUser() {
 		return Locator::forThe()->field("newusername")->
-				describedAs("User name field for new user in Users Settings");
+			describedAs("User name field for new user in Users Settings");
 	}
 
 	/**
@@ -49,7 +50,7 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
 	 */
 	public static function displayNameFieldForNewUser() {
 		return Locator::forThe()->field("newdisplayname")->
-				describedAs("Display name field for new user in Users Settings");
+			describedAs("Display name field for new user in Users Settings");
 	}
 
 	/**
@@ -57,7 +58,7 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
 	 */
 	public static function passwordFieldForNewUser() {
 		return Locator::forThe()->field("newuserpassword")->
-				describedAs("Password field for new user in Users Settings");
+			describedAs("Password field for new user in Users Settings");
 	}
 
 	/**
@@ -65,7 +66,7 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
 	 */
 	public static function newUserButton() {
 		return Locator::forThe()->id("new-user-button")->
-				describedAs("New user button in Users Settings");
+			describedAs("New user button in Users Settings");
 	}
 
 	/**
@@ -73,26 +74,26 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
 	 */
 	public static function createNewUserButton() {
 		return Locator::forThe()->xpath("//form[@id = 'new-user']//input[@type = 'submit']")->
-				describedAs("Create user button in Users Settings");
+			describedAs("Create user button in Users Settings");
 	}
 
 	/**
 	 * @return Locator
 	 */
 	public static function rowForUser($user) {
-		return Locator::forThe()->xpath("//div[@id='app-content']/div/div[normalize-space() = '$user']/..")->
-				describedAs("Row for user $user in Users Settings");
+		return Locator::forThe()->css("div.user-list-grid div.row[data-id=$user]")->
+			describedAs("Row for user $user in Users Settings");
 	}
 
 	/**
 	 * Warning: you need to watch out for the proper classes order
-	 * 
+	 *
 	 * @return Locator
 	 */
 	public static function classCellForUser($class, $user) {
 		return Locator::forThe()->xpath("//*[contains(concat(' ', normalize-space(@class), ' '), ' $class ')]")->
-				descendantOf(self::rowForUser($user))->
-				describedAs("$class cell for user $user in Users Settings");
+			descendantOf(self::rowForUser($user))->
+			describedAs("$class cell for user $user in Users Settings");
 	}
 
 	/**
@@ -100,8 +101,8 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
 	 */
 	public static function inputForUserInCell($cell, $user) {
 		return Locator::forThe()->css("input")->
-				descendantOf(self::classCellForUser($cell, $user))->
-				describedAs("$cell input for user $user in Users Settings");
+			descendantOf(self::classCellForUser($cell, $user))->
+			describedAs("$cell input for user $user in Users Settings");
 	}
 
 	/**
@@ -116,8 +117,8 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
 	 */
 	public static function optionInInputForUser($cell, $user) {
 		return Locator::forThe()->css(".multiselect__option--highlight")->
-				descendantOf(self::classCellForUser($cell, $user))->
-				describedAs("Selected $cell option in $cell input for user $user in Users Settings");
+			descendantOf(self::classCellForUser($cell, $user))->
+			describedAs("Selected $cell option in $cell input for user $user in Users Settings");
 	}
 
 	/**
@@ -125,8 +126,8 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
 	 */
 	public static function actionsMenuOf($user) {
 		return Locator::forThe()->css(".icon-more")->
-				descendantOf(self::rowForUser($user))->
-				describedAs("Actions menu for user $user in Users Settings");
+			descendantOf(self::rowForUser($user))->
+			describedAs("Actions menu for user $user in Users Settings");
 	}
 
 	/**
@@ -134,8 +135,8 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
 	 */
 	public static function theAction($action, $user) {
 		return Locator::forThe()->xpath("//button[normalize-space() = '$action']")->
-				descendantOf(self::rowForUser($user))->
-				describedAs("$action action for the user $user row in Users Settings");
+			descendantOf(self::rowForUser($user))->
+			describedAs("$action action for the user $user row in Users Settings");
 	}
 
 	/**
@@ -143,7 +144,7 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
 	 */
 	public static function theColumn($column) {
 		return Locator::forThe()->xpath("//div[@class='user-list-grid']//div[normalize-space() = '$column']")->
-				describedAs("The $column column in Users Settings");
+			describedAs("The $column column in Users Settings");
 	}
 
 	/**
@@ -151,8 +152,25 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
 	 */
 	public static function selectedSelectOption($cell, $user) {
 		return Locator::forThe()->css(".multiselect__single")->
-				descendantOf(self::classCellForUser($cell, $user))->
-				describedAs("The selected option of the $cell select for the user $user in Users Settings");
+			descendantOf(self::classCellForUser($cell, $user))->
+			describedAs("The selected option of the $cell select for the user $user in Users Settings");
+	}
+
+	/**
+	 * @return Locator
+	 */
+	public static function editModeToggle($user) {
+		return Locator::forThe()->css(".toggleUserActions button.icon-rename")->
+			descendantOf(self::rowForUser($user))->
+			describedAs("The edit toggle button for the user $user in Users Settings");
+	}
+
+	/**
+	 * @return Locator
+	 */
+	public static function editModeOn($user) {
+		return Locator::forThe()->css("div.user-list-grid div.row.row--editable[data-id=$user]")->
+			describedAs("I see the edit mode is on for the user $user in Users Settings");
 	}
 
 	/**
@@ -204,6 +222,13 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
 		$this->actor->find(self::createNewUserButton(), 10)->click();
 	}
 
+	/**
+	 * @When I toggle the edit mode for the user :user
+	 */
+	public function iToggleTheEditModeForUser($user) {
+		$this->actor->find(self::editModeToggle($user), 10)->click();
+	}
+
 	/**
 	 * @When I create user :user with password :password
 	 */
@@ -258,7 +283,7 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
 	 */
 	public function iSeeThatTheNewUserFormIsShown() {
 		PHPUnit_Framework_Assert::assertTrue(
-				$this->actor->find(self::newUserForm(), 10)->isVisible());
+			$this->actor->find(self::newUserForm(), 10)->isVisible());
 	}
 
 	/**
@@ -266,7 +291,7 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
 	 */
 	public function iSeeTheAction($action, $user) {
 		PHPUnit_Framework_Assert::assertTrue(
-				$this->actor->find(self::theAction($action, $user), 10)->isVisible());
+			$this->actor->find(self::theAction($action, $user), 10)->isVisible());
 	}
 
 	/**
@@ -274,7 +299,7 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
 	 */
 	public function iSeeThatTheColumnIsShown($column) {
 		PHPUnit_Framework_Assert::assertTrue(
-				$this->actor->find(self::theColumn($column), 10)->isVisible());
+			$this->actor->find(self::theColumn($column), 10)->isVisible());
 	}
 
 	/**
@@ -289,15 +314,16 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
 	 * @Then I see that the display name for the user :user is :displayName
 	 */
 	public function iSeeThatTheDisplayNameForTheUserIs($user, $displayName) {
-		PHPUnit_Framework_Assert::assertEquals($displayName, $this->actor->find(self::displayNameCellForUser($user), 10)->getValue());
+		PHPUnit_Framework_Assert::assertEquals(
+			$displayName, $this->actor->find(self::displayNameCellForUser($user), 10)->getValue());
 	}
 
 	/**
 	 * @Then I see that the :cell cell for user :user is done loading
 	 */
 	public function iSeeThatTheCellForUserIsDoneLoading($cell, $user) {
-		WaitFor::elementToBeEventuallyShown($this->actor, self::classCellForUser($cell.' icon-loading-small', $user));
-		WaitFor::elementToBeEventuallyNotShown($this->actor, self::classCellForUser($cell.' icon-loading-small', $user));
+		WaitFor::elementToBeEventuallyShown($this->actor, self::classCellForUser($cell . ' icon-loading-small', $user));
+		WaitFor::elementToBeEventuallyNotShown($this->actor, self::classCellForUser($cell . ' icon-loading-small', $user));
 	}
 
 	/**
@@ -307,6 +333,11 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
 		PHPUnit_Framework_Assert::assertEquals(
 			$this->actor->find(self::selectedSelectOption('quota', $user), 2)->getText(), $quota);
 	}
-	
 
+	/**
+	 * @Then I see that the edit mode is on for user :user
+	 */
+	public function iSeeThatTheEditModeIsOn($user) {
+		WaitFor::elementToBeEventuallyShown($this->actor, self::editModeOn($user));
+	}
 }
diff --git a/tests/acceptance/features/users.feature b/tests/acceptance/features/users.feature
index 263e9fddfc04ccf839407f176d7795b9645a6553..c4cfa3b69bf851666e5234d0083ef02b257c2b5e 100644
--- a/tests/acceptance/features/users.feature
+++ b/tests/acceptance/features/users.feature
@@ -63,18 +63,20 @@ Feature: users
     And I am logged in as the admin
     And I open the User settings
     And I see that the list of users contains the user user0
-    # disabled because we need the TAB patch: 
+    When I toggle the edit mode for the user user0
+    Then I see that the edit mode is on for user user0
+    # disabled because we need the TAB patch:
     # https://github.com/minkphp/MinkSelenium2Driver/pull/244
     # When I assign the user user0 to the group admin
     # Then I see that the section Admins is shown
     # And I see that the section Admins has a count of 2
-  
+
   Scenario: create and delete a group
     Given I act as Jane
     And I am logged in as the admin
     And I open the User settings
     And I see that the list of users contains the user user0
-    # disabled because we need the TAB patch: 
+    # disabled because we need the TAB patch:
     # https://github.com/minkphp/MinkSelenium2Driver/pull/244
     # And I assign the user user0 to the group Group1
     # And I see that the section Group1 is shown
@@ -112,7 +114,7 @@ Feature: users
     Then I see that the "Storage location" column is shown
     When I toggle the showUserBackend checkbox in the settings
     Then I see that the "User backend" column is shown
-    
+
 #  Scenario: change display name
 #    Given I act as Jane
 #    And I am logged in as the admin
@@ -128,6 +130,8 @@ Feature: users
     And I am logged in as the admin
     And I open the User settings
     And I see that the list of users contains the user user0
+    When I toggle the edit mode for the user user0
+    Then I see that the edit mode is on for user user0
     And I see that the password of user0 is ""
     When I set the password for user0 to 123456
     And I see that the password cell for user user0 is done loading
@@ -149,8 +153,10 @@ Feature: users
     And I am logged in as the admin
     And I open the User settings
     And I see that the list of users contains the user user0
+    When I toggle the edit mode for the user user0
+    Then I see that the edit mode is on for user user0
     And I see that the user quota of user0 is Unlimited
-    # disabled because we need the TAB patch: 
+    # disabled because we need the TAB patch:
     # https://github.com/minkphp/MinkSelenium2Driver/pull/244
     # When I set the user user0 quota to 1GB
     # And I see that the quota cell for user user0 is done loading
@@ -163,4 +169,4 @@ Feature: users
     # Then I see that the user quota of user0 is "0 B"
     # When I set the user user0 quota to Default
     # And I see that the quota cell for user user0 is done loading
-    # Then I see that the user quota of user0 is "Default quota"
\ No newline at end of file
+    # Then I see that the user quota of user0 is "Default quota"