diff --git a/apps/settings/css/settings.scss b/apps/settings/css/settings.scss
index 7b90a261c9077adedbf6a2d6d86f8081aea5da73..dc9f7b5741604463383e3c4fac843af1a36944ae 100644
--- a/apps/settings/css/settings.scss
+++ b/apps/settings/css/settings.scss
@@ -663,8 +663,6 @@ span.version {
 }
 
 .app-level {
-	margin-top: 8px;
-
 	span {
 		color: var(--color-text-maxcontrast);
 		background-color: transparent;
diff --git a/apps/settings/js/vue-0.js b/apps/settings/js/vue-0.js
index 4e5d6cda8e46c2e39e2de6d8925924b77583fae6..499281f8b3bef0b45d50fc81426030bfa5b257ce 100644
Binary files a/apps/settings/js/vue-0.js and b/apps/settings/js/vue-0.js differ
diff --git a/apps/settings/js/vue-0.js.map b/apps/settings/js/vue-0.js.map
index 642324ff6af4f315310907013b67a4094eb54aca..887fcc153d97c16616d15bee8b2503627c3604d3 100644
Binary files a/apps/settings/js/vue-0.js.map and b/apps/settings/js/vue-0.js.map differ
diff --git a/apps/settings/js/vue-5.js b/apps/settings/js/vue-5.js
index bd529a1dab503b1548cbf868fc8e5ecd1493bf26..d575663433859cc60fa6643be0f3c20edad18df0 100644
Binary files a/apps/settings/js/vue-5.js and b/apps/settings/js/vue-5.js differ
diff --git a/apps/settings/js/vue-5.js.map b/apps/settings/js/vue-5.js.map
index 7887add594ffa94c88b93785093f7471bd3f905f..5a0c11b7c700ecd3e082a92956e9a229f59962bf 100644
Binary files a/apps/settings/js/vue-5.js.map and b/apps/settings/js/vue-5.js.map differ
diff --git a/apps/settings/js/vue-6.js b/apps/settings/js/vue-6.js
index caffe98d91f79400974a19148ebb7f4c85b096d4..6fe5a2dae81ce297bc7c16cb7d52fb5b8e979089 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 a23ad4f22780ffbae6a786ec4d8efb4b52b20732..5fc6c45729727473233a7357a49cd4ba39d5585a 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-7.js b/apps/settings/js/vue-7.js
index c918e823b17f73abeda7611c7667333aed273cba..2bb630b6652aa5406d73836065f38db9f18d1575 100644
Binary files a/apps/settings/js/vue-7.js and b/apps/settings/js/vue-7.js differ
diff --git a/apps/settings/js/vue-7.js.map b/apps/settings/js/vue-7.js.map
index 4be042dc8ef26c6f58aa3f2c88360c4b6eb62f27..21ce72178afacefe23507df619542cd2330ed921 100644
Binary files a/apps/settings/js/vue-7.js.map and b/apps/settings/js/vue-7.js.map differ
diff --git a/apps/settings/js/vue-8.js b/apps/settings/js/vue-8.js
index 7c2e4c9b1e84922a268953a75f8ac22aad9772fd..22bf3803fcf9e3522576266f41200ead96ef9b66 100644
Binary files a/apps/settings/js/vue-8.js and b/apps/settings/js/vue-8.js differ
diff --git a/apps/settings/js/vue-8.js.map b/apps/settings/js/vue-8.js.map
index 8ab4dcc14c7ebfc9e40c4585844c583afd8b2cd1..08f21d56c180537b5b19cdec687a317a13df9179 100644
Binary files a/apps/settings/js/vue-8.js.map and b/apps/settings/js/vue-8.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 54f76e26a40ec8638ed5a5167dec07a75d57e11f..56ac2fe80d3bb3975174f43e28f6b388f188ca6d 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 ebed48babe12c0b762d59cc37233a8d998f806fa..fe0b4cddda07dc99ee148ef72f97cacc8e704c07 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/src/components/AppDetails.vue b/apps/settings/src/components/AppDetails.vue
index fa586b3d0a12cbe9903371447ad183e9d7e6fdc9..55519bf9f8054df9fced182b1b632a2130567b6e 100644
--- a/apps/settings/src/components/AppDetails.vue
+++ b/apps/settings/src/components/AppDetails.vue
@@ -21,76 +21,74 @@
   -->
 
 <template>
-	<div id="app-details-view" style="padding: 20px;">
-		<div class="actions">
-			<div class="actions-buttons">
+	<div class="app-details">
+		<div class="app-details__actions">
+			<div v-if="app.active && canLimitToGroups(app)" class="app-details__actions-groups">
+				<input :id="prefix('groups_enable', app.id)"
+					v-model="groupCheckedAppsData"
+					type="checkbox"
+					:value="app.id"
+					class="groups-enable__checkbox checkbox"
+					@change="setGroupLimit">
+				<label :for="prefix('groups_enable', app.id)">{{ t('settings', 'Limit to groups') }}</label>
+				<input type="hidden"
+					class="group_select"
+					:title="t('settings', 'All')"
+					value="">
+				<Multiselect v-if="isLimitedToGroups(app)"
+					:options="groups"
+					:value="appGroups"
+					:options-limit="5"
+					:placeholder="t('settings', 'Limit app usage to groups')"
+					label="name"
+					track-by="id"
+					class="multiselect-vue"
+					:multiple="true"
+					:close-on-select="false"
+					:tag-width="60"
+					@select="addGroupLimitation"
+					@remove="removeGroupLimitation"
+					@search-change="asyncFindGroup">
+					<span slot="noResult">{{ t('settings', 'No results') }}</span>
+				</Multiselect>
+			</div>
+			<div class="app-details__actions-manage">
 				<input v-if="app.update"
 					class="update primary"
 					type="button"
-					:value="t('settings', 'Update to {version}', {version: app.update})"
-					:disabled="installing || loading(app.id)"
+					:value="t('settings', 'Update to {version}', { version: app.update })"
+					:disabled="installing || isLoading"
 					@click="update(app.id)">
 				<input v-if="app.canUnInstall"
 					class="uninstall"
 					type="button"
 					:value="t('settings', 'Remove')"
-					:disabled="installing || loading(app.id)"
+					:disabled="installing || isLoading"
 					@click="remove(app.id)">
 				<input v-if="app.active"
 					class="enable"
 					type="button"
 					:value="t('settings','Disable')"
-					:disabled="installing || loading(app.id)"
+					:disabled="installing || isLoading"
 					@click="disable(app.id)">
 				<input v-if="!app.active && (app.canInstall || app.isCompatible)"
 					v-tooltip.auto="enableButtonTooltip"
 					class="enable primary"
 					type="button"
 					:value="enableButtonText"
-					:disabled="!app.canInstall || installing || loading(app.id)"
+					:disabled="!app.canInstall || installing || isLoading"
 					@click="enable(app.id)">
-				<input v-else-if="!app.active"
+				<input v-else-if="!app.active && !app.canInstall"
 					v-tooltip.auto="forceEnableButtonTooltip"
 					class="enable force"
 					type="button"
 					:value="forceEnableButtonText"
-					:disabled="installing || loading(app.id)"
+					:disabled="installing || isLoading"
 					@click="forceEnable(app.id)">
 			</div>
-			<div class="app-groups">
-				<div v-if="app.active && canLimitToGroups(app)" class="groups-enable">
-					<input :id="prefix('groups_enable', app.id)"
-						v-model="groupCheckedAppsData"
-						type="checkbox"
-						:value="app.id"
-						class="groups-enable__checkbox checkbox"
-						@change="setGroupLimit">
-					<label :for="prefix('groups_enable', app.id)">{{ t('settings', 'Limit to groups') }}</label>
-					<input type="hidden"
-						class="group_select"
-						:title="t('settings', 'All')"
-						value="">
-					<Multiselect v-if="isLimitedToGroups(app)"
-						:options="groups"
-						:value="appGroups"
-						:options-limit="5"
-						:placeholder="t('settings', 'Limit app usage to groups')"
-						label="name"
-						track-by="id"
-						class="multiselect-vue"
-						:multiple="true"
-						:close-on-select="false"
-						:tag-width="60"
-						@select="addGroupLimitation"
-						@remove="removeGroupLimitation"
-						@search-change="asyncFindGroup">
-						<span slot="noResult">{{ t('settings', 'No results') }}</span>
-					</Multiselect>
-				</div>
-			</div>
 		</div>
 
-		<ul class="app-dependencies">
+		<ul class="app-details__dependencies">
 			<li v-if="app.missingMinOwnCloudVersion">
 				{{ t('settings', 'This app has no minimum Nextcloud version assigned. This will be an error in the future.') }}
 			</li>
@@ -107,7 +105,7 @@
 			</li>
 		</ul>
 
-		<p class="documentation">
+		<p class="app-details__documentation">
 			<a v-if="!app.internal"
 				class="appslink"
 				:href="appstoreUrl"
@@ -142,7 +140,7 @@
 				rel="noreferrer noopener">{{ t('settings', 'Developer documentation') }} ↗</a>
 		</p>
 
-		<div class="app-description" v-html="renderMarkdown" />
+		<div class="app-details__description" v-html="renderMarkdown" />
 	</div>
 </template>
 
@@ -151,7 +149,6 @@ import { Multiselect } from '@nextcloud/vue'
 import marked from 'marked'
 import dompurify from 'dompurify'
 
-import AppScore from './AppList/AppScore'
 import AppManagement from '../mixins/AppManagement'
 import PrefixMixin from './PrefixMixin'
 
@@ -160,11 +157,15 @@ export default {
 
 	components: {
 		Multiselect,
-		AppScore,
 	},
 	mixins: [AppManagement, PrefixMixin],
 
-	props: ['category', 'app'],
+	props: {
+		app: {
+			type: Object,
+			required: true,
+		},
+	},
 
 	data() {
 		return {
@@ -182,9 +183,6 @@ export default {
 			}
 			return null
 		},
-		hasRating() {
-			return this.app.appstoreData && this.app.appstoreData.ratingNumOverall > 5
-		},
 		author() {
 			if (typeof this.app.author === 'string') {
 				return [
@@ -275,16 +273,45 @@ export default {
 }
 </script>
 
-<style scoped>
-	.force {
-		background: var(--color-main-background);
-		border-color: var(--color-error);
-		color: var(--color-error);
+<style scoped lang="scss">
+.app-details {
+	padding: 20px;
+
+	&__actions {
+		// app management
+		&-manage {
+			// if too many, shrink them and ellipsis
+			display: flex;
+			input {
+				flex: 0 1 auto;
+				min-width: 0;
+				text-overflow: ellipsis;
+				white-space: nowrap;
+				overflow: hidden;
+			}
+		}
 	}
-	.force:hover,
-	.force:active {
-		background: var(--color-error);
-		border-color: var(--color-error) !important;
-		color: var(--color-main-background);
+	&__dependencies {
+		opacity: .7;
 	}
+	&__documentation {
+		padding-top: 20px;
+	}
+	&__description {
+		padding-top: 20px;
+	}
+}
+
+.force {
+	color: var(--color-error);
+	border-color: var(--color-error);
+	background: var(--color-main-background);
+}
+.force:hover,
+.force:active {
+	color: var(--color-main-background);
+	border-color: var(--color-error) !important;
+	background: var(--color-error);
+}
+
 </style>
diff --git a/apps/settings/src/views/Apps.vue b/apps/settings/src/views/Apps.vue
index 13ddd8bfaf8bdb8bed8a690bd4a7dab2d4486187..313c58afbd96077768d60a0d12de9614663219e9 100644
--- a/apps/settings/src/views/Apps.vue
+++ b/apps/settings/src/views/Apps.vue
@@ -25,6 +25,7 @@
 		:class="{ 'with-app-sidebar': app}"
 		:content-class="{ 'icon-loading': loadingList }"
 		:navigation-class="{ 'icon-loading': loading }">
+		<!-- Categories & filters -->
 		<AppNavigation>
 			<template #list>
 				<AppNavigationItem
@@ -86,9 +87,13 @@
 					:title="t('settings', 'Developer documentation') + ' ↗'" />
 			</template>
 		</AppNavigation>
+
+		<!-- Apps list -->
 		<AppContent class="app-settings-content" :class="{ 'icon-loading': loadingList }">
 			<AppList :category="category" :app="app" :search="searchQuery" />
 		</AppContent>
+
+		<!-- Selected app details -->
 		<AppSidebar
 			v-if="id && app"
 			v-bind="appSidebar"
@@ -97,7 +102,9 @@
 			<template v-if="!appSidebar.background" #header>
 				<div class="app-sidebar-header__figure--default-app-icon icon-settings-dark" />
 			</template>
+
 			<template #primary-actions>
+				<!-- Featured/Supported badges -->
 				<div v-if="app.level === 300 || app.level === 200 || hasRating" class="app-level">
 					<span v-if="app.level === 300"
 						v-tooltip.auto="t('settings', 'This app is supported via your current Nextcloud subscription.')"
@@ -110,47 +117,14 @@
 					<AppScore v-if="hasRating" :score="app.appstoreData.ratingOverall" />
 				</div>
 			</template>
-			<template #secondary-actions>
-				<ActionButton v-if="app.update"
-					:disabled="installing || isLoading"
-					icon="icon-download"
-					@click="update(app.id)">
-					{{ t('settings', 'Update to {version}', {version: app.update}) }}
-				</ActionButton>
-				<ActionButton v-if="app.canUnInstall"
-					:disabled="installing || isLoading"
-					icon="icon-delete"
-					@click="remove(app.id)">
-					{{ t('settings', 'Remove') }}
-				</ActionButton>
-				<ActionButton v-if="app.active"
-					:disabled="installing || isLoading"
-					icon="icon-close"
-					@click="disable(app.id)">
-					{{ t('settings','Disable') }}
-				</ActionButton>
-				<ActionButton v-if="!app.active && (app.canInstall || app.isCompatible)"
-					v-tooltip.auto="enableButtonTooltip"
-					:disabled="!app.canInstall || installing || isLoading"
-					icon="icon-checkmark"
-					@click="enable(app.id)">
-					{{ enableButtonText }}
-				</ActionButton>
-				<ActionButton v-else-if="!app.active"
-					v-tooltip.auto="forceEnableButtonTooltip"
-					:disabled="installing || isLoading"
-					icon="icon-checkmark"
-					@click="forceEnable(app.id)">
-					{{ forceEnableButtonText }}
-				</ActionButton>
-			</template>
-			<AppDetails :category="category" :app="app" />
+
+			<!-- Tab content -->
+			<AppDetails :app="app" />
 		</AppSidebar>
 	</Content>
 </template>
 
 <script>
-import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
 import AppContent from '@nextcloud/vue/dist/Components/AppContent'
 import AppNavigation from '@nextcloud/vue/dist/Components/AppNavigation'
 import AppNavigationCounter from '@nextcloud/vue/dist/Components/AppNavigationCounter'
@@ -164,13 +138,14 @@ import VueLocalStorage from 'vue-localstorage'
 import AppList from '../components/AppList'
 import AppDetails from '../components/AppDetails'
 import AppManagement from '../mixins/AppManagement'
+import AppScore from '../components/AppList/AppScore'
 
 Vue.use(VueLocalStorage)
 
 export default {
 	name: 'Apps',
+
 	components: {
-		ActionButton,
 		AppContent,
 		AppDetails,
 		AppList,
@@ -178,10 +153,13 @@ export default {
 		AppNavigationCounter,
 		AppNavigationItem,
 		AppNavigationSpacer,
+		AppScore,
 		AppSidebar,
 		Content,
 	},
+
 	mixins: [AppManagement],
+
 	props: {
 		category: {
 			type: String,
@@ -192,11 +170,14 @@ export default {
 			default: '',
 		},
 	},
+
 	data() {
 		return {
 			searchQuery: '',
+			screenshotLoaded: false,
 		}
 	},
+
 	computed: {
 		loading() {
 			return this.$store.getters.loading('categories')
@@ -220,6 +201,10 @@ export default {
 			return this.$store.getters.getServerData
 		},
 
+		hasRating() {
+			return this.app.appstoreData && this.app.appstoreData.ratingNumOverall > 5
+		},
+
 		// sidebar app binding
 		appSidebar() {
 			const author = Array.isArray(this.app.author)
@@ -235,20 +220,33 @@ export default {
 
 			return {
 				subtitle,
-				background: this.app.screenshot
+				background: this.app.screenshot && this.screenshotLoaded
 					? this.app.screenshot
 					: this.app.preview,
-				compact: !this.app.screenshot,
+				compact: !(this.app.screenshot && this.screenshotLoaded),
 				title: this.app.name,
 
 			}
 		},
 	},
+
 	watch: {
 		category(val, old) {
 			this.setSearch('')
 		},
+
+		app() {
+			this.screenshotLoaded = false
+			if (this.app && this.app.screenshot) {
+				const image = new Image()
+				image.onload = (e) => {
+					this.screenshotLoaded = true
+				}
+				image.src = this.app.screenshot
+			}
+		},
 	},
+
 	beforeMount() {
 		this.$store.dispatch('getCategories')
 		this.$store.dispatch('getAllApps')
@@ -261,6 +259,7 @@ export default {
 		 */
 		this.appSearch = new OCA.Search(this.setSearch, this.resetSearch)
 	},
+
 	methods: {
 		setSearch(query) {
 			this.searchQuery = query
@@ -279,17 +278,17 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-
-#app-sidebar::v-deep {
+.app-sidebar::v-deep {
 	&:not(.app-sidebar--without-background) {
 		// with full screenshot, let's fill the figure
 		:not(.app-sidebar-header--compact) .app-sidebar-header__figure {
-			background-size: cover
+			background-size: cover;
 		}
 		// revert sidebar app icon so it is black
 		.app-sidebar-header--compact .app-sidebar-header__figure {
-			filter: invert(1);
 			background-size: 32px;
+
+			filter: invert(1);
 		}
 	}
 
@@ -300,19 +299,30 @@ export default {
 			align-items: center;
 			justify-content: center;
 			&--default-app-icon {
-				height: 32px;
 				width: 32px;
+				height: 32px;
 				background-size: 32px;
 			}
 		}
 	}
 
-	// allow multi line subtitle for the license
-	.app-sidebar-header__subtitle {
-		white-space: pre-line !important;
-		line-height: 16px;
-		overflow: visible !important;
-		height: 22px;
+	// TODO: migrate to components
+	.app-sidebar-header__desc {
+		// allow multi line subtitle for the license
+		.app-sidebar-header__subtitle {
+			overflow: visible !important;
+			height: auto;
+			white-space: normal !important;
+			line-height: 16px;
+		}
+	}
+
+	.app-sidebar-header__action {
+		// align with tab content
+		margin: 0 20px;
+		input {
+			margin: 3px;
+		}
 	}
 }