diff --git a/changelog/unreleased/enhancement-spaces-sidebar b/changelog/unreleased/enhancement-spaces-sidebar
new file mode 100644
index 00000000000..fc4a2731600
--- /dev/null
+++ b/changelog/unreleased/enhancement-spaces-sidebar
@@ -0,0 +1,6 @@
+Enhancement: Implement the right sidebar for spaces
+
+The right sidebar for a space functions similar to the files sidebar and gives the user basic information and actions for the current space.
+
+https://github.com/owncloud/web/pull/6437
+https://github.com/owncloud/web/issues/6284
diff --git a/packages/web-app-files/src/components/SideBar/Actions/SpaceActions.vue b/packages/web-app-files/src/components/SideBar/Actions/SpaceActions.vue
new file mode 100644
index 00000000000..76b1f3f1178
--- /dev/null
+++ b/packages/web-app-files/src/components/SideBar/Actions/SpaceActions.vue
@@ -0,0 +1,59 @@
+<template>
+  <oc-list id="oc-spaces-actions-sidebar" class-name="oc-mt-s">
+    <action-menu-item
+      v-for="(action, index) in actions"
+      :key="`action-${index}`"
+      :action="action"
+      :items="resources"
+      class="oc-py-xs"
+    />
+  </oc-list>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+import ActionMenuItem from '../../ActionMenuItem.vue'
+import Rename from '../../../mixins/spaces/actions/rename'
+import Delete from '../../../mixins/spaces/actions/delete'
+import Disable from '../../../mixins/spaces/actions/disable'
+import Restore from '../../../mixins/spaces/actions/restore'
+import EditDescription from '../../../mixins/spaces/actions/editDescription'
+
+export default {
+  name: 'SpaceActions',
+  title: ($gettext) => {
+    return $gettext('Actions')
+  },
+  components: { ActionMenuItem },
+  mixins: [Rename, Delete, EditDescription, Disable, Restore],
+  computed: {
+    ...mapGetters('Files', ['highlightedFile']),
+
+    resources() {
+      return [this.highlightedFile]
+    },
+
+    actions() {
+      return [
+        ...this.$_rename_items,
+        ...this.$_editDescription_items,
+        ...this.$_restore_items,
+        ...this.$_delete_items,
+        ...this.$_disable_items
+      ].filter((item) => item.isEnabled({ resources: this.resources }))
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+#oc-spaces-actions-sidebar {
+  > li a,
+  > li a:hover {
+    color: var(--oc-color-swatch-passive-default);
+    display: inline-flex;
+    gap: 10px;
+    vertical-align: top;
+  }
+}
+</style>
diff --git a/packages/web-app-files/src/components/SideBar/Details/SpaceDetails.vue b/packages/web-app-files/src/components/SideBar/Details/SpaceDetails.vue
new file mode 100644
index 00000000000..1567bcd94d4
--- /dev/null
+++ b/packages/web-app-files/src/components/SideBar/Details/SpaceDetails.vue
@@ -0,0 +1,230 @@
+<template>
+  <div id="oc-space-details-sidebar">
+    <div class="oc-space-details-sidebar-image oc-text-center">
+      <oc-spinner v-if="loadImageTask.isRunning" />
+      <img
+        v-else-if="spaceImage"
+        :src="'data:image/jpeg;base64,' + spaceImage"
+        alt=""
+        class="oc-mb-m"
+      />
+      <oc-icon
+        v-else
+        name="layout-grid"
+        size="xxlarge"
+        class="space-default-image oc-px-m oc-py-m"
+      />
+    </div>
+    <div v-if="hasPeopleShares || hasLinkShares" class="oc-flex oc-flex-middle oc-mb-m">
+      <oc-icon v-if="hasPeopleShares" name="group" class="oc-mr-s" />
+      <oc-icon v-if="hasLinkShares" name="link" class="oc-mr-s" />
+      <span class="oc-text-small" v-text="shareLabel" />
+    </div>
+    <div>
+      <table class="details-table" :aria-label="detailsTableLabel">
+        <tr>
+          <th scope="col" class="oc-pr-s" v-text="$gettext('Last activity')" />
+          <td v-text="lastModifyDate" />
+        </tr>
+        <tr v-if="space.description">
+          <th scope="col" class="oc-pr-s" v-text="$gettext('Subtitle')" />
+          <td v-text="space.description" />
+        </tr>
+        <tr>
+          <th scope="col" class="oc-pr-s" v-text="$gettext('Manager')" />
+          <td>
+            <span v-if="!loadOwnersTask.isRunning" v-text="ownerUsernames" />
+          </td>
+        </tr>
+        <tr>
+          <th scope="col" class="oc-pr-s" v-text="$gettext('Quota')" />
+          <td>
+            <space-quota :space-quota="space.spaceQuota" />
+          </td>
+        </tr>
+      </table>
+    </div>
+  </div>
+</template>
+<script>
+import { ref } from '@vue/composition-api'
+import Mixins from '../../../mixins'
+import MixinResources from '../../../mixins/resources'
+import { mapGetters } from 'vuex'
+import { useTask } from 'vue-concurrency'
+import { buildWebDavSpacesPath } from '../../../helpers/resources'
+import { useStore } from 'web-pkg/src/composables'
+import { clientService } from 'web-pkg/src/services'
+import SpaceQuota from '../../SpaceQuota.vue'
+
+export default {
+  name: 'SpaceDetails',
+  components: { SpaceQuota },
+  mixins: [Mixins, MixinResources],
+  inject: ['displayedItem'],
+  title: ($gettext) => {
+    return $gettext('Details')
+  },
+  setup() {
+    const store = useStore()
+    const spaceImage = ref('')
+    const owners = ref([])
+    const graphClient = clientService.graphAuthenticated(
+      store.getters.configuration.server,
+      store.getters.getToken
+    )
+
+    const loadImageTask = useTask(function* (signal, ref) {
+      if (!ref.space?.spaceImageData) {
+        return
+      }
+
+      const fileContents = yield ref.$client.files.getFileContents(
+        buildWebDavSpacesPath(ref.space.id, ref.space.spaceImageData.name),
+        { responseType: 'arrayBuffer' }
+      )
+
+      spaceImage.value = Buffer.from(fileContents).toString('base64')
+    })
+
+    const loadOwnersTask = useTask(function* (signal, ref) {
+      const promises = []
+      for (const userId of ref.ownerUserIds) {
+        promises.push(graphClient.users.getUser(userId))
+      }
+
+      if (promises.length > 0) {
+        yield Promise.all(promises).then((resolvedData) => {
+          resolvedData.forEach((response) => {
+            owners.value.push(response.data)
+          })
+        })
+      }
+    })
+
+    return { loadImageTask, loadOwnersTask, spaceImage, owners }
+  },
+  computed: {
+    ...mapGetters(['user']),
+
+    space() {
+      return this.displayedItem.value
+    },
+    detailsTableLabel() {
+      return this.$gettext('Overview of the information about the selected space')
+    },
+    lastModifyDate() {
+      return this.formDateFromISO(this.space.mdate)
+    },
+    ownerUserIds() {
+      const permissions = this.space.spacePermissions?.filter((permission) =>
+        permission.roles.includes('manager')
+      )
+      if (!permissions.length) {
+        return []
+      }
+
+      const userIds = permissions.reduce((acc, item) => {
+        const ids = item.grantedTo.map((user) => user.user.id)
+        acc = acc.concat(ids)
+        return acc
+      }, [])
+
+      return [...new Set(userIds)]
+    },
+    ownerUsernames() {
+      const userId = this.user?.id
+      return this.owners
+        .map((owner) => {
+          if (owner.onPremisesSamAccountName === userId) {
+            return this.$gettextInterpolate(this.$gettext('%{displayName} (me)'), {
+              displayName: owner.displayName
+            })
+          }
+          return owner.displayName
+        })
+        .join(', ')
+    },
+    hasPeopleShares() {
+      return false // @TODO
+    },
+    hasLinkShares() {
+      return false // @TODO
+    },
+    peopleShareCount() {
+      return 0 // @TODO
+    },
+    linkShareCount() {
+      return 0 // @TODO
+    },
+    shareLabel() {
+      let peopleString, linksString
+
+      if (this.hasPeopleShares) {
+        peopleString = this.$gettextInterpolate(
+          this.$ngettext(
+            'This space has been shared with %{peopleShareCount} person.',
+            'This space has been shared with %{peopleShareCount} people.',
+            this.peopleShareCount
+          ),
+          {
+            peopleShareCount: this.peopleShareCount
+          }
+        )
+      }
+
+      if (this.hasLinkShares) {
+        linksString = this.$gettextInterpolate(
+          this.$ngettext(
+            '%{linkShareCount} link giving access.',
+            '%{linkShareCount} links giving access.',
+            this.linkShareCount
+          ),
+          {
+            linkShareCount: this.linkShareCount
+          }
+        )
+      }
+
+      if (peopleString && linksString) {
+        return `${peopleString} ${linksString}`
+      }
+
+      if (peopleString) {
+        return peopleString
+      }
+
+      if (linksString) {
+        return linksString
+      }
+
+      return ''
+    }
+  },
+  mounted() {
+    this.loadImageTask.perform(this)
+    this.loadOwnersTask.perform(this)
+  }
+}
+</script>
+<style lang="scss" scoped>
+.oc-space-details-sidebar {
+  &-image img {
+    max-height: 150px;
+    object-fit: cover;
+    width: 100%;
+  }
+}
+
+.details-table {
+  text-align: left;
+
+  tr {
+    height: 1.5rem;
+  }
+
+  th {
+    font-weight: 600;
+  }
+}
+</style>
diff --git a/packages/web-app-files/src/components/SideBar/SideBar.vue b/packages/web-app-files/src/components/SideBar/SideBar.vue
index e9580086050..b974dae2d0e 100644
--- a/packages/web-app-files/src/components/SideBar/SideBar.vue
+++ b/packages/web-app-files/src/components/SideBar/SideBar.vue
@@ -52,10 +52,11 @@
           </oc-button>
         </div>
         <file-info
-          v-if="isSingleResource"
+          v-if="isSingleResource && !highlightedFileIsSpace"
           class="sidebar-panel__file_info"
           :is-content-displayed="isContentDisplayed"
         />
+        <space-info v-if="highlightedFileIsSpace" class="sidebar-panel__space_info" />
         <div class="sidebar-panel__body">
           <template v-if="isContentDisplayed">
             <component :is="panel.component" class="sidebar-panel__body-content" />
@@ -95,12 +96,13 @@ import { isLocationCommonActive, isLocationSharesActive } from '../../router'
 import { computed } from '@vue/composition-api'
 
 import FileInfo from './FileInfo.vue'
+import SpaceInfo from './SpaceInfo.vue'
 
 let visibilityObserver
 let hiddenObserver
 
 export default {
-  components: { FileInfo },
+  components: { FileInfo, SpaceInfo },
 
   provide() {
     return {
@@ -181,7 +183,7 @@ export default {
       return null
     },
     isSingleResource() {
-      return !this.areMultipleSelected && !this.isRootFolder
+      return !this.areMultipleSelected && (!this.isRootFolder || this.highlightedFileIsSpace)
     },
     areMultipleSelected() {
       return this.selectedFiles && this.selectedFiles.length > 1
@@ -194,6 +196,9 @@ export default {
     },
     highlightedFileFavorite() {
       return this.highlightedFile?.starred
+    },
+    highlightedFileIsSpace() {
+      return this.highlightedFile?.type === 'space'
     }
   },
   watch: {
@@ -292,7 +297,10 @@ export default {
         return
       }
 
-      if (isLocationCommonActive(this.$router, 'files-common-trash')) {
+      if (
+        isLocationCommonActive(this.$router, 'files-common-trash') ||
+        this.highlightedFileIsSpace
+      ) {
         this.selectedFile = this.highlightedFile
         return
       }
@@ -391,7 +399,8 @@ export default {
     }
   }
 
-  &__file_info {
+  &__file_info,
+  &__space_info {
     border-bottom: 1px solid var(--oc-color-border);
     background-color: var(--oc-color-background-default);
     padding: 0 10px;
diff --git a/packages/web-app-files/src/components/SideBar/SpaceInfo.vue b/packages/web-app-files/src/components/SideBar/SpaceInfo.vue
new file mode 100644
index 00000000000..fe2bc529ad1
--- /dev/null
+++ b/packages/web-app-files/src/components/SideBar/SpaceInfo.vue
@@ -0,0 +1,52 @@
+<template>
+  <div class="space_info">
+    <div class="space_info__body oc-text-overflow oc-flex oc-flex-middle">
+      <div class="oc-mr-s">
+        <oc-icon
+          name="layout-grid"
+          :size="space.description ? 'large' : 'medium'"
+          class="oc-display-block"
+        />
+      </div>
+      <div>
+        <h3 data-testid="space-info-name" v-text="space.name" />
+        <span data-testid="space-info-subtitle" v-text="space.description" />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import Mixins from '../../mixins'
+
+export default {
+  name: 'SpaceInfo',
+  mixins: [Mixins],
+  inject: ['displayedItem'],
+  computed: {
+    space() {
+      return this.displayedItem.value
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+.space_info {
+  display: grid;
+  grid-template-columns: auto 1fr auto;
+  align-items: center;
+  grid-gap: 5px;
+
+  &__body {
+    text-align: left;
+    font-size: 0.75rem;
+
+    h3 {
+      font-size: 0.9rem;
+      font-weight: 600;
+      margin: 0;
+    }
+  }
+}
+</style>
diff --git a/packages/web-app-files/src/components/SpaceQuota.vue b/packages/web-app-files/src/components/SpaceQuota.vue
new file mode 100644
index 00000000000..865b50c81da
--- /dev/null
+++ b/packages/web-app-files/src/components/SpaceQuota.vue
@@ -0,0 +1,58 @@
+<template>
+  <div class="space-quota">
+    <p class="oc-mb-s oc-mt-rm" v-text="spaceStorageDetailsLabel" />
+    <oc-progress
+      :value="parseInt(quotaUsagePercent)"
+      :max="100"
+      size="small"
+      :variation="quotaProgressVariant"
+    />
+  </div>
+</template>
+
+<script>
+import filesize from 'filesize'
+
+export default {
+  name: 'SpaceQuota',
+  props: {
+    spaceQuota: {
+      type: Object,
+      required: true
+    }
+  },
+  computed: {
+    spaceStorageDetailsLabel() {
+      return this.$gettextInterpolate(
+        this.$gettext('%{used} of %{total} used (%{percentage}% used)'),
+        {
+          used: this.quotaUsed,
+          total: this.quotaTotal,
+          percentage: this.quotaUsagePercent
+        }
+      )
+    },
+    quotaTotal() {
+      return filesize(this.spaceQuota.total)
+    },
+    quotaUsed() {
+      return filesize(this.spaceQuota.used)
+    },
+    quotaUsagePercent() {
+      return ((this.spaceQuota.used / this.spaceQuota.total) * 100).toFixed(1)
+    },
+    quotaProgressVariant() {
+      switch (this.spaceQuota.state) {
+        case 'normal':
+          return 'primary'
+        case 'nearing':
+          return 'warning'
+        case 'critical':
+          return 'warning'
+        default:
+          return 'danger'
+      }
+    }
+  }
+}
+</script>
diff --git a/packages/web-app-files/src/fileSideBars.js b/packages/web-app-files/src/fileSideBars.js
index eae8cb8038e..63ab3881037 100644
--- a/packages/web-app-files/src/fileSideBars.js
+++ b/packages/web-app-files/src/fileSideBars.js
@@ -5,18 +5,20 @@ import FileVersions from './components/SideBar/Versions/FileVersions.vue'
 import FileShares from './components/SideBar/Shares/FileShares.vue'
 import FileLinks from './components/SideBar/Links/FileLinks.vue'
 import NoSelection from './components/SideBar/NoSelection.vue'
-import { isLocationCommonActive } from './router'
+import SpaceActions from './components/SideBar/Actions/SpaceActions.vue'
+import SpaceDetails from './components/SideBar/Details/SpaceDetails.vue'
+import { isLocationCommonActive, isLocationSpacesActive } from './router'
 
 export default [
   // We don't have file details in the trashbin, yet.
   // Only allow `actions` panel on trashbin route for now.
-  ({ rootFolder }) => ({
+  ({ rootFolder, highlightedFile }) => ({
     app: 'no-selection-item',
     icon: 'questionnaire-line',
     component: NoSelection,
     default: () => true,
     get enabled() {
-      return rootFolder
+      return rootFolder && highlightedFile?.type !== 'space'
     }
   }),
   ({ router, multipleSelection, rootFolder }) => ({
@@ -92,5 +94,23 @@ export default [
       }
       return !!capabilities.core && highlightedFile && highlightedFile.type !== 'folder'
     }
+  }),
+  ({ router, highlightedFile }) => ({
+    app: 'details-space-item',
+    icon: 'questionnaire-line',
+    component: SpaceDetails,
+    default: isLocationSpacesActive(router, 'files-spaces-projects'),
+    get enabled() {
+      return highlightedFile?.type === 'space'
+    }
+  }),
+  ({ highlightedFile }) => ({
+    app: 'space-actions-item',
+    component: SpaceActions,
+    icon: 'slideshow-3',
+    iconFillType: 'line',
+    get enabled() {
+      return highlightedFile?.type === 'space'
+    }
   })
 ]
diff --git a/packages/web-app-files/src/mixins/spaces/actions/delete.js b/packages/web-app-files/src/mixins/spaces/actions/delete.js
index 02dce11ebfc..564cce18c23 100644
--- a/packages/web-app-files/src/mixins/spaces/actions/delete.js
+++ b/packages/web-app-files/src/mixins/spaces/actions/delete.js
@@ -14,12 +14,12 @@ export default {
             return this.$gettext('Delete')
           },
           handler: this.$_delete_trigger,
-          isEnabled: ({ spaces }) => {
-            if (spaces.length !== 1) {
+          isEnabled: ({ resources }) => {
+            if (resources.length !== 1) {
               return false
             }
 
-            return spaces[0].disabled
+            return resources[0].disabled
           },
           componentType: 'oc-button',
           class: 'oc-files-actions-delete-trigger'
@@ -37,21 +37,21 @@ export default {
     ]),
     ...mapMutations('Files', ['REMOVE_FILE']),
 
-    $_delete_trigger({ spaces }) {
-      if (spaces.length !== 1) {
+    $_delete_trigger({ resources }) {
+      if (resources.length !== 1) {
         return
       }
 
       const modal = {
         variation: 'danger',
-        title: this.$gettext('Delete space') + ' ' + spaces[0].name,
+        title: this.$gettext('Delete space') + ' ' + resources[0].name,
         cancelText: this.$gettext('Cancel'),
         confirmText: this.$gettext('Delete'),
         icon: 'alarm-warning',
         message: this.$gettext('Are you sure you want to delete this space?'),
         hasInput: false,
         onCancel: this.hideModal,
-        onConfirm: () => this.$_delete_deleteSpace(spaces[0].id)
+        onConfirm: () => this.$_delete_deleteSpace(resources[0].id)
       }
 
       this.createModal(modal)
diff --git a/packages/web-app-files/src/mixins/spaces/actions/disable.js b/packages/web-app-files/src/mixins/spaces/actions/disable.js
index 9a7222d8f6a..75a018c6a13 100644
--- a/packages/web-app-files/src/mixins/spaces/actions/disable.js
+++ b/packages/web-app-files/src/mixins/spaces/actions/disable.js
@@ -14,12 +14,12 @@ export default {
             return this.$gettext('Disable')
           },
           handler: this.$_disable_trigger,
-          isEnabled: ({ spaces }) => {
-            if (spaces.length !== 1) {
+          isEnabled: ({ resources }) => {
+            if (resources.length !== 1) {
               return false
             }
 
-            return !spaces[0].disabled
+            return !resources[0].disabled
           },
           componentType: 'oc-button',
           class: 'oc-files-actions-disable-trigger'
@@ -37,21 +37,21 @@ export default {
     ]),
     ...mapMutations('Files', ['UPDATE_RESOURCE_FIELD']),
 
-    $_disable_trigger({ spaces }) {
-      if (spaces.length !== 1) {
+    $_disable_trigger({ resources }) {
+      if (resources.length !== 1) {
         return
       }
 
       const modal = {
         variation: 'danger',
-        title: this.$gettext('Disable space') + ' ' + spaces[0].name,
+        title: this.$gettext('Disable space') + ' ' + resources[0].name,
         cancelText: this.$gettext('Cancel'),
         confirmText: this.$gettext('Disable'),
         icon: 'alarm-warning',
         message: this.$gettext('Are you sure you want to disable this space?'),
         hasInput: false,
         onCancel: this.hideModal,
-        onConfirm: () => this.$_disable_disableSpace(spaces[0].id)
+        onConfirm: () => this.$_disable_disableSpace(resources[0].id)
       }
 
       this.createModal(modal)
diff --git a/packages/web-app-files/src/mixins/spaces/actions/editDescription.js b/packages/web-app-files/src/mixins/spaces/actions/editDescription.js
index 1e9b7a46ec0..833d8f54b9d 100644
--- a/packages/web-app-files/src/mixins/spaces/actions/editDescription.js
+++ b/packages/web-app-files/src/mixins/spaces/actions/editDescription.js
@@ -31,22 +31,22 @@ export default {
     ]),
     ...mapMutations('Files', ['UPDATE_RESOURCE_FIELD']),
 
-    $_editDescription_trigger({ spaces }) {
-      if (spaces.length !== 1) {
+    $_editDescription_trigger({ resources }) {
+      if (resources.length !== 1) {
         return
       }
 
       const modal = {
         variation: 'passive',
-        title: this.$gettext('Change description for space') + ' ' + spaces[0].name,
+        title: this.$gettext('Change description for space') + ' ' + resources[0].name,
         cancelText: this.$gettext('Cancel'),
         confirmText: this.$gettext('Confirm'),
         hasInput: true,
         inputLabel: this.$gettext('Space description'),
-        inputValue: spaces[0].description,
+        inputValue: resources[0].description,
         onCancel: this.hideModal,
         onConfirm: (description) =>
-          this.$_editDescription_editDescriptionSpace(spaces[0].id, description)
+          this.$_editDescription_editDescriptionSpace(resources[0].id, description)
       }
 
       this.createModal(modal)
diff --git a/packages/web-app-files/src/mixins/spaces/actions/rename.js b/packages/web-app-files/src/mixins/spaces/actions/rename.js
index 5cf4049b939..f7bde4bd7d0 100644
--- a/packages/web-app-files/src/mixins/spaces/actions/rename.js
+++ b/packages/web-app-files/src/mixins/spaces/actions/rename.js
@@ -14,7 +14,7 @@ export default {
             return this.$gettext('Rename')
           },
           handler: this.$_rename_trigger,
-          isEnabled: ({ spaces }) => spaces.length === 1,
+          isEnabled: ({ resources }) => resources.length === 1,
           componentType: 'oc-button',
           class: 'oc-files-actions-rename-trigger'
         }
@@ -31,21 +31,21 @@ export default {
     ]),
     ...mapMutations('Files', ['UPDATE_RESOURCE_FIELD']),
 
-    $_rename_trigger({ spaces }) {
-      if (spaces.length !== 1) {
+    $_rename_trigger({ resources }) {
+      if (resources.length !== 1) {
         return
       }
 
       const modal = {
         variation: 'passive',
-        title: this.$gettext('Rename space') + ' ' + spaces[0].name,
+        title: this.$gettext('Rename space') + ' ' + resources[0].name,
         cancelText: this.$gettext('Cancel'),
         confirmText: this.$gettext('Rename'),
         hasInput: true,
         inputLabel: this.$gettext('Space name'),
-        inputValue: spaces[0].name,
+        inputValue: resources[0].name,
         onCancel: this.hideModal,
-        onConfirm: (name) => this.$_rename_renameSpace(spaces[0].id, name),
+        onConfirm: (name) => this.$_rename_renameSpace(resources[0].id, name),
         onInput: this.$_rename_checkName
       }
 
diff --git a/packages/web-app-files/src/mixins/spaces/actions/restore.js b/packages/web-app-files/src/mixins/spaces/actions/restore.js
index a8ab0968bc9..7bbd5afd937 100644
--- a/packages/web-app-files/src/mixins/spaces/actions/restore.js
+++ b/packages/web-app-files/src/mixins/spaces/actions/restore.js
@@ -14,12 +14,12 @@ export default {
             return this.$gettext('Restore')
           },
           handler: this.$_restore_trigger,
-          isEnabled: ({ spaces }) => {
-            if (spaces.length !== 1) {
+          isEnabled: ({ resources }) => {
+            if (resources.length !== 1) {
               return false
             }
 
-            return spaces[0].disabled
+            return resources[0].disabled
           },
           componentType: 'oc-button',
           class: 'oc-files-actions-restore-trigger'
@@ -37,21 +37,21 @@ export default {
     ]),
     ...mapMutations('Files', ['UPDATE_RESOURCE_FIELD']),
 
-    $_restore_trigger({ spaces }) {
-      if (spaces.length !== 1) {
+    $_restore_trigger({ resources }) {
+      if (resources.length !== 1) {
         return
       }
 
       const modal = {
         variation: 'passive',
-        title: this.$gettext('Restore space') + ' ' + spaces[0].name,
+        title: this.$gettext('Restore space') + ' ' + resources[0].name,
         cancelText: this.$gettext('Cancel'),
         confirmText: this.$gettext('Restore'),
         icon: 'alarm-warning',
         message: this.$gettext('Are you sure you want to restore this space?'),
         hasInput: false,
         onCancel: this.hideModal,
-        onConfirm: () => this.$_restore_restoreSpace(spaces[0].id)
+        onConfirm: () => this.$_restore_restoreSpace(resources[0].id)
       }
 
       this.createModal(modal)
diff --git a/packages/web-app-files/src/mixins/spaces/actions/showDetails.js b/packages/web-app-files/src/mixins/spaces/actions/showDetails.js
index d8772747761..f9422f7bc2c 100644
--- a/packages/web-app-files/src/mixins/spaces/actions/showDetails.js
+++ b/packages/web-app-files/src/mixins/spaces/actions/showDetails.js
@@ -1,4 +1,4 @@
-import { mapActions } from 'vuex'
+import { mapActions, mapMutations } from 'vuex'
 
 export default {
   computed: {
@@ -10,7 +10,7 @@ export default {
           iconFillType: 'line',
           label: () => this.$gettext('Details'),
           handler: this.$_showDetails_trigger,
-          isEnabled: () => false, // @TODO As soon as we have the details
+          isEnabled: ({ resources }) => resources.length === 1,
           componentType: 'oc-button',
           class: 'oc-files-actions-show-details-trigger'
         }
@@ -18,10 +18,16 @@ export default {
     }
   },
   methods: {
-    ...mapActions('Files/sidebar', { openSidebar: 'open' }),
+    ...mapActions('Files/sidebar', { openSidebar: 'open', closeSidebar: 'close' }),
+    ...mapMutations('Files', ['SET_FILE_SELECTION']),
 
-    async $_showDetails_trigger() {
-      await this.openSidebar()
+    $_showDetails_trigger({ resources }) {
+      if (resources.length !== 1) {
+        return
+      }
+
+      this.SET_FILE_SELECTION([resources[0]])
+      this.openSidebar()
     }
   }
 }
diff --git a/packages/web-app-files/src/router/spaces.ts b/packages/web-app-files/src/router/spaces.ts
index 33ffc763d52..f9aac42b612 100644
--- a/packages/web-app-files/src/router/spaces.ts
+++ b/packages/web-app-files/src/router/spaces.ts
@@ -36,7 +36,7 @@ export const buildRoutes = (components: RouteComponents): RouteConfig[] => [
         component: components.Spaces.Projects,
         meta: {
           hideFilelistActions: true,
-          hasBulkActions: true,
+          hasBulkActions: false,
           hideViewOptions: true,
           title: $gettext('Spaces')
         }
diff --git a/packages/web-app-files/src/views/spaces/Projects.vue b/packages/web-app-files/src/views/spaces/Projects.vue
index 920b523ce66..55a386b1319 100644
--- a/packages/web-app-files/src/views/spaces/Projects.vue
+++ b/packages/web-app-files/src/views/spaces/Projects.vue
@@ -103,14 +103,14 @@
                         <li
                           v-for="(action, actionIndex) in getContextMenuActions(space)"
                           :key="`action-${actionIndex}`"
-                          class="oc-spaces-context-action oc-py-xs oc-px-s"
+                          class="oc-files-context-action oc-px-s"
                         >
                           <oc-button
                             appearance="raw"
                             justify-content="left"
-                            @click="action.handler({ spaces: [space] })"
+                            @click="action.handler({ resources: [space] })"
                           >
-                            <oc-icon :name="action.icon" />
+                            <oc-icon :name="action.icon" fill-type="line" class="oc-flex" />
                             {{ action.label() }}
                           </oc-button>
                         </li>
@@ -231,17 +231,22 @@ export default {
   },
   methods: {
     ...mapActions(['createModal', 'hideModal', 'setModalInputErrorMessage']),
-    ...mapMutations('Files', ['SET_CURRENT_FOLDER', 'LOAD_FILES', 'CLEAR_CURRENT_FILES_LIST']),
+    ...mapMutations('Files', [
+      'SET_CURRENT_FOLDER',
+      'LOAD_FILES',
+      'CLEAR_CURRENT_FILES_LIST',
+      'SET_FILE_SELECTION'
+    ]),
 
     getContextMenuActions(space) {
       return [
         ...this.$_rename_items,
         ...this.$_editDescription_items,
-        ...this.$_showDetails_items,
         ...this.$_restore_items,
         ...this.$_delete_items,
-        ...this.$_disable_items
-      ].filter((item) => item.isEnabled({ spaces: [space] }))
+        ...this.$_disable_items,
+        ...this.$_showDetails_items
+      ].filter((item) => item.isEnabled({ resources: [space] }))
     },
 
     getSpaceProjectRoute({ id, name }) {
diff --git a/packages/web-app-files/tests/unit/components/SideBar/Details/SpaceDetails.spec.js b/packages/web-app-files/tests/unit/components/SideBar/Details/SpaceDetails.spec.js
new file mode 100644
index 00000000000..a1dfa2e7670
--- /dev/null
+++ b/packages/web-app-files/tests/unit/components/SideBar/Details/SpaceDetails.spec.js
@@ -0,0 +1,102 @@
+import { createLocalVue, shallowMount } from '@vue/test-utils'
+import Vuex from 'vuex'
+import SpaceDetails from '../../../../../src/components/SideBar/Details/SpaceDetails.vue'
+import stubs from '../../../../../../../tests/unit/stubs'
+import GetTextPlugin from 'vue-gettext'
+import AsyncComputed from 'vue-async-computed'
+import VueCompositionAPI from '@vue/composition-api/dist/vue-composition-api'
+
+const localVue = createLocalVue()
+localVue.use(Vuex)
+localVue.use(AsyncComputed)
+localVue.use(VueCompositionAPI)
+localVue.use(GetTextPlugin, {
+  translations: 'does-not-matter.json',
+  silent: true
+})
+const OcTooltip = jest.fn()
+
+const spaceMock = {
+  type: 'space',
+  name: ' space',
+  id: '1',
+  mdate: 'Wed, 21 Oct 2015 07:28:00 GMT',
+  spaceQuota: {
+    used: 100,
+    total: 1000
+  }
+}
+
+const formDateFromJSDate = jest.fn().mockImplementation(() => 'ABSOLUTE_TIME')
+const formDateFromHTTP = jest.fn().mockImplementation(() => 'ABSOLUTE_TIME')
+const refreshShareDetailsTree = jest.fn()
+beforeEach(() => {
+  formDateFromJSDate.mockClear()
+  formDateFromHTTP.mockClear()
+  refreshShareDetailsTree.mockReset()
+})
+
+describe('Details SideBar Panel', () => {
+  it('displayes the details side panel', () => {
+    const wrapper = createWrapper(spaceMock)
+    expect(wrapper).toMatchSnapshot()
+  })
+})
+
+function createWrapper(spaceResource) {
+  const component = {
+    ...SpaceDetails,
+    setup: () => ({
+      spaceImage: '',
+      owners: [],
+      loadImageTask: {
+        isRunning: false,
+        perform: jest.fn()
+      },
+      loadOwnersTask: {
+        isRunning: false,
+        perform: jest.fn()
+      }
+    })
+  }
+  return shallowMount(component, {
+    store: new Vuex.Store({
+      getters: {
+        user: function () {
+          return { id: 'marie' }
+        }
+      },
+      modules: {
+        Files: {
+          namespaced: true,
+          state: {
+            sharesTree: {}
+          },
+          getters: {
+            highlightedFile: function () {
+              return spaceResource
+            }
+          }
+        }
+      }
+    }),
+    localVue,
+    stubs: stubs,
+    directives: {
+      OcTooltip
+    },
+    mixins: [
+      {
+        methods: {
+          formDateFromJSDate,
+          formDateFromHTTP
+        }
+      }
+    ],
+    provide: {
+      displayedItem: {
+        value: spaceResource
+      }
+    }
+  })
+}
diff --git a/packages/web-app-files/tests/unit/components/SideBar/Details/__snapshots__/SpaceDetails.spec.js.snap b/packages/web-app-files/tests/unit/components/SideBar/Details/__snapshots__/SpaceDetails.spec.js.snap
new file mode 100644
index 00000000000..a63d31fd829
--- /dev/null
+++ b/packages/web-app-files/tests/unit/components/SideBar/Details/__snapshots__/SpaceDetails.spec.js.snap
@@ -0,0 +1,29 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Details SideBar Panel displayes the details side panel 1`] = `
+<div id="oc-space-details-sidebar">
+  <div class="oc-space-details-sidebar-image oc-text-center">
+    <oc-icon-stub name="layout-grid" size="xxlarge" class="space-default-image oc-px-m oc-py-m"></oc-icon-stub>
+  </div>
+  <!---->
+  <div>
+    <table aria-label="Overview of the information about the selected space" class="details-table">
+      <tr>
+        <th scope="col" class="oc-pr-s">Last activity</th>
+        <td>Invalid DateTime</td>
+      </tr>
+      <!---->
+      <tr>
+        <th scope="col" class="oc-pr-s">Manager</th>
+        <td><span></span></td>
+      </tr>
+      <tr>
+        <th scope="col" class="oc-pr-s">Quota</th>
+        <td>
+          <space-quota-stub spacequota="[object Object]"></space-quota-stub>
+        </td>
+      </tr>
+    </table>
+  </div>
+</div>
+`;
diff --git a/packages/web-app-files/tests/unit/components/SideBar/SpaceInfo.spec.js b/packages/web-app-files/tests/unit/components/SideBar/SpaceInfo.spec.js
new file mode 100644
index 00000000000..511a22eb235
--- /dev/null
+++ b/packages/web-app-files/tests/unit/components/SideBar/SpaceInfo.spec.js
@@ -0,0 +1,106 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils'
+import Vuex from 'vuex'
+import GetTextPlugin from 'vue-gettext'
+import AsyncComputed from 'vue-async-computed'
+
+import stubs from '@/tests/unit/stubs'
+
+import SpaceInfo from '@files/src/components/SideBar/SpaceInfo.vue'
+
+const spaceMock = {
+  type: 'space',
+  name: ' space',
+  id: '1',
+  mdate: 'Wed, 21 Oct 2015 07:28:00 GMT',
+  spaceQuota: {
+    used: 100
+  }
+}
+
+const localVue = createLocalVue()
+localVue.use(Vuex)
+localVue.use(AsyncComputed)
+localVue.use(GetTextPlugin, {
+  translations: 'does-not-matter.json',
+  silent: true
+})
+
+const selectors = {
+  name: '[data-testid="space-info-name"]',
+  subtitle: '[data-testid="space-info-subtitle"]'
+}
+
+const formDateFromRFC = jest.fn()
+const formRelativeDateFromRFC = jest.fn()
+const resetDateMocks = () => {
+  formDateFromRFC.mockReset()
+  formRelativeDateFromRFC.mockReset()
+  formDateFromRFC.mockImplementation(() => 'ABSOLUTE_TIME')
+  formRelativeDateFromRFC.mockImplementation(() => 'RELATIVE_TIME')
+}
+
+describe('SpaceInfo', () => {
+  it('shows space info', () => {
+    resetDateMocks()
+
+    const wrapper = createWrapper(spaceMock)
+    expect(wrapper.find(selectors.name).exists()).toBeTruthy()
+    expect(wrapper.find(selectors.subtitle).exists()).toBeTruthy()
+    expect(wrapper).toMatchSnapshot()
+  })
+})
+
+function createWrapper(spaceResource) {
+  return shallowMount(SpaceInfo, {
+    store: new Vuex.Store({
+      getters: {
+        user: function () {
+          return { id: 'marie' }
+        },
+        capabilities: jest.fn(() => ({}))
+      },
+      modules: {
+        Files: {
+          namespaced: true,
+          getters: {
+            highlightedFile: function () {
+              return spaceResource
+            }
+          }
+        }
+      }
+    }),
+    localVue,
+    stubs: {
+      ...stubs,
+      'oc-resource-icon': true,
+      'oc-resource-name': true
+    },
+    directives: {
+      OcTooltip: null
+    },
+    mixins: [
+      {
+        methods: {
+          formDateFromRFC,
+          formRelativeDateFromRFC
+        }
+      }
+    ],
+    mocks: {
+      $router: {
+        currentRoute: {
+          name: 'some-route',
+          query: { page: 1 }
+        },
+        resolve: (r) => ({ href: r.name })
+      },
+      publicPage: () => false
+    },
+    provide: {
+      displayedItem: {
+        value: spaceResource
+      }
+    }
+  })
+}
diff --git a/packages/web-app-files/tests/unit/components/SideBar/__snapshots__/SpaceInfo.spec.js.snap b/packages/web-app-files/tests/unit/components/SideBar/__snapshots__/SpaceInfo.spec.js.snap
new file mode 100644
index 00000000000..a76e34a8f09
--- /dev/null
+++ b/packages/web-app-files/tests/unit/components/SideBar/__snapshots__/SpaceInfo.spec.js.snap
@@ -0,0 +1,14 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`SpaceInfo shows space info 1`] = `
+<div class="space_info">
+  <div class="space_info__body oc-text-overflow oc-flex oc-flex-middle">
+    <div class="oc-mr-s">
+      <oc-icon-stub name="layout-grid" size="medium" class="oc-display-block"></oc-icon-stub>
+    </div>
+    <div>
+      <h3 data-testid="space-info-name"> space</h3> <span data-testid="space-info-subtitle"></span>
+    </div>
+  </div>
+</div>
+`;
diff --git a/packages/web-app-files/tests/unit/components/SpaceQuota.spec.js b/packages/web-app-files/tests/unit/components/SpaceQuota.spec.js
new file mode 100644
index 00000000000..40ac4ebbc0a
--- /dev/null
+++ b/packages/web-app-files/tests/unit/components/SpaceQuota.spec.js
@@ -0,0 +1,42 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils'
+import Vuex from 'vuex'
+import DesignSystem from 'owncloud-design-system'
+import GetTextPlugin from 'vue-gettext'
+
+import SpaceQuota from '@files/src/components/SpaceQuota'
+
+const localVue = createLocalVue()
+localVue.use(Vuex)
+localVue.use(DesignSystem)
+localVue.use(GetTextPlugin, {
+  translations: 'does-not-matter.json',
+  silent: true
+})
+
+describe('SpaceQuota component', () => {
+  it('renders the space storage quota label', () => {
+    const wrapper = getWrapper({ total: 10, used: 1, state: 'normal' })
+    expect(wrapper.find('.space-quota').exists()).toBeTruthy()
+    expect(wrapper).toMatchSnapshot()
+  })
+  it.each([
+    { state: 'normal', expectedVariation: 'primary' },
+    { state: 'nearing', expectedVariation: 'warning' },
+    { state: 'critical', expectedVariation: 'warning' },
+    { state: 'exceeded', expectedVariation: 'danger' }
+  ])('renders the progress variant correctly', (dataSet) => {
+    const wrapper = getWrapper({ total: 10, used: 1, state: dataSet.state })
+    const progressBar = wrapper.find('.space-quota oc-progress-stub')
+    expect(progressBar.exists()).toBeTruthy()
+    expect(progressBar.props().variation).toBe(dataSet.expectedVariation)
+  })
+})
+
+function getWrapper(spaceQuota) {
+  return shallowMount(SpaceQuota, {
+    localVue,
+    propsData: {
+      spaceQuota
+    }
+  })
+}
diff --git a/packages/web-app-files/tests/unit/components/__snapshots__/SpaceQuota.spec.js.snap b/packages/web-app-files/tests/unit/components/__snapshots__/SpaceQuota.spec.js.snap
new file mode 100644
index 00000000000..f6d007d7977
--- /dev/null
+++ b/packages/web-app-files/tests/unit/components/__snapshots__/SpaceQuota.spec.js.snap
@@ -0,0 +1,8 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`SpaceQuota component renders the space storage quota label 1`] = `
+<div class="space-quota">
+  <p class="oc-mb-s oc-mt-rm">1 B of 10 B used (10.0% used)</p>
+  <oc-progress-stub value="10" max="100" size="small" variation="primary"></oc-progress-stub>
+</div>
+`;
diff --git a/packages/web-app-files/tests/unit/mixins/spaces/delete.spec.js b/packages/web-app-files/tests/unit/mixins/spaces/delete.spec.js
index 9a2a33eae2a..08657a68a7e 100644
--- a/packages/web-app-files/tests/unit/mixins/spaces/delete.spec.js
+++ b/packages/web-app-files/tests/unit/mixins/spaces/delete.spec.js
@@ -50,14 +50,40 @@ describe('delete', () => {
     })
   }
 
+  describe('isEnabled property', () => {
+    it('should be false when not resource given', () => {
+      const wrapper = getWrapper()
+      expect(wrapper.vm.$_delete_items[0].isEnabled({ resources: [] })).toBe(false)
+    })
+    it('should be false when the space is not disabled', () => {
+      const wrapper = getWrapper()
+      expect(
+        wrapper.vm.$_delete_items[0].isEnabled({ resources: [{ id: 1, disabled: false }] })
+      ).toBe(false)
+    })
+    it('should be true when the space is disabled', () => {
+      const wrapper = getWrapper()
+      expect(
+        wrapper.vm.$_delete_items[0].isEnabled({ resources: [{ id: 1, disabled: true }] })
+      ).toBe(true)
+    })
+  })
+
   describe('method "$_delete_trigger"', () => {
     it('should trigger the delete modal window', async () => {
       const wrapper = getWrapper()
       const spyCreateModalStub = jest.spyOn(wrapper.vm, 'createModal')
-      await wrapper.vm.$_delete_trigger({ spaces: [{ id: 1 }] })
+      await wrapper.vm.$_delete_trigger({ resources: [{ id: 1 }] })
 
       expect(spyCreateModalStub).toHaveBeenCalledTimes(1)
     })
+    it('should not trigger the delete modal window without any resource', async () => {
+      const wrapper = getWrapper()
+      const spyCreateModalStub = jest.spyOn(wrapper.vm, 'createModal')
+      await wrapper.vm.$_delete_trigger({ resources: [] })
+
+      expect(spyCreateModalStub).toHaveBeenCalledTimes(0)
+    })
   })
 
   describe('method "$_delete_deleteSpace"', () => {
diff --git a/packages/web-app-files/tests/unit/mixins/spaces/disable.spec.js b/packages/web-app-files/tests/unit/mixins/spaces/disable.spec.js
new file mode 100644
index 00000000000..d23eaef8f1c
--- /dev/null
+++ b/packages/web-app-files/tests/unit/mixins/spaces/disable.spec.js
@@ -0,0 +1,115 @@
+import Vuex from 'vuex'
+import { createStore } from 'vuex-extensions'
+import { mount, createLocalVue } from '@vue/test-utils'
+import disable from '@files/src/mixins/spaces/actions/disable.js'
+import { createLocationSpaces } from '../../../../src/router'
+import mockAxios from 'jest-mock-axios'
+
+const localVue = createLocalVue()
+localVue.use(Vuex)
+
+describe('disable', () => {
+  const Component = {
+    render() {},
+    mixins: [disable]
+  }
+
+  function getWrapper() {
+    return mount(Component, {
+      localVue,
+      mocks: {
+        $router: {
+          currentRoute: createLocationSpaces('files-spaces-projects'),
+          resolve: (r) => {
+            return { href: r.name }
+          }
+        },
+        $gettext: jest.fn()
+      },
+      store: createStore(Vuex.Store, {
+        actions: {
+          createModal: jest.fn(),
+          hideModal: jest.fn(),
+          showMessage: jest.fn(),
+          setModalInputErrorMessage: jest.fn()
+        },
+        getters: {
+          configuration: () => ({
+            server: 'https://example.com'
+          }),
+          getToken: () => 'token'
+        },
+        modules: {
+          Files: {
+            namespaced: true,
+            mutations: {
+              UPDATE_RESOURCE_FIELD: jest.fn()
+            }
+          }
+        }
+      })
+    })
+  }
+
+  describe('isEnabled property', () => {
+    it('should be false when no resource given', () => {
+      const wrapper = getWrapper()
+      expect(wrapper.vm.$_disable_items[0].isEnabled({ resources: [] })).toBe(false)
+    })
+    it('should be true when the space is not disabled', () => {
+      const wrapper = getWrapper()
+      expect(
+        wrapper.vm.$_disable_items[0].isEnabled({ resources: [{ id: 1, disabled: false }] })
+      ).toBe(true)
+    })
+    it('should be false when the space is disabled', () => {
+      const wrapper = getWrapper()
+      expect(
+        wrapper.vm.$_disable_items[0].isEnabled({ resources: [{ id: 1, disabled: true }] })
+      ).toBe(false)
+    })
+  })
+
+  describe('method "$_disable_trigger"', () => {
+    it('should trigger the disable modal window', async () => {
+      const wrapper = getWrapper()
+      const spyCreateModalStub = jest.spyOn(wrapper.vm, 'createModal')
+      await wrapper.vm.$_disable_trigger({ resources: [{ id: 1 }] })
+
+      expect(spyCreateModalStub).toHaveBeenCalledTimes(1)
+    })
+    it('should not trigger the disable modal window without any resource', async () => {
+      const wrapper = getWrapper()
+      const spyCreateModalStub = jest.spyOn(wrapper.vm, 'createModal')
+      await wrapper.vm.$_disable_trigger({ resources: [] })
+
+      expect(spyCreateModalStub).toHaveBeenCalledTimes(0)
+    })
+  })
+
+  describe('method "$_disable_disableSpace"', () => {
+    it('should hide the modal on success', async () => {
+      mockAxios.request.mockImplementationOnce(() => {
+        return Promise.resolve()
+      })
+
+      const wrapper = getWrapper()
+      const hideModalStub = jest.spyOn(wrapper.vm, 'hideModal')
+      await wrapper.vm.$_disable_disableSpace(1)
+
+      expect(hideModalStub).toHaveBeenCalledTimes(1)
+    })
+
+    it('should show message on error', async () => {
+      mockAxios.request.mockImplementationOnce(() => {
+        return Promise.reject(new Error())
+      })
+
+      const wrapper = getWrapper()
+      const showMessageStub = jest.spyOn(wrapper.vm, 'showMessage')
+      await wrapper.vm.$_disable_disableSpace(1)
+
+      expect(showMessageStub).toHaveBeenCalledTimes(1)
+    })
+  })
+})
diff --git a/packages/web-app-files/tests/unit/mixins/spaces/editDescription.spec.js b/packages/web-app-files/tests/unit/mixins/spaces/editDescription.spec.js
new file mode 100644
index 00000000000..37317b9fd3f
--- /dev/null
+++ b/packages/web-app-files/tests/unit/mixins/spaces/editDescription.spec.js
@@ -0,0 +1,93 @@
+import Vuex from 'vuex'
+import { createStore } from 'vuex-extensions'
+import { mount, createLocalVue } from '@vue/test-utils'
+import EditDescription from '@files/src/mixins/spaces/actions/editDescription.js'
+import { createLocationSpaces } from '../../../../src/router'
+import mockAxios from 'jest-mock-axios'
+
+const localVue = createLocalVue()
+localVue.use(Vuex)
+
+describe('editDescription', () => {
+  const Component = {
+    render() {},
+    mixins: [EditDescription]
+  }
+
+  function getWrapper() {
+    return mount(Component, {
+      localVue,
+      mocks: {
+        $router: {
+          currentRoute: createLocationSpaces('files-spaces-projects'),
+          resolve: (r) => {
+            return { href: r.name }
+          }
+        },
+        $gettext: jest.fn()
+      },
+      store: createStore(Vuex.Store, {
+        actions: {
+          createModal: jest.fn(),
+          hideModal: jest.fn(),
+          showMessage: jest.fn()
+        },
+        getters: {
+          configuration: () => ({
+            server: 'https://example.com'
+          }),
+          getToken: () => 'token'
+        },
+        modules: {
+          Files: {
+            namespaced: true,
+            mutations: {
+              UPDATE_RESOURCE_FIELD: jest.fn()
+            }
+          }
+        }
+      })
+    })
+  }
+
+  describe('method "$_editDescription_trigger"', () => {
+    it('should trigger the editDescription modal window with one resource', async () => {
+      const wrapper = getWrapper()
+      const spyCreateModalStub = jest.spyOn(wrapper.vm, 'createModal')
+      await wrapper.vm.$_editDescription_trigger({ resources: [{ id: 1 }] })
+
+      expect(spyCreateModalStub).toHaveBeenCalledTimes(1)
+    })
+    it('should not trigger the editDescription modal window with no resource', async () => {
+      const wrapper = getWrapper()
+      const spyCreateModalStub = jest.spyOn(wrapper.vm, 'createModal')
+      await wrapper.vm.$_editDescription_trigger({ resources: [] })
+
+      expect(spyCreateModalStub).toHaveBeenCalledTimes(0)
+    })
+  })
+
+  describe('method "$_editDescription_editDescriptionSpace"', () => {
+    it('should hide the modal on success', async () => {
+      mockAxios.request.mockImplementationOnce(() => {
+        return Promise.resolve()
+      })
+      const wrapper = getWrapper()
+      const hideModalStub = jest.spyOn(wrapper.vm, 'hideModal')
+      await wrapper.vm.$_editDescription_editDescriptionSpace(1)
+
+      expect(hideModalStub).toHaveBeenCalledTimes(1)
+    })
+
+    it('should show message on error', async () => {
+      mockAxios.request.mockImplementationOnce(() => {
+        return Promise.reject(new Error())
+      })
+      const wrapper = getWrapper()
+      const showMessageStub = jest.spyOn(wrapper.vm, 'showMessage')
+      await wrapper.vm.$_editDescription_editDescriptionSpace(1)
+
+      expect(showMessageStub).toHaveBeenCalledTimes(1)
+    })
+  })
+})
diff --git a/packages/web-app-files/tests/unit/mixins/spaces/rename.spec.js b/packages/web-app-files/tests/unit/mixins/spaces/rename.spec.js
index d052a5e1b26..2b1e4b8822d 100644
--- a/packages/web-app-files/tests/unit/mixins/spaces/rename.spec.js
+++ b/packages/web-app-files/tests/unit/mixins/spaces/rename.spec.js
@@ -55,10 +55,17 @@ describe('rename', () => {
     it('should trigger the rename modal window', async () => {
       const wrapper = getWrapper()
       const spyCreateModalStub = jest.spyOn(wrapper.vm, 'createModal')
-      await wrapper.vm.$_rename_trigger({ spaces: [{ id: 1, name: 'renamed space' }] })
+      await wrapper.vm.$_rename_trigger({ resources: [{ id: 1, name: 'renamed space' }] })
 
       expect(spyCreateModalStub).toHaveBeenCalledTimes(1)
     })
+    it('should not trigger the rename modal window without any resource', async () => {
+      const wrapper = getWrapper()
+      const spyCreateModalStub = jest.spyOn(wrapper.vm, 'createModal')
+      await wrapper.vm.$_rename_trigger({ resources: [] })
+
+      expect(spyCreateModalStub).toHaveBeenCalledTimes(0)
+    })
   })
 
   describe('method "$_rename_checkName"', () => {
diff --git a/packages/web-app-files/tests/unit/mixins/spaces/restore.spec.js b/packages/web-app-files/tests/unit/mixins/spaces/restore.spec.js
new file mode 100644
index 00000000000..484fcaec5d5
--- /dev/null
+++ b/packages/web-app-files/tests/unit/mixins/spaces/restore.spec.js
@@ -0,0 +1,115 @@
+import Vuex from 'vuex'
+import { createStore } from 'vuex-extensions'
+import { mount, createLocalVue } from '@vue/test-utils'
+import restore from '@files/src/mixins/spaces/actions/restore.js'
+import { createLocationSpaces } from '../../../../src/router'
+import mockAxios from 'jest-mock-axios'
+
+const localVue = createLocalVue()
+localVue.use(Vuex)
+
+describe('restore', () => {
+  const Component = {
+    render() {},
+    mixins: [restore]
+  }
+
+  function getWrapper() {
+    return mount(Component, {
+      localVue,
+      mocks: {
+        $router: {
+          currentRoute: createLocationSpaces('files-spaces-projects'),
+          resolve: (r) => {
+            return { href: r.name }
+          }
+        },
+        $gettext: jest.fn()
+      },
+      store: createStore(Vuex.Store, {
+        actions: {
+          createModal: jest.fn(),
+          hideModal: jest.fn(),
+          showMessage: jest.fn(),
+          setModalInputErrorMessage: jest.fn()
+        },
+        getters: {
+          configuration: () => ({
+            server: 'https://example.com'
+          }),
+          getToken: () => 'token'
+        },
+        modules: {
+          Files: {
+            namespaced: true,
+            mutations: {
+              UPDATE_RESOURCE_FIELD: jest.fn()
+            }
+          }
+        }
+      })
+    })
+  }
+
+  describe('isEnabled property', () => {
+    it('should be false when no resource given', () => {
+      const wrapper = getWrapper()
+      expect(wrapper.vm.$_restore_items[0].isEnabled({ resources: [] })).toBe(false)
+    })
+    it('should be false when the space is not disabled', () => {
+      const wrapper = getWrapper()
+      expect(
+        wrapper.vm.$_restore_items[0].isEnabled({ resources: [{ id: 1, disabled: false }] })
+      ).toBe(false)
+    })
+    it('should be true when the space is disabled', () => {
+      const wrapper = getWrapper()
+      expect(
+        wrapper.vm.$_restore_items[0].isEnabled({ resources: [{ id: 1, disabled: true }] })
+      ).toBe(true)
+    })
+  })
+
+  describe('method "$_restore_trigger"', () => {
+    it('should trigger the restore modal window', async () => {
+      const wrapper = getWrapper()
+      const spyCreateModalStub = jest.spyOn(wrapper.vm, 'createModal')
+      await wrapper.vm.$_restore_trigger({ resources: [{ id: 1 }] })
+
+      expect(spyCreateModalStub).toHaveBeenCalledTimes(1)
+    })
+    it('should not trigger the restore modal window without any resource', async () => {
+      const wrapper = getWrapper()
+      const spyCreateModalStub = jest.spyOn(wrapper.vm, 'createModal')
+      await wrapper.vm.$_restore_trigger({ resources: [] })
+
+      expect(spyCreateModalStub).toHaveBeenCalledTimes(0)
+    })
+  })
+
+  describe('method "$_restore_restoreSpace"', () => {
+    it('should hide the modal on success', async () => {
+      mockAxios.request.mockImplementationOnce(() => {
+        return Promise.resolve()
+      })
+
+      const wrapper = getWrapper()
+      const hideModalStub = jest.spyOn(wrapper.vm, 'hideModal')
+      await wrapper.vm.$_restore_restoreSpace(1, 'renamed space')
+
+      expect(hideModalStub).toHaveBeenCalledTimes(1)
+    })
+
+    it('should show message on error', async () => {
+      mockAxios.request.mockImplementationOnce(() => {
+        return Promise.reject(new Error())
+      })
+
+      const wrapper = getWrapper()
+      const showMessageStub = jest.spyOn(wrapper.vm, 'showMessage')
+      await wrapper.vm.$_restore_restoreSpace(1)
+
+      expect(showMessageStub).toHaveBeenCalledTimes(1)
+    })
+  })
+})
diff --git a/packages/web-app-files/tests/unit/mixins/spaces/showDetails.spec.js b/packages/web-app-files/tests/unit/mixins/spaces/showDetails.spec.js
new file mode 100644
index 00000000000..2faa986bcb7
--- /dev/null
+++ b/packages/web-app-files/tests/unit/mixins/spaces/showDetails.spec.js
@@ -0,0 +1,70 @@
+import Vuex from 'vuex'
+import { createStore } from 'vuex-extensions'
+import { mount, createLocalVue } from '@vue/test-utils'
+import ShowDetails from '@files/src/mixins/spaces/actions/showDetails.js'
+import { createLocationSpaces } from '../../../../src/router'
+
+const localVue = createLocalVue()
+localVue.use(Vuex)
+
+describe('showDetails', () => {
+  const Component = {
+    render() {},
+    mixins: [ShowDetails]
+  }
+
+  function getWrapper() {
+    return mount(Component, {
+      localVue,
+      mocks: {
+        $router: {
+          currentRoute: createLocationSpaces('files-spaces-projects'),
+          resolve: (r) => {
+            return { href: r.name }
+          }
+        },
+        $gettext: jest.fn()
+      },
+      store: createStore(Vuex.Store, {
+        modules: {
+          Files: {
+            namespaced: true,
+            mutations: {
+              SET_FILE_SELECTION: jest.fn()
+            },
+            modules: {
+              sidebar: {
+                namespaced: true,
+                actions: {
+                  close: jest.fn(),
+                  open: jest.fn()
+                }
+              }
+            }
+          }
+        }
+      })
+    })
+  }
+
+  describe('method "$_showDetails_trigger"', () => {
+    it('should trigger the sidebar for one resource', async () => {
+      const wrapper = getWrapper()
+      const setSelectionStub = jest.spyOn(wrapper.vm, 'SET_FILE_SELECTION')
+      const openSidebarStub = jest.spyOn(wrapper.vm, 'openSidebar')
+      await wrapper.vm.$_showDetails_trigger({ resources: [{ id: 1 }] })
+
+      expect(setSelectionStub).toHaveBeenCalledTimes(1)
+      expect(openSidebarStub).toHaveBeenCalledTimes(1)
+    })
+    it('should not trigger the sidebar without any resource', async () => {
+      const wrapper = getWrapper()
+      const setSelectionStub = jest.spyOn(wrapper.vm, 'SET_FILE_SELECTION')
+      const openSidebarStub = jest.spyOn(wrapper.vm, 'openSidebar')
+      await wrapper.vm.$_showDetails_trigger({ resources: [] })
+
+      expect(setSelectionStub).toHaveBeenCalledTimes(0)
+      expect(openSidebarStub).toHaveBeenCalledTimes(0)
+    })
+  })
+})
diff --git a/packages/web-app-files/tests/unit/views/spaces/__snapshots__/Projects.spec.js.snap b/packages/web-app-files/tests/unit/views/spaces/__snapshots__/Projects.spec.js.snap
index 94c49e2c834..a02fb2060fd 100644
--- a/packages/web-app-files/tests/unit/views/spaces/__snapshots__/Projects.spec.js.snap
+++ b/packages/web-app-files/tests/unit/views/spaces/__snapshots__/Projects.spec.js.snap
@@ -27,12 +27,15 @@ exports[`Spaces component should list spaces 1`] = `
                 <div id="space-context-drop-1" class="oc-drop oc-box-shadow-medium oc-rounded" options="[object Object]">
                   <div class="oc-card oc-card-body oc-rounded oc-background-highlight oc-p-s">
                     <ul class="oc-list oc-files-context-actions">
-                      <li class="oc-spaces-context-action oc-py-xs oc-px-s"><button class="oc-button oc-button-m oc-button-justify-content-left oc-button-gap-m oc-button-passive oc-button-passive-raw"><span class="oc-icon oc-icon-m oc-icon-passive"><!----></span>
+                      <li class="oc-files-context-action oc-px-s"><button class="oc-button oc-button-m oc-button-justify-content-left oc-button-gap-m oc-button-passive oc-button-passive-raw"><span class="oc-flex oc-icon oc-icon-m oc-icon-passive"><!----></span>
                           Rename
                         </button></li>
-                      <li class="oc-spaces-context-action oc-py-xs oc-px-s"><button class="oc-button oc-button-m oc-button-justify-content-left oc-button-gap-m oc-button-passive oc-button-passive-raw"><span class="oc-icon oc-icon-m oc-icon-passive"><!----></span>
+                      <li class="oc-files-context-action oc-px-s"><button class="oc-button oc-button-m oc-button-justify-content-left oc-button-gap-m oc-button-passive oc-button-passive-raw"><span class="oc-flex oc-icon oc-icon-m oc-icon-passive"><!----></span>
                           Disable
                         </button></li>
+                      <li class="oc-files-context-action oc-px-s"><button class="oc-button oc-button-m oc-button-justify-content-left oc-button-gap-m oc-button-passive oc-button-passive-raw"><span class="oc-flex oc-icon oc-icon-m oc-icon-passive"><!----></span>
+                          Details
+                        </button></li>
                     </ul>
                   </div>
                 </div>
diff --git a/packages/web-client/src/index.ts b/packages/web-client/src/index.ts
index eb99d411c9e..7adc7b356f9 100644
--- a/packages/web-client/src/index.ts
+++ b/packages/web-client/src/index.ts
@@ -4,7 +4,9 @@ import {
   MeDrivesApi,
   Drive,
   DrivesApiFactory,
-  CollectionOfDrives
+  CollectionOfDrives,
+  UserApiFactory,
+  User
 } from './generated'
 
 export interface Graph {
@@ -15,6 +17,9 @@ export interface Graph {
     updateDrive: (id: string, drive: Drive, options: any) => AxiosPromise<Drive>
     deleteDrive: (id: string, ifMatch: string, options: any) => AxiosPromise<void>
   }
+  users: {
+    getUser: (userId: string) => AxiosPromise<User>
+  }
 }
 
 const graph = (baseURI: string, axiosClient: AxiosInstance): Graph => {
@@ -24,6 +29,7 @@ const graph = (baseURI: string, axiosClient: AxiosInstance): Graph => {
   })
 
   const meDrivesApi = new MeDrivesApi(config, config.basePath, axiosClient)
+  const userApiFactory = UserApiFactory(config, config.basePath, axiosClient)
   const drivesApiFactory = DrivesApiFactory(config, config.basePath, axiosClient)
 
   return {
@@ -36,6 +42,9 @@ const graph = (baseURI: string, axiosClient: AxiosInstance): Graph => {
         drivesApiFactory.updateDrive(id, drive, options),
       deleteDrive: (id: string, ifMatch: string, options: any): AxiosPromise<void> =>
         drivesApiFactory.deleteDrive(id, ifMatch, options)
+    },
+    users: {
+      getUser: (userId: string) => userApiFactory.getUser(userId)
     }
   }
 }
diff --git a/packages/web-runtime/tests/unit/components/Topbar/__snapshots__/UserMenu.spec.js.snap b/packages/web-runtime/tests/unit/components/Topbar/__snapshots__/UserMenu.spec.js.snap
index cf3998fea82..88547f37df8 100644
--- a/packages/web-runtime/tests/unit/components/Topbar/__snapshots__/UserMenu.spec.js.snap
+++ b/packages/web-runtime/tests/unit/components/Topbar/__snapshots__/UserMenu.spec.js.snap
@@ -29,7 +29,8 @@ exports[`User Menu component when basic quota is set renders a danger quota prog
       <li class="storage-wrapper oc-pl-s">
         <oc-icon-stub name="cloud" filltype="line" accessiblelabel="" type="span" size="medium" variation="passive" color="" class="oc-p-xs"></oc-icon-stub>
         <div class="storage-wrapper-text">
-          <p class="oc-my-rm"><span>Personal storage (91.0% used)</span> <br> <span class="oc-text-small">910 B of 1 kB used</span></p> <progress max="100" aria-valuemax="100" aria-valuenow="91" aria-busy="true" aria-valuemin="0" tabindex="-1" class="oc-progress oc-progress-small oc-progress-danger" value="91"></progress>
+          <p class="oc-my-rm"><span>Personal storage (91.0% used)</span> <br> <span class="oc-text-small">910 B of 1 kB used</span></p>
+          <oc-progress-stub value="91" max="100" size="small" variation="danger"></oc-progress-stub>
         </div>
       </li>
     </oc-list-stub>
@@ -130,7 +131,8 @@ exports[`User Menu component when quota and no email is set renders a navigation
       <li class="storage-wrapper oc-pl-s">
         <oc-icon-stub name="cloud" filltype="line" accessiblelabel="" type="span" size="medium" variation="passive" color="" class="oc-p-xs"></oc-icon-stub>
         <div class="storage-wrapper-text">
-          <p class="oc-my-rm"><span>Personal storage (30.0% used)</span> <br> <span class="oc-text-small">300 B of 1 kB used</span></p> <progress max="100" aria-valuemax="100" aria-valuenow="30" aria-busy="true" aria-valuemin="0" tabindex="-1" class="oc-progress oc-progress-small oc-progress-primary" value="30"></progress>
+          <p class="oc-my-rm"><span>Personal storage (30.0% used)</span> <br> <span class="oc-text-small">300 B of 1 kB used</span></p>
+          <oc-progress-stub value="30" max="100" size="small" variation="primary"></oc-progress-stub>
         </div>
       </li>
     </oc-list-stub>
@@ -167,7 +169,8 @@ exports[`User Menu component when quota is above 80% and below 90% renders a war
       <li class="storage-wrapper oc-pl-s">
         <oc-icon-stub name="cloud" filltype="line" accessiblelabel="" type="span" size="medium" variation="passive" color="" class="oc-p-xs"></oc-icon-stub>
         <div class="storage-wrapper-text">
-          <p class="oc-my-rm"><span>Personal storage (81.0% used)</span> <br> <span class="oc-text-small">810 B of 1 kB used</span></p> <progress max="100" aria-valuemax="100" aria-valuenow="81" aria-busy="true" aria-valuemin="0" tabindex="-1" class="oc-progress oc-progress-small oc-progress-warning" value="81"></progress>
+          <p class="oc-my-rm"><span>Personal storage (81.0% used)</span> <br> <span class="oc-text-small">810 B of 1 kB used</span></p>
+          <oc-progress-stub value="81" max="100" size="small" variation="warning"></oc-progress-stub>
         </div>
       </li>
     </oc-list-stub>
@@ -204,7 +207,8 @@ exports[`User Menu component when quota is below 80% renders a primary quota pro
       <li class="storage-wrapper oc-pl-s">
         <oc-icon-stub name="cloud" filltype="line" accessiblelabel="" type="span" size="medium" variation="passive" color="" class="oc-p-xs"></oc-icon-stub>
         <div class="storage-wrapper-text">
-          <p class="oc-my-rm"><span>Personal storage (30.0% used)</span> <br> <span class="oc-text-small">300 B of 1 kB used</span></p> <progress max="100" aria-valuemax="100" aria-valuenow="30" aria-busy="true" aria-valuemin="0" tabindex="-1" class="oc-progress oc-progress-small oc-progress-primary" value="30"></progress>
+          <p class="oc-my-rm"><span>Personal storage (30.0% used)</span> <br> <span class="oc-text-small">300 B of 1 kB used</span></p>
+          <oc-progress-stub value="30" max="100" size="small" variation="primary"></oc-progress-stub>
         </div>
       </li>
     </oc-list-stub>
diff --git a/tests/unit/stubs/index.js b/tests/unit/stubs/index.js
index bf488c93235..af6f74c8983 100644
--- a/tests/unit/stubs/index.js
+++ b/tests/unit/stubs/index.js
@@ -15,5 +15,6 @@ export default {
   'oc-img': true,
   'oc-page-size': true,
   'router-link': true,
-  'portal-target': true
+  'portal-target': true,
+  'oc-progress': true
 }