diff --git a/app/assets/javascripts/organizations/shared/constants.js b/app/assets/javascripts/organizations/shared/constants.js
index c0661e79de97450dbb6ffb73dc927422f900adaf..b65ebfd06ce8b5be2c15b0c2f372f600234f2c7c 100644
--- a/app/assets/javascripts/organizations/shared/constants.js
+++ b/app/assets/javascripts/organizations/shared/constants.js
@@ -9,6 +9,15 @@ export const ORGANIZATION_ROOT_ROUTE_NAME = 'root';
 export const ACCESS_LEVEL_DEFAULT = 'default';
 export const ACCESS_LEVEL_OWNER = 'owner';
 
+// Matches `app/graphql/types/organizations/organization_user_access_level_enum.rb
+export const ACCESS_LEVEL_DEFAULT_STRING = 'DEFAULT';
+export const ACCESS_LEVEL_OWNER_STRING = 'OWNER';
+
+export const ACCESS_LEVEL_LABEL = {
+  [ACCESS_LEVEL_DEFAULT_STRING]: __('User'),
+  [ACCESS_LEVEL_OWNER_STRING]: __('Owner'),
+};
+
 export const FORM_FIELD_NAME = 'name';
 export const FORM_FIELD_ID = 'id';
 export const FORM_FIELD_PATH = 'path';
diff --git a/app/assets/javascripts/organizations/users/components/user_details_drawer.vue b/app/assets/javascripts/organizations/users/components/user_details_drawer.vue
new file mode 100644
index 0000000000000000000000000000000000000000..6aa5218db18fc6b4ed92b73f4be31f106bc7fc65
--- /dev/null
+++ b/app/assets/javascripts/organizations/users/components/user_details_drawer.vue
@@ -0,0 +1,160 @@
+<script>
+import { GlCollapsibleListbox, GlDrawer, GlTooltipDirective } from '@gitlab/ui';
+import UserAvatar from '~/vue_shared/components/users_table/user_avatar.vue';
+import {
+  ACCESS_LEVEL_DEFAULT_STRING,
+  ACCESS_LEVEL_LABEL,
+  ACCESS_LEVEL_OWNER_STRING,
+} from '~/organizations/shared/constants';
+import { getContentWrapperHeight } from '~/lib/utils/dom_utils';
+import { DRAWER_Z_INDEX } from '~/lib/utils/constants';
+import { s__ } from '~/locale';
+import organizationUserUpdateMutation from '~/organizations/users/graphql/mutations/organization_user_update.mutation.graphql';
+import { createAlert } from '~/alert';
+
+export default {
+  name: 'UserDetailsDrawer',
+  components: {
+    GlCollapsibleListbox,
+    GlDrawer,
+    UserAvatar,
+  },
+  directives: {
+    GlTooltip: GlTooltipDirective,
+  },
+  inject: ['paths'],
+  i18n: {
+    title: s__('Organization|Organization user details'),
+    roleListboxLabel: s__('Organization|Organization role'),
+    disabledRoleListboxTooltipText: s__('Organization|Organizations must have at least one owner.'),
+    errorMessage: s__(
+      'Organization|An error occurred updating the organization role. Please try again.',
+    ),
+    successMessage: s__('Organization|Organization role was updated successfully.'),
+  },
+  roleListboxItems: [
+    {
+      text: ACCESS_LEVEL_LABEL[ACCESS_LEVEL_DEFAULT_STRING],
+      value: ACCESS_LEVEL_DEFAULT_STRING,
+    },
+    {
+      text: ACCESS_LEVEL_LABEL[ACCESS_LEVEL_OWNER_STRING],
+      value: ACCESS_LEVEL_OWNER_STRING,
+    },
+  ],
+  props: {
+    user: {
+      type: Object,
+      required: false,
+      default: null,
+    },
+  },
+  data() {
+    return {
+      initialAccessLevel: this.user?.accessLevel.stringValue,
+      selectedAccessLevel: this.user?.accessLevel.stringValue,
+      loading: false,
+    };
+  },
+  computed: {
+    drawerHeaderHeight() {
+      return getContentWrapperHeight();
+    },
+    roleListboxDisabled() {
+      return this.user?.isLastOwner;
+    },
+    roleListboxTooltip() {
+      return this.roleListboxDisabled ? this.$options.i18n.disabledRoleListboxTooltipText : null;
+    },
+  },
+  watch: {
+    user(value) {
+      this.initialAccessLevel = value?.accessLevel.stringValue;
+      this.selectedAccessLevel = value?.accessLevel.stringValue;
+    },
+  },
+  methods: {
+    setLoading(value) {
+      this.loading = value;
+      this.$emit('loading', value);
+    },
+    onUpdateSuccess() {
+      this.initialAccessLevel = this.selectedAccessLevel;
+      this.$toast.show(this.$options.i18n.successMessage);
+      this.$emit('role-change');
+    },
+    async onRoleSelect() {
+      this.setLoading(true);
+
+      try {
+        const {
+          data: {
+            organizationUserUpdate: { errors },
+          },
+        } = await this.$apollo.mutate({
+          mutation: organizationUserUpdateMutation,
+          variables: {
+            input: {
+              id: this.user.gid,
+              accessLevel: this.selectedAccessLevel,
+            },
+          },
+        });
+
+        if (errors.length) {
+          createAlert({ message: errors[0] });
+          return;
+        }
+
+        this.onUpdateSuccess();
+      } catch (error) {
+        createAlert({ message: this.$options.i18n.errorMessage, error, captureError: true });
+      } finally {
+        this.setLoading(false);
+      }
+    },
+    close() {
+      this.$emit('close');
+    },
+  },
+  DRAWER_Z_INDEX,
+};
+</script>
+
+<template>
+  <gl-drawer
+    v-if="user"
+    open
+    header-sticky
+    :header-height="drawerHeaderHeight"
+    :z-index="$options.DRAWER_Z_INDEX"
+    @close="close"
+  >
+    <template #title>
+      <h4 class="gl-m-0">{{ $options.i18n.title }}</h4>
+    </template>
+    <template #default>
+      <div>
+        <user-avatar :user="user" :admin-user-path="paths.adminUser" />
+      </div>
+      <div>
+        <h5>{{ $options.i18n.roleListboxLabel }}</h5>
+        <div
+          v-gl-tooltip="{ disabled: !roleListboxTooltip, title: roleListboxTooltip }"
+          class="gl-rounded-base focus:gl-focus"
+          :tabindex="roleListboxDisabled && 0"
+        >
+          <gl-collapsible-listbox
+            v-model="selectedAccessLevel"
+            block
+            toggle-class="gl-form-input-xl"
+            :disabled="roleListboxDisabled"
+            :items="$options.roleListboxItems"
+            :loading="loading"
+            @select="onRoleSelect"
+          />
+        </div>
+      </div>
+    </template>
+  </gl-drawer>
+</template>
diff --git a/app/assets/javascripts/organizations/users/components/users_view.vue b/app/assets/javascripts/organizations/users/components/users_view.vue
index 1dff17eef946520a040d8adc32cb4d26036ed209..e8a3b307733d0b0cb074a7f1b727098f535aa06e 100644
--- a/app/assets/javascripts/organizations/users/components/users_view.vue
+++ b/app/assets/javascripts/organizations/users/components/users_view.vue
@@ -1,21 +1,19 @@
 <script>
-import {
-  GlLoadingIcon,
-  GlKeysetPagination,
-  GlCollapsibleListbox,
-  GlTooltipDirective,
-} from '@gitlab/ui';
-import { createAlert } from '~/alert';
+import { GlButton, GlLoadingIcon, GlKeysetPagination, GlTooltipDirective } from '@gitlab/ui';
 import UsersTable from '~/vue_shared/components/users_table/users_table.vue';
+import UserDetailsDrawer from '~/organizations/users/components/user_details_drawer.vue';
 import {
   FIELD_NAME,
   FIELD_ORGANIZATION_ROLE,
   FIELD_CREATED_AT,
   FIELD_LAST_ACTIVITY_ON,
 } from '~/vue_shared/components/users_table/constants';
-import { ACCESS_LEVEL_DEFAULT, ACCESS_LEVEL_OWNER } from '~/organizations/shared/constants';
+import {
+  ACCESS_LEVEL_DEFAULT,
+  ACCESS_LEVEL_OWNER,
+  ACCESS_LEVEL_LABEL,
+} from '~/organizations/shared/constants';
 import { __, s__ } from '~/locale';
-import organizationUserUpdateMutation from '../graphql/mutations/organization_user_update.mutation.graphql';
 
 export default {
   name: 'UsersView',
@@ -30,10 +28,11 @@ export default {
     GlTooltip: GlTooltipDirective,
   },
   components: {
+    GlButton,
     GlLoadingIcon,
     GlKeysetPagination,
-    GlCollapsibleListbox,
     UsersTable,
+    UserDetailsDrawer,
   },
   inject: ['paths'],
   roleListboxItems: [
@@ -70,52 +69,22 @@ export default {
   },
   data() {
     return {
-      roleListboxLoadingStates: [],
+      userDetailsDrawerActiveUser: null,
+      userDetailsDrawerLoading: false,
     };
   },
   methods: {
-    async onRoleSelect(accessLevel, user) {
-      this.roleListboxLoadingStates.push(user.gid);
-
-      try {
-        const {
-          data: {
-            organizationUserUpdate: { errors },
-          },
-        } = await this.$apollo.mutate({
-          mutation: organizationUserUpdateMutation,
-          variables: {
-            input: {
-              id: user.gid,
-              accessLevel,
-            },
-          },
-        });
-
-        if (errors.length) {
-          createAlert({ message: errors[0] });
-
-          return;
-        }
-
-        this.$toast.show(this.$options.i18n.successMessage);
-        this.$emit('role-change');
-      } catch (error) {
-        createAlert({ message: this.$options.i18n.errorMessage, error, captureError: true });
-      } finally {
-        this.roleListboxLoadingStates.splice(this.roleListboxLoadingStates.indexOf(user.gid), 1);
-      }
+    setUserDetailsDrawerActiveUser(user) {
+      this.userDetailsDrawerActiveUser = user;
     },
-    roleListboxItemText(accessLevel) {
-      return this.$options.roleListboxItems.find((item) => item.value === accessLevel).text;
+    setUserDetailsDrawerLoading(loading) {
+      this.userDetailsDrawerLoading = loading;
     },
-    isRoleListboxDisabled(user) {
-      return user.isLastOwner;
+    onRoleChange() {
+      this.$emit('role-change');
     },
-    roleListboxTooltip(user) {
-      return this.isRoleListboxDisabled(user)
-        ? this.$options.i18n.disabledRoleListboxTooltipText
-        : null;
+    userAccessLevelLabel(user) {
+      return ACCESS_LEVEL_LABEL[user.accessLevel.stringValue];
     },
   },
 };
@@ -132,26 +101,25 @@ export default {
         :column-widths="$options.usersTable.columnWidths"
       >
         <template #organization-role="{ user }">
-          <div
-            v-gl-tooltip="{ disabled: !roleListboxTooltip(user), title: roleListboxTooltip(user) }"
-            class="gl-rounded-base focus:gl-focus"
-            :tabindex="isRoleListboxDisabled(user) && 0"
+          <gl-button
+            class="gl-block"
+            variant="link"
+            :disabled="userDetailsDrawerLoading"
+            @click="setUserDetailsDrawerActiveUser(user)"
           >
-            <gl-collapsible-listbox
-              :disabled="isRoleListboxDisabled(user)"
-              :selected="user.accessLevel.stringValue"
-              block
-              toggle-class="gl-form-input-xl"
-              :items="$options.roleListboxItems"
-              :loading="roleListboxLoadingStates.includes(user.gid)"
-              @select="onRoleSelect($event, user)"
-            />
-          </div>
+            {{ userAccessLevelLabel(user) }}
+          </gl-button>
         </template>
       </users-table>
       <div class="gl-flex gl-justify-center">
         <gl-keyset-pagination v-bind="pageInfo" @prev="$emit('prev')" @next="$emit('next')" />
       </div>
     </template>
+    <user-details-drawer
+      :user="userDetailsDrawerActiveUser"
+      @loading="setUserDetailsDrawerLoading"
+      @close="setUserDetailsDrawerActiveUser(null)"
+      @role-change="onRoleChange"
+    />
   </div>
 </template>
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index fb91a0863ee9433a8634083a22d4830459bc41ce..c58c9dbdc665e22b010d00ad7338391ad3ab7935 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -38938,6 +38938,9 @@ msgstr ""
 msgid "Organization|Organization successfully created."
 msgstr ""
 
+msgid "Organization|Organization user details"
+msgstr ""
+
 msgid "Organization|Organization visibility level"
 msgstr ""
 
diff --git a/spec/frontend/organizations/users/components/user_details_drawer_spec.js b/spec/frontend/organizations/users/components/user_details_drawer_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..f608d7b0c1e69dbc8d8502f93772f89d948b1db4
--- /dev/null
+++ b/spec/frontend/organizations/users/components/user_details_drawer_spec.js
@@ -0,0 +1,239 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import { GlCollapsibleListbox, GlDrawer } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import organizationUserUpdateResponseWithErrors from 'test_fixtures/graphql/organizations/organization_user_update.mutation.graphql_with_errors.json';
+import organizationUserUpdateResponse from 'test_fixtures/graphql/organizations/organization_user_update.mutation.graphql.json';
+import organizationUserUpdateMutation from '~/organizations/users/graphql/mutations/organization_user_update.mutation.graphql';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { pageInfoMultiplePages } from 'jest/organizations/mock_data';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+import UserDetailsDrawer from '~/organizations/users/components/user_details_drawer.vue';
+import UserAvatar from '~/vue_shared/components/users_table/user_avatar.vue';
+import waitForPromises from 'helpers/wait_for_promises';
+import { createAlert } from '~/alert';
+import {
+  ACCESS_LEVEL_DEFAULT_STRING,
+  ACCESS_LEVEL_OWNER_STRING,
+} from '~/organizations/shared/constants';
+import { MOCK_PATHS, MOCK_USERS_FORMATTED } from '../mock_data';
+
+Vue.use(VueApollo);
+
+jest.mock('~/alert');
+
+describe('UserDetailsDrawer', () => {
+  let wrapper;
+  let mockApollo;
+
+  const mockUser = MOCK_USERS_FORMATTED[0];
+
+  const successfulResponseHandler = jest.fn().mockResolvedValue(organizationUserUpdateResponse);
+  const mockToastShow = jest.fn();
+
+  const findGlDrawer = () => wrapper.findComponent(GlDrawer);
+  const findGlCollapsibleListbox = () => wrapper.findComponent(GlCollapsibleListbox);
+  const findUserAvatar = () => wrapper.findComponent(UserAvatar);
+
+  const selectRole = (value) => findGlCollapsibleListbox().vm.$emit('select', value);
+
+  const createComponent = ({ props = {}, handler = successfulResponseHandler } = {}) => {
+    mockApollo = createMockApollo([[organizationUserUpdateMutation, handler]]);
+
+    wrapper = shallowMount(UserDetailsDrawer, {
+      propsData: {
+        user: mockUser,
+        pageInfo: pageInfoMultiplePages,
+        ...props,
+      },
+      provide: {
+        paths: MOCK_PATHS,
+      },
+      apolloProvider: mockApollo,
+      mocks: {
+        $toast: {
+          show: mockToastShow,
+        },
+      },
+      directives: {
+        GlTooltip: createMockDirective('gl-tooltip'),
+      },
+    });
+  };
+
+  afterEach(() => {
+    mockApollo = null;
+  });
+
+  describe('when there is no active user', () => {
+    it('does not render drawer', () => {
+      createComponent({ props: { user: null } });
+
+      expect(findGlDrawer().exists()).toBe(false);
+    });
+  });
+
+  describe('when there is an active user', () => {
+    beforeEach(() => {
+      createComponent();
+    });
+
+    it('renders open drawer', () => {
+      expect(findGlDrawer().exists()).toBe(true);
+      expect(findGlDrawer().props('open')).toBe(true);
+    });
+
+    it('renders drawer title', () => {
+      expect(findGlDrawer().text()).toContain('Organization user details');
+    });
+
+    it('renders user avatar', () => {
+      expect(findUserAvatar().props()).toMatchObject({
+        user: mockUser,
+        adminUserPath: MOCK_PATHS.adminUser,
+      });
+    });
+
+    it('renders role listbox label', () => {
+      expect(findGlDrawer().text()).toContain('Organization role');
+    });
+
+    it('renders role listbox correct props', () => {
+      expect(findGlCollapsibleListbox().props()).toMatchObject({
+        items: UserDetailsDrawer.roleListboxItems,
+        selected: mockUser.accessLevel.stringValue,
+        disabled: false,
+      });
+    });
+
+    it('does not render disabled listbox tooltip', () => {
+      const tooltipContainer = findGlCollapsibleListbox().element.parentNode;
+      const tooltip = getBinding(tooltipContainer, 'gl-tooltip');
+
+      expect(tooltip.value.disabled).toBe(true);
+      expect(tooltipContainer.getAttribute('tabindex')).toBe(null);
+    });
+
+    describe('when user is last owner of organization', () => {
+      beforeEach(() => {
+        createComponent({
+          props: {
+            loading: false,
+            user: { ...mockUser, isLastOwner: true },
+          },
+        });
+      });
+
+      it('renders listbox as disabled', () => {
+        expect(findGlCollapsibleListbox().props('disabled')).toBe(true);
+      });
+
+      it('renders tooltip and makes element focusable', () => {
+        const tooltipContainer = findGlCollapsibleListbox().element.parentNode;
+        const tooltip = getBinding(tooltipContainer, 'gl-tooltip');
+
+        expect(tooltip.value).toEqual({
+          title: 'Organizations must have at least one owner.',
+          disabled: false,
+        });
+        expect(tooltipContainer.getAttribute('tabindex')).toBe('0');
+      });
+    });
+
+    describe('when selecting new role', () => {
+      beforeEach(() => {
+        createComponent();
+        selectRole(ACCESS_LEVEL_DEFAULT_STRING);
+      });
+
+      it('calls GraphQL mutation with correct variables', () => {
+        expect(successfulResponseHandler).toHaveBeenCalledWith({
+          input: {
+            id: mockUser.gid,
+            accessLevel: ACCESS_LEVEL_DEFAULT_STRING,
+          },
+        });
+      });
+
+      it('sets listbox to loading', () => {
+        expect(findGlCollapsibleListbox().props('loading')).toBe(true);
+      });
+
+      it('emits loading start event', () => {
+        expect(wrapper.emitted('loading')[0]).toEqual([true]);
+      });
+
+      describe('when role update is successful', () => {
+        beforeEach(async () => {
+          await waitForPromises();
+        });
+
+        it('shows toast when GraphQL mutation is successful', () => {
+          expect(mockToastShow).toHaveBeenCalledWith('Organization role was updated successfully.');
+        });
+
+        it('emits role-change event', () => {
+          expect(wrapper.emitted('role-change')).toHaveLength(1);
+        });
+
+        it('emits loading end event', () => {
+          expect(wrapper.emitted('loading')[1]).toEqual([false]);
+        });
+      });
+
+      describe('when role update has a validation error', () => {
+        beforeEach(async () => {
+          const errorResponseHandler = jest
+            .fn()
+            .mockResolvedValue(organizationUserUpdateResponseWithErrors);
+
+          createComponent({
+            handler: errorResponseHandler,
+            props: { user: { ...mockUser, accessLevel: ACCESS_LEVEL_OWNER_STRING } },
+          });
+
+          selectRole(ACCESS_LEVEL_DEFAULT_STRING);
+          await waitForPromises();
+        });
+
+        it('creates an alert', () => {
+          expect(createAlert).toHaveBeenCalledWith({
+            message: 'You cannot change the access of the last owner from the organization',
+          });
+        });
+      });
+
+      describe('when role update has a network error', () => {
+        const error = new Error();
+
+        beforeEach(async () => {
+          const errorResponseHandler = jest.fn().mockRejectedValue(error);
+
+          createComponent({
+            handler: errorResponseHandler,
+            props: { user: { ...mockUser, accessLevel: ACCESS_LEVEL_OWNER_STRING } },
+          });
+
+          selectRole(ACCESS_LEVEL_DEFAULT_STRING);
+          await waitForPromises();
+        });
+
+        it('creates an alert', () => {
+          expect(createAlert).toHaveBeenCalledWith({
+            message: 'An error occurred updating the organization role. Please try again.',
+            error,
+            captureError: true,
+          });
+        });
+      });
+    });
+
+    describe('when drawer is closed', () => {
+      it('emits close event', () => {
+        findGlDrawer().vm.$emit('close');
+
+        expect(wrapper.emitted('close')).toHaveLength(1);
+      });
+    });
+  });
+});
diff --git a/spec/frontend/organizations/users/components/users_view_spec.js b/spec/frontend/organizations/users/components/users_view_spec.js
index bc2635c38d5c94a905b398221ac084b6c6e05d74..919e9f6630311b4d377cb4fe762dbc14320954f1 100644
--- a/spec/frontend/organizations/users/components/users_view_spec.js
+++ b/spec/frontend/organizations/users/components/users_view_spec.js
@@ -1,64 +1,34 @@
-import VueApollo from 'vue-apollo';
-import Vue, { nextTick } from 'vue';
-import { GlLoadingIcon, GlKeysetPagination, GlCollapsibleListbox } from '@gitlab/ui';
-import organizationUserUpdateResponse from 'test_fixtures/graphql/organizations/organization_user_update.mutation.graphql.json';
-import organizationUserUpdateResponseWithErrors from 'test_fixtures/graphql/organizations/organization_user_update.mutation.graphql_with_errors.json';
-import { mountExtended } from 'helpers/vue_test_utils_helper';
+import { GlLoadingIcon, GlKeysetPagination, GlButton } from '@gitlab/ui';
+import { shallowMount, mount } from '@vue/test-utils';
+import UserDetailsDrawer from '~/organizations/users/components/user_details_drawer.vue';
 import UsersView from '~/organizations/users/components/users_view.vue';
 import UsersTable from '~/vue_shared/components/users_table/users_table.vue';
-import organizationUserUpdateMutation from '~/organizations/users/graphql/mutations/organization_user_update.mutation.graphql';
-import createMockApollo from 'helpers/mock_apollo_helper';
-import { createAlert } from '~/alert';
-import waitForPromises from 'helpers/wait_for_promises';
-import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
 import { pageInfoMultiplePages } from 'jest/organizations/mock_data';
+import { ACCESS_LEVEL_LABEL } from '~/organizations/shared/constants';
 import { MOCK_PATHS, MOCK_USERS_FORMATTED } from '../mock_data';
 
-Vue.use(VueApollo);
-
-jest.mock('~/alert');
-
 describe('UsersView', () => {
   let wrapper;
-  let mockApollo;
-
-  const successfulResponseHandler = jest.fn().mockResolvedValue(organizationUserUpdateResponse);
-  const mockToastShow = jest.fn();
 
-  const createComponent = ({ propsData = {}, handler = successfulResponseHandler } = {}) => {
-    mockApollo = createMockApollo([[organizationUserUpdateMutation, handler]]);
-
-    wrapper = mountExtended(UsersView, {
+  const createComponent = ({ mountFn = shallowMount, props = {} } = {}) => {
+    wrapper = mountFn(UsersView, {
       propsData: {
         loading: false,
         users: MOCK_USERS_FORMATTED,
         pageInfo: pageInfoMultiplePages,
-        ...propsData,
+        ...props,
       },
       provide: {
         paths: MOCK_PATHS,
       },
-      apolloProvider: mockApollo,
-      mocks: {
-        $toast: {
-          show: mockToastShow,
-        },
-      },
-      directives: {
-        GlTooltip: createMockDirective('gl-tooltip'),
-      },
     });
   };
 
   const findGlLoading = () => wrapper.findComponent(GlLoadingIcon);
-  const findUsersTable = () => wrapper.findComponent(UsersTable);
+  const findGlButton = () => wrapper.findComponent(GlButton);
   const findGlKeysetPagination = () => wrapper.findComponent(GlKeysetPagination);
-  const findListbox = () => wrapper.findComponent(GlCollapsibleListbox);
-  const listboxSelectOwner = () => findListbox().vm.$emit('select', 'OWNER');
-
-  afterEach(() => {
-    mockApollo = null;
-  });
+  const findUserDetailsDrawer = () => wrapper.findComponent(UserDetailsDrawer);
+  const findUsersTable = () => wrapper.findComponent(UsersTable);
 
   describe.each`
     description                            | loading  | usersData
@@ -67,7 +37,7 @@ describe('UsersView', () => {
     ${'when not loading and has no users'} | ${false} | ${[]}
   `('$description', ({ loading, usersData }) => {
     beforeEach(() => {
-      createComponent({ propsData: { loading, users: usersData } });
+      createComponent({ props: { loading, users: usersData } });
     });
 
     it(`does ${loading ? '' : 'not '}render loading icon`, () => {
@@ -102,142 +72,49 @@ describe('UsersView', () => {
   });
 
   describe('Organization role', () => {
-    it('renders listbox with role options', () => {
-      createComponent();
+    const mockUser = MOCK_USERS_FORMATTED[0];
 
-      expect(wrapper.findComponent(GlCollapsibleListbox).props()).toMatchObject({
-        items: [
-          {
-            text: 'User',
-            value: 'DEFAULT',
-          },
-          {
-            text: 'Owner',
-            value: 'OWNER',
-          },
-        ],
-        selected: MOCK_USERS_FORMATTED[0].accessLevel.stringValue,
-        disabled: false,
-      });
+    beforeEach(() => {
+      createComponent({ mountFn: mount });
     });
 
-    it('does not render tooltip', () => {
-      createComponent();
-
-      const tooltipContainer = findListbox().element.parentNode;
-      const tooltip = getBinding(tooltipContainer, 'gl-tooltip');
+    it("render an organization role button with the user's role", () => {
+      const userAccessLevel = mockUser.accessLevel.stringValue;
 
-      expect(tooltip.value.disabled).toBe(true);
-      expect(tooltipContainer.getAttribute('tabindex')).toBe(null);
+      expect(findGlButton().text()).toBe(ACCESS_LEVEL_LABEL[userAccessLevel]);
     });
 
-    describe('when user is last owner of organization', () => {
-      const [firstUser] = MOCK_USERS_FORMATTED;
-
-      beforeEach(() => {
-        createComponent({
-          propsData: {
-            loading: false,
-            users: [{ ...firstUser, isLastOwner: true }],
-          },
-        });
+    describe('when the organization role button is clicked', () => {
+      beforeEach(async () => {
+        await findGlButton().trigger('click');
       });
 
-      it('renders listbox as disabled', () => {
-        expect(findListbox().props('disabled')).toBe(true);
+      it("sets the user details drawer's active user to selected user", () => {
+        expect(findUserDetailsDrawer().props('user')).toBe(mockUser);
       });
 
-      it('renders tooltip and makes element focusable', () => {
-        const tooltipContainer = findListbox().element.parentNode;
-        const tooltip = getBinding(tooltipContainer, 'gl-tooltip');
+      describe('when the user details drawer is closed', () => {
+        it("reset the user details drawer's active user to null", async () => {
+          await findUserDetailsDrawer().vm.$emit('close');
 
-        expect(tooltip.value).toEqual({
-          title: 'Organizations must have at least one owner.',
-          disabled: false,
+          expect(findGlButton().props('user')).toBeUndefined();
         });
-        expect(tooltipContainer.getAttribute('tabindex')).toBe('0');
       });
     });
 
-    describe('when role is changed', () => {
-      afterEach(async () => {
-        // clean up any unresolved GraphQL mutations
-        await waitForPromises();
-      });
+    describe('when the user details drawer is loading', () => {
+      it('disable the organization role button', async () => {
+        await findUserDetailsDrawer().vm.$emit('loading', true);
 
-      it('calls GraphQL mutation with correct variables', () => {
-        createComponent();
-        listboxSelectOwner();
-
-        expect(successfulResponseHandler).toHaveBeenCalledWith({
-          input: {
-            id: MOCK_USERS_FORMATTED[0].gid,
-            accessLevel: 'OWNER',
-          },
-        });
+        expect(findGlButton().props('disabled')).toBe(true);
       });
+    });
 
-      it('shows dropdown as loading while waiting for GraphQL mutation', async () => {
-        createComponent();
-        listboxSelectOwner();
-
-        await nextTick();
-
-        expect(findListbox().props('loading')).toBe(true);
-      });
-
-      it('shows toast when GraphQL mutation is successful', async () => {
-        createComponent();
-        listboxSelectOwner();
-
-        await waitForPromises();
-
-        expect(mockToastShow).toHaveBeenCalledWith('Organization role was updated successfully.');
-      });
-
-      it('emits role-change event when GraphQL mutation is successful', async () => {
-        createComponent();
-        listboxSelectOwner();
-
-        await waitForPromises();
-
-        expect(wrapper.emitted('role-change')).toEqual([[]]);
-      });
-
-      it('calls createAlert when GraphQL mutation has validation error', async () => {
-        const errorResponseHandler = jest
-          .fn()
-          .mockResolvedValue(organizationUserUpdateResponseWithErrors);
-        createComponent({
-          handler: errorResponseHandler,
-        });
-
-        listboxSelectOwner();
-
-        await waitForPromises();
-
-        expect(createAlert).toHaveBeenCalledWith({
-          message: 'You cannot change the access of the last owner from the organization',
-        });
-      });
-
-      it('calls createAlert when GraphQL mutation has network error', async () => {
-        const error = new Error();
-        const errorResponseHandler = jest.fn().mockRejectedValue(error);
-
-        createComponent({
-          handler: errorResponseHandler,
-        });
-
-        listboxSelectOwner();
-
-        await waitForPromises();
+    describe('when the user role has been changed', () => {
+      it('emits role-change event', async () => {
+        await findUserDetailsDrawer().vm.$emit('role-change');
 
-        expect(createAlert).toHaveBeenCalledWith({
-          message: 'An error occurred updating the organization role. Please try again.',
-          error,
-          captureError: true,
-        });
+        expect(wrapper.emitted('role-change')).toHaveLength(1);
       });
     });
   });