diff --git a/app/assets/javascripts/invite_members/components/invite_modal_base.vue b/app/assets/javascripts/invite_members/components/invite_modal_base.vue
index 04dc1cd376d8a4968ac14df6c598c1b04bec2d17..5fef12f964f80fdf97a4d561ac34647bb377d36a 100644
--- a/app/assets/javascripts/invite_members/components/invite_modal_base.vue
+++ b/app/assets/javascripts/invite_members/components/invite_modal_base.vue
@@ -335,6 +335,15 @@ export default {
             :loading="isLoadingRoles"
             block
           >
+            <template #list-item="{ item }">
+              <div :class="{ 'gl-font-weight-bold': item.memberRoleId }">{{ item.text }}</div>
+              <div
+                v-if="item.description"
+                class="gl-text-gray-700 gl-font-sm gl-pt-1 gl-line-clamp-2"
+              >
+                {{ item.description }}
+              </div>
+            </template>
             <template #footer>
               <manage-roles-dropdown-footer />
             </template>
diff --git a/app/assets/javascripts/members/components/table/max_role.vue b/app/assets/javascripts/members/components/table/max_role.vue
index 92006e73678d78b0a242292f67d33a979d30b193..d6a798d37ce3d3a138208de65baa62a6caf032d8 100644
--- a/app/assets/javascripts/members/components/table/max_role.vue
+++ b/app/assets/javascripts/members/components/table/max_role.vue
@@ -1,5 +1,5 @@
 <script>
-import { GlBadge, GlCollapsibleListbox } from '@gitlab/ui';
+import { GlBadge, GlCollapsibleListbox, GlTooltipDirective } from '@gitlab/ui';
 import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
 // eslint-disable-next-line no-restricted-imports
 import { mapActions } from 'vuex';
@@ -15,10 +15,12 @@ export default {
     GlBadge,
     LdapDropdownFooter: () =>
       import('ee_component/members/components/action_dropdowns/ldap_dropdown_footer.vue'),
-    CustomPermissions: () => import('ee_component/members/components/table/custom_permissions.vue'),
     ManageRolesDropdownFooter: () =>
       import('ee_component/members/components/action_dropdowns/manage_roles_dropdown_footer.vue'),
   },
+  directives: {
+    GlTooltip: GlTooltipDirective,
+  },
   inject: ['namespace', 'group'],
   props: {
     member: {
@@ -98,6 +100,9 @@ export default {
       }
     },
   },
+  i18n: {
+    customRole: s__('MemberRole|Custom role'),
+  },
 };
 </script>
 
@@ -115,7 +120,12 @@ export default {
       @select="handleSelect"
     >
       <template #list-item="{ item }">
-        <span data-testid="access-level-link">{{ item.text }}</span>
+        <div data-testid="access-level-link" :class="{ 'gl-font-weight-bold': item.memberRoleId }">
+          {{ item.text }}
+        </div>
+        <div v-if="item.description" class="gl-text-gray-700 gl-font-sm gl-pt-1 gl-line-clamp-2">
+          {{ item.description }}
+        </div>
       </template>
       <template #footer>
         <ldap-dropdown-footer
@@ -126,12 +136,10 @@ export default {
       </template>
     </gl-collapsible-listbox>
 
-    <gl-badge v-else>{{ member.accessLevel.stringValue }}</gl-badge>
+    <div v-else v-gl-tooltip.ds0="member.accessLevel.description" data-testid="role-text">
+      {{ member.accessLevel.stringValue }}
+    </div>
 
-    <custom-permissions
-      v-if="memberRoleId !== null"
-      :member-role-id="memberRoleId"
-      :custom-permissions="customPermissions"
-    />
+    <gl-badge v-if="memberRoleId" class="gl-mt-3">{{ $options.i18n.customRole }}</gl-badge>
   </div>
 </template>
diff --git a/ee/app/assets/javascripts/invite_members/components/invite_modal_base.vue b/ee/app/assets/javascripts/invite_members/components/invite_modal_base.vue
index d70736ab6debd1d8c94ae19ac24c6a36fe15fa5c..833e9935e2627f0352ed3fea9a5186c5f0b5dcf9 100644
--- a/ee/app/assets/javascripts/invite_members/components/invite_modal_base.vue
+++ b/ee/app/assets/javascripts/invite_members/components/invite_modal_base.vue
@@ -108,9 +108,10 @@ export default {
       update(data) {
         const memberRoles = data?.memberRoles?.nodes || [];
 
-        return memberRoles.map(({ id, name, baseAccessLevel }) => ({
+        return memberRoles.map(({ id, name, description, baseAccessLevel }) => ({
           baseAccessLevel: baseAccessLevel.integerValue,
           name,
+          description,
           memberRoleId: getIdFromGraphQLId(id),
         }));
       },
@@ -133,10 +134,10 @@ export default {
       },
       update(data) {
         const memberRoles = data?.namespace?.memberRoles?.nodes || [];
-
-        return memberRoles.map(({ id, name, baseAccessLevel }) => ({
+        return memberRoles.map(({ id, name, description, baseAccessLevel }) => ({
           baseAccessLevel: baseAccessLevel.integerValue,
           name,
+          description,
           memberRoleId: getIdFromGraphQLId(id),
         }));
       },
diff --git a/ee/app/assets/javascripts/invite_members/graphql/fragments/member_roles.fragment.graphql b/ee/app/assets/javascripts/invite_members/graphql/fragments/member_roles.fragment.graphql
index 2d6c868263bc7caa589708ec3ce07140c8ef819f..cfa76711489058c89dd8de22330bfcc5c27a24b1 100644
--- a/ee/app/assets/javascripts/invite_members/graphql/fragments/member_roles.fragment.graphql
+++ b/ee/app/assets/javascripts/invite_members/graphql/fragments/member_roles.fragment.graphql
@@ -6,6 +6,7 @@ fragment MemberRoles on MemberRoleConnection {
     }
     id
     name
+    description
     enabledPermissions {
       nodes {
         name
diff --git a/ee/app/assets/javascripts/members/components/table/custom_permissions.vue b/ee/app/assets/javascripts/members/components/table/custom_permissions.vue
deleted file mode 100644
index 78432678c7864313357f81c165a4756d40e5c71e..0000000000000000000000000000000000000000
--- a/ee/app/assets/javascripts/members/components/table/custom_permissions.vue
+++ /dev/null
@@ -1,61 +0,0 @@
-<script>
-import { GlBadge } from '@gitlab/ui';
-import { convertToGraphQLId } from '~/graphql_shared/utils';
-import { TYPENAME_MEMBER_ROLE } from '~/graphql_shared/constants';
-import { s__ } from '~/locale';
-import * as Sentry from '~/sentry/sentry_browser_wrapper';
-import enabledMemberRolePermissions from '../../graphql/queries/enabled_member_role_permissions.query.graphql';
-
-export default {
-  components: {
-    GlBadge,
-  },
-  props: {
-    memberRoleId: {
-      type: Number,
-      required: true,
-    },
-    customPermissions: {
-      type: Array,
-      required: true,
-    },
-  },
-  data() {
-    return {
-      enabledPermissions: [],
-      permissions: this.customPermissions,
-    };
-  },
-  watch: {
-    memberRoleId(newMemberRoleId) {
-      this.$apollo.addSmartQuery('enabledPermissions', {
-        query: enabledMemberRolePermissions,
-        variables: () => ({
-          id: convertToGraphQLId(TYPENAME_MEMBER_ROLE, newMemberRoleId),
-        }),
-        update: (data) => data.memberRole.enabledPermissions,
-        result: () => {
-          this.permissions = this.enabledPermissions.nodes;
-        },
-        error: (error) => {
-          Sentry.captureException(error);
-        },
-      });
-    },
-  },
-  i18n: {
-    title: s__('MemberRole|Custom permissions:'),
-  },
-};
-</script>
-
-<template>
-  <div
-    class="gl-mt-3 gl-display-flex gl-flex-wrap gl-justify-content-end gl-lg-justify-content-start gl-gap-2"
-  >
-    <span data-testid="title">{{ $options.i18n.title }}</span>
-    <gl-badge v-for="permission in permissions" :key="permission.name" variant="success" size="sm">
-      {{ permission.name }}
-    </gl-badge>
-  </div>
-</template>
diff --git a/ee/app/assets/javascripts/members/graphql/queries/enabled_member_role_permissions.query.graphql b/ee/app/assets/javascripts/members/graphql/queries/enabled_member_role_permissions.query.graphql
deleted file mode 100644
index 0981e5136e7fc6ba0dcc0d41671653dd19315768..0000000000000000000000000000000000000000
--- a/ee/app/assets/javascripts/members/graphql/queries/enabled_member_role_permissions.query.graphql
+++ /dev/null
@@ -1,11 +0,0 @@
-query enabledMemberRolePermissions($id: MemberRoleID!) {
-  memberRole(id: $id) {
-    id
-    enabledPermissions {
-      nodes {
-        name
-        value
-      }
-    }
-  }
-}
diff --git a/ee/app/assets/javascripts/members/utils.js b/ee/app/assets/javascripts/members/utils.js
index 2467c9d8505f4c713004eb8f223a5650d2469d37..df494b09d8c4fb021cc85308e31cb6203065f90a 100644
--- a/ee/app/assets/javascripts/members/utils.js
+++ b/ee/app/assets/javascripts/members/utils.js
@@ -1,6 +1,5 @@
-import { groupBy, uniqueId } from 'lodash';
-import { ACCESS_LEVEL_LABELS } from '~/access_level/constants';
-import { __, s__, sprintf } from '~/locale';
+import { uniqueId } from 'lodash';
+import { __, s__ } from '~/locale';
 import {
   generateBadges as CEGenerateBadges,
   roleDropdownItems as CERoleDropdownItems,
@@ -60,21 +59,15 @@ export const roleDropdownItems = ({ validRoles, customRoles }) => {
 
   const { flatten: staticRoleDropdownItems } = CERoleDropdownItems({ validRoles });
 
-  const customRoleDropdownItems = customRoles.map(({ baseAccessLevel, name, memberRoleId }) => ({
-    accessLevel: baseAccessLevel,
-    memberRoleId,
-    text: name,
-    value: uniqueId('role-custom-'),
-  }));
-
-  const customRoleDropdownGroups = Object.entries(
-    groupBy(customRoleDropdownItems, 'accessLevel'),
-  ).map(([accessLevel, options]) => ({
-    text: sprintf(s__('MemberRole|Custom roles based on %{accessLevel}'), {
-      accessLevel: ACCESS_LEVEL_LABELS[accessLevel],
+  const customRoleDropdownItems = customRoles.map(
+    ({ baseAccessLevel, name, memberRoleId, description }) => ({
+      accessLevel: baseAccessLevel,
+      memberRoleId,
+      text: name,
+      value: uniqueId('role-custom-'),
+      description,
     }),
-    options,
-  }));
+  );
 
   return {
     flatten: [...staticRoleDropdownItems, ...customRoleDropdownItems],
@@ -83,7 +76,10 @@ export const roleDropdownItems = ({ validRoles, customRoles }) => {
         text: s__('MemberRole|Standard roles'),
         options: staticRoleDropdownItems,
       },
-      ...customRoleDropdownGroups,
+      {
+        text: s__('MemberRole|Custom roles'),
+        options: customRoleDropdownItems,
+      },
     ],
   };
 };
diff --git a/ee/app/presenters/ee/member_presenter.rb b/ee/app/presenters/ee/member_presenter.rb
index 1c3e0093856baa0d9d9b778158cfebe4b48e7655..adaa6b01cfc2c58186303b8763e894753837a2da 100644
--- a/ee/app/presenters/ee/member_presenter.rb
+++ b/ee/app/presenters/ee/member_presenter.rb
@@ -37,7 +37,8 @@ def valid_member_roles
         {
           base_access_level: member_role.base_access_level,
           member_role_id: member_role.id,
-          name: member_role.name
+          name: member_role.name,
+          description: member_role.description
         }
       end
     end
diff --git a/ee/spec/frontend/invite_members/components/invite_modal_base_spec.js b/ee/spec/frontend/invite_members/components/invite_modal_base_spec.js
index 7e2824b3dc716efd243daf2d4325525732efa0d2..b02fe484134457daa95358b3b2970eb978f9af13 100644
--- a/ee/spec/frontend/invite_members/components/invite_modal_base_spec.js
+++ b/ee/spec/frontend/invite_members/components/invite_modal_base_spec.js
@@ -158,8 +158,18 @@ describe('EEInviteModalBase', () => {
         await waitForPromises();
 
         expect(findCEBase().props('accessLevels').customRoles).toMatchObject([
-          { baseAccessLevel: 10, memberRoleId: 103, name: 'My role project 1' },
-          { baseAccessLevel: 10, memberRoleId: 104, name: 'My role instance 1' },
+          {
+            baseAccessLevel: 10,
+            memberRoleId: 103,
+            name: 'My role project 1',
+            description: 'My role project 1 description',
+          },
+          {
+            baseAccessLevel: 10,
+            memberRoleId: 104,
+            name: 'My role instance 1',
+            description: 'My role instance 1 description',
+          },
         ]);
       });
     });
@@ -170,9 +180,24 @@ describe('EEInviteModalBase', () => {
         await waitForPromises();
 
         expect(findCEBase().props('accessLevels').customRoles).toMatchObject([
-          { baseAccessLevel: 10, memberRoleId: 100, name: 'My role group 1' },
-          { baseAccessLevel: 20, memberRoleId: 101, name: 'My role group 2' },
-          { baseAccessLevel: 10, memberRoleId: 104, name: 'My role instance 1' },
+          {
+            baseAccessLevel: 10,
+            memberRoleId: 100,
+            name: 'My role group 1',
+            description: 'My role group 1 description',
+          },
+          {
+            baseAccessLevel: 20,
+            memberRoleId: 101,
+            name: 'My role group 2',
+            description: 'My role group 2 description',
+          },
+          {
+            baseAccessLevel: 10,
+            memberRoleId: 104,
+            name: 'My role instance 1',
+            description: 'My role instance 1 description',
+          },
         ]);
       });
     });
diff --git a/ee/spec/frontend/invite_members/mock_data.js b/ee/spec/frontend/invite_members/mock_data.js
index f834fab90f0efd2e343fd1dddf31637bc094a989..d1b286609fdba0b14cb7be6b53ebe346236bd5ff 100644
--- a/ee/spec/frontend/invite_members/mock_data.js
+++ b/ee/spec/frontend/invite_members/mock_data.js
@@ -62,6 +62,7 @@ export const mockGroupMemberRoles = {
             },
             id: 'gid://gitlab/MemberRole/100',
             name: 'My role group 1',
+            description: 'My role group 1 description',
             enabledPermissions: {
               nodes: [
                 {
@@ -80,6 +81,7 @@ export const mockGroupMemberRoles = {
             },
             id: 'gid://gitlab/MemberRole/101',
             name: 'My role group 2',
+            description: 'My role group 2 description',
             enabledPermissions: {
               nodes: [
                 {
@@ -112,6 +114,7 @@ export const mockProjectMemberRoles = {
             },
             id: 'gid://gitlab/MemberRole/103',
             name: 'My role project 1',
+            description: 'My role project 1 description',
             enabledPermissions: {
               nodes: [
                 {
@@ -141,6 +144,7 @@ export const mockInstanceMemberRoles = {
           },
           id: 'gid://gitlab/MemberRole/104',
           name: 'My role instance 1',
+          description: 'My role instance 1 description',
           enabledPermissions: {
             nodes: [
               {
diff --git a/ee/spec/frontend/members/components/table/custom_permissions_spec.js b/ee/spec/frontend/members/components/table/custom_permissions_spec.js
deleted file mode 100644
index 60ab9c83e981ef9b9814c0905b316b88ac984b71..0000000000000000000000000000000000000000
--- a/ee/spec/frontend/members/components/table/custom_permissions_spec.js
+++ /dev/null
@@ -1,96 +0,0 @@
-import { GlBadge } from '@gitlab/ui';
-import Vue from 'vue';
-import VueApollo from 'vue-apollo';
-import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import waitForPromises from 'helpers/wait_for_promises';
-import CustomPermissions from 'ee/members/components/table/custom_permissions.vue';
-import enabledMemberRolePermissions from 'ee/members/graphql/queries/enabled_member_role_permissions.query.graphql';
-import createMockApollo from 'helpers/mock_apollo_helper';
-import { convertToGraphQLId } from '~/graphql_shared/utils';
-import { TYPENAME_MEMBER_ROLE } from '~/graphql_shared/constants';
-import * as Sentry from '~/sentry/sentry_browser_wrapper';
-
-Vue.use(VueApollo);
-
-jest.mock('~/sentry/sentry_browser_wrapper');
-
-describe('CustomPermissions', () => {
-  let wrapper;
-
-  const customPermissions = [{ name: 'Read code' }, { name: 'Read vulnerability' }];
-  const enabledMemberRoleResponse = jest.fn().mockResolvedValue({
-    data: {
-      memberRole: {
-        id: 'gid://gitlab/MemberRole/400',
-        enabledPermissions: {
-          nodes: [
-            {
-              name: 'Admin merge request',
-              value: 'ADMIN_MERGE_REQUEST',
-            },
-          ],
-        },
-      },
-    },
-  });
-
-  const createComponent = ({ mockedResponse = enabledMemberRoleResponse } = {}) => {
-    const apolloProvider = createMockApollo([[enabledMemberRolePermissions, mockedResponse]]);
-
-    wrapper = shallowMountExtended(CustomPermissions, {
-      apolloProvider,
-      propsData: {
-        memberRoleId: 10,
-        customPermissions,
-      },
-    });
-  };
-
-  const findBadges = () => wrapper.findAllComponents(GlBadge);
-
-  describe('when GraphQL queries success', () => {
-    beforeEach(() => {
-      createComponent();
-      waitForPromises();
-    });
-
-    it('shows a title', () => {
-      expect(wrapper.findByTestId('title').text()).toBe(wrapper.vm.$options.i18n.title);
-    });
-
-    it('shows badges', () => {
-      const badges = findBadges();
-      expect(badges).toHaveLength(2);
-
-      expect(badges.at(0).props()).toMatchObject({ variant: 'success', size: 'sm' });
-      expect(badges.at(0).text()).toBe('Read code');
-
-      expect(badges.at(1).props()).toMatchObject({ variant: 'success', size: 'sm' });
-      expect(badges.at(1).text()).toBe('Read vulnerability');
-    });
-
-    it('update badges', async () => {
-      const memberRoleId = 400;
-      wrapper.setProps({ memberRoleId });
-      await waitForPromises();
-
-      expect(enabledMemberRoleResponse).toHaveBeenCalledWith({
-        id: convertToGraphQLId(TYPENAME_MEMBER_ROLE, memberRoleId),
-      });
-      expect(findBadges()).toHaveLength(1);
-    });
-  });
-
-  describe('when GraphQL queries fail', () => {
-    it('reports fetching errors to Sentry', async () => {
-      const myError = new Error('dummy');
-      createComponent({ mockedResponse: jest.fn().mockRejectedValue(myError) });
-
-      const memberRoleId = 400;
-      wrapper.setProps({ memberRoleId });
-      await waitForPromises();
-
-      expect(Sentry.captureException).toHaveBeenCalledWith(myError);
-    });
-  });
-});
diff --git a/ee/spec/frontend/members/components/table/max_role_spec.js b/ee/spec/frontend/members/components/table/max_role_spec.js
index d5df495d05fd7a8ac59fbd208ababf35ff74152e..4eb928af614b74aeded1ea1fee5386990ac22f39 100644
--- a/ee/spec/frontend/members/components/table/max_role_spec.js
+++ b/ee/spec/frontend/members/components/table/max_role_spec.js
@@ -1,8 +1,8 @@
-import { GlCollapsibleListbox, GlListboxItem } from '@gitlab/ui';
-import { mount } from '@vue/test-utils';
+import { GlCollapsibleListbox, GlListboxItem, GlBadge } from '@gitlab/ui';
 import Vue from 'vue';
 // eslint-disable-next-line no-restricted-imports
 import Vuex from 'vuex';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
 import LdapDropdownFooter from 'ee/members/components/action_dropdowns/ldap_dropdown_footer.vue';
 import ManageRolesDropdownFooter from 'ee/members/components/action_dropdowns/manage_roles_dropdown_footer.vue';
 import { guestOverageConfirmAction } from 'ee/members/guest_overage_confirm_action';
@@ -10,6 +10,8 @@ import waitForPromises from 'helpers/wait_for_promises';
 import MaxRole from '~/members/components/table/max_role.vue';
 import { MEMBER_TYPES } from '~/members/constants';
 import * as Sentry from '~/sentry/sentry_browser_wrapper';
+import { s__ } from '~/locale';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
 import { upgradedMember as member } from '../../mock_data';
 
 Vue.use(Vuex);
@@ -39,7 +41,7 @@ describe('MaxRole', () => {
   };
 
   const createComponent = (propsData = {}, store = createStore()) => {
-    wrapper = mount(MaxRole, {
+    wrapper = mountExtended(MaxRole, {
       provide: {
         namespace: MEMBER_TYPES.user,
         group: {
@@ -56,22 +58,41 @@ describe('MaxRole', () => {
       stubs: {
         CustomPermissions: CustomPermissionsStub,
       },
+      directives: {
+        GlTooltip: createMockDirective('gl-tooltip'),
+      },
     });
 
     return waitForPromises();
   };
 
-  const findCustomPermissions = () => wrapper.findComponent(CustomPermissionsStub);
+  const findBadge = () => wrapper.findComponent(GlBadge);
   const findListbox = () => wrapper.findComponent(GlCollapsibleListbox);
   const findListboxItems = () => wrapper.findAllComponents(GlListboxItem);
   const findListboxItemByText = (text) =>
     findListboxItems().wrappers.find((item) => item.text() === text);
+  const findRoleText = () => wrapper.findByTestId('role-text');
 
   describe('when a member has custom permissions', () => {
-    it('renders an initial list', async () => {
-      await createComponent();
+    beforeEach(() => {
+      createComponent({
+        permissions: {
+          canUpdate: false,
+        },
+      });
+    });
+
+    it('renders role text and a custom role badge', () => {
+      expect(findRoleText().text()).toBe('custom role 1');
+
+      expect(findBadge().exists()).toBe(true);
+      expect(findBadge().text()).toBe(s__('MemberRole|Custom role'));
+    });
 
-      expect(findCustomPermissions().exists()).toBe(true);
+    it('renders a tooltip', () => {
+      const tooltip = getBinding(findRoleText().element, 'gl-tooltip');
+      expect(tooltip).toBeDefined();
+      expect(tooltip.value).toBe('custom role 1 description');
     });
   });
 
@@ -91,8 +112,8 @@ describe('MaxRole', () => {
       );
     });
 
-    it('does not render a list', () => {
-      expect(findCustomPermissions().exists()).toBe(false);
+    it('does not render a custom role badge', () => {
+      expect(findBadge().exists()).toBe(false);
     });
 
     describe('after unsuccessful role assignment', () => {
@@ -109,8 +130,8 @@ describe('MaxRole', () => {
         expect(findListbox().find('[aria-selected=true]').text()).toBe('Owner');
       });
 
-      it('resets list of permissions', () => {
-        expect(findCustomPermissions().exists()).toBe(false);
+      it('resets custom role badge', () => {
+        expect(findBadge().exists()).toBe(false);
       });
     });
   });
@@ -169,10 +190,8 @@ describe('MaxRole', () => {
 
       expect(findListbox().props('items')[0].text).toBe('Standard roles');
       expect(findListbox().props('items')[0].options).toHaveLength(6);
-      expect(findListbox().props('items')[1].text).toBe('Custom roles based on Guest');
-      expect(findListbox().props('items')[1].options).toHaveLength(2);
-      expect(findListbox().props('items')[2].text).toBe('Custom roles based on Reporter');
-      expect(findListbox().props('items')[2].options).toHaveLength(1);
+      expect(findListbox().props('items')[1].text).toBe('Custom roles');
+      expect(findListbox().props('items')[1].options).toHaveLength(3);
     });
 
     it('calls `updateMemberRole` Vuex action', async () => {
diff --git a/ee/spec/frontend/members/mock_data.js b/ee/spec/frontend/members/mock_data.js
index ecb34c2646ac38d4e5bd9526168c2116462d70c7..c56d6cf4a30f0fe71196a634504f41111d81f126 100644
--- a/ee/spec/frontend/members/mock_data.js
+++ b/ee/spec/frontend/members/mock_data.js
@@ -9,7 +9,12 @@ export const bannedMember = {
 
 export const customRoles = [
   { baseAccessLevel: 20, name: 'custom role 3', memberRoleId: 103 },
-  { baseAccessLevel: 10, name: 'custom role 1', memberRoleId: 101 },
+  {
+    baseAccessLevel: 10,
+    name: 'custom role 1',
+    description: 'custom role 1 description',
+    memberRoleId: 101,
+  },
   { baseAccessLevel: 10, name: 'custom role 2', memberRoleId: 102 },
 ];
 
@@ -17,7 +22,12 @@ export const customPermissions = [{ name: 'Read code' }, { name: 'Read vulnerabi
 
 export const upgradedMember = {
   ...member,
-  accessLevel: { integerValue: 10, stringValue: 'custom role 1', memberRoleId: 101 },
+  accessLevel: {
+    integerValue: 10,
+    stringValue: 'custom role 1',
+    memberRoleId: 101,
+    description: 'custom role 1 description',
+  },
   customPermissions,
   customRoles,
 };
diff --git a/ee/spec/frontend/members/utils_spec.js b/ee/spec/frontend/members/utils_spec.js
index 3d8f429bd96a88e185b27d1ad59c179b363c5580..c4b031a1669a370ee2000b510ef0a086c9555c17 100644
--- a/ee/spec/frontend/members/utils_spec.js
+++ b/ee/spec/frontend/members/utils_spec.js
@@ -107,6 +107,7 @@ describe('Members Utils', () => {
         expect(flatten).toContainEqual({
           text: 'custom role 1',
           value: 'role-custom-0',
+          description: 'custom role 1 description',
           accessLevel: 10,
           memberRoleId: 101,
         });
@@ -115,13 +116,11 @@ describe('Members Utils', () => {
       it('returns properly formatted dropdowns', () => {
         const { formatted } = roleDropdownItems({ ...memberMock, customRoles });
 
-        expect(formatted).toHaveLength(3);
+        expect(formatted).toHaveLength(2);
         expect(formatted[0].text).toBe('Standard roles');
         expect(formatted[0].options).toHaveLength(0);
-        expect(formatted[1].text).toBe('Custom roles based on Guest');
-        expect(formatted[1].options).toHaveLength(2);
-        expect(formatted[2].text).toBe('Custom roles based on Reporter');
-        expect(formatted[2].options).toHaveLength(1);
+        expect(formatted[1].text).toBe('Custom roles');
+        expect(formatted[1].options).toHaveLength(3);
       });
     });
   });
diff --git a/ee/spec/frontend/roles_and_permissions/mock_data.js b/ee/spec/frontend/roles_and_permissions/mock_data.js
index ba1eb8d2b9ffd5f88efc8c331cf13efa815bf2c8..8af66f44d3f6bc5534792562914bbaf8727e7e86 100644
--- a/ee/spec/frontend/roles_and_permissions/mock_data.js
+++ b/ee/spec/frontend/roles_and_permissions/mock_data.js
@@ -41,6 +41,7 @@ export const mockMemberRoles = {
             },
             id: 'gid://gitlab/MemberRole/1',
             name: 'Test',
+            description: 'Test description',
             enabledPermissions: {
               nodes: [
                 {
@@ -75,6 +76,7 @@ export const mockInstanceMemberRoles = {
           },
           id: 'gid://gitlab/MemberRole/2',
           name: 'Instance Test',
+          description: 'Instance Test description',
           enabledPermissions: {
             nodes: [
               {
diff --git a/ee/spec/helpers/ee/groups/group_members_helper_spec.rb b/ee/spec/helpers/ee/groups/group_members_helper_spec.rb
index e0990fc2cb18b67d0dca8a7e8838ce5229c12e60..f5aeca63fdcadae44a4d475c5fc7119139c20ec9 100644
--- a/ee/spec/helpers/ee/groups/group_members_helper_spec.rb
+++ b/ee/spec/helpers/ee/groups/group_members_helper_spec.rb
@@ -113,7 +113,7 @@
   end
 
   context 'when member has custom role' do
-    let(:member_role) { create(:member_role, :guest, name: 'guest plus', namespace: group, read_code: true) }
+    let(:member_role) { create(:member_role, :guest, name: 'guest plus', namespace: group, read_code: true, description: 'My custom role') }
     let(:user) { create(:user) }
     let(:banned) { present_members(create_list(:group_member, 1, :guest, group: group, user: user, member_role: member_role)) }
 
diff --git a/ee/spec/presenters/member_presenter_spec.rb b/ee/spec/presenters/member_presenter_spec.rb
index 9d65e8058118f18d5989d4df4224d91e0e4c1314..d5fc654c193dde7b199a30eaaa086f1d330595c0 100644
--- a/ee/spec/presenters/member_presenter_spec.rb
+++ b/ee/spec/presenters/member_presenter_spec.rb
@@ -48,7 +48,9 @@
 
   describe '#valid_member_roles' do
     let_it_be(:member_role_guest) { create(:member_role, :guest, name: 'guest plus', namespace: root_group) }
-    let_it_be(:member_role_reporter) { create(:member_role, :reporter, name: 'reporter plus', namespace: root_group) }
+    let_it_be(:member_role_reporter) do
+      create(:member_role, :reporter, name: 'reporter plus', namespace: root_group, description: 'My custom role')
+    end
 
     let_it_be(:member_role_instance) do
       create(:member_role, :guest, :instance, name: 'guest plus (instance-level)')
@@ -70,7 +72,8 @@
             {
               base_access_level: Gitlab::Access::REPORTER,
               member_role_id: member_role_reporter.id,
-              name: 'reporter plus'
+              name: 'reporter plus',
+              description: 'My custom role'
             }
           ]
         )
@@ -82,17 +85,20 @@
             {
               base_access_level: Gitlab::Access::REPORTER,
               member_role_id: member_role_reporter.id,
-              name: 'reporter plus'
+              name: 'reporter plus',
+              description: 'My custom role'
             },
             {
               base_access_level: Gitlab::Access::GUEST,
               member_role_id: member_role_guest.id,
-              name: 'guest plus'
+              name: 'guest plus',
+              description: nil
             },
             {
               base_access_level: Gitlab::Access::GUEST,
               member_role_id: member_role_instance.id,
-              name: 'guest plus (instance-level)'
+              name: 'guest plus (instance-level)',
+              description: nil
             }
           ]
         )
diff --git a/ee/spec/serializers/member_entity_spec.rb b/ee/spec/serializers/member_entity_spec.rb
index ca14ce019b1c476e1fc428009fc71356d95dacd5..e576e9abea029839eea7a79f3f60b372f12984b3 100644
--- a/ee/spec/serializers/member_entity_spec.rb
+++ b/ee/spec/serializers/member_entity_spec.rb
@@ -86,7 +86,7 @@
     it_behaves_like 'member.json'
 
     context 'with custom role' do
-      let(:member_role) { create(:member_role, :guest, name: 'guest plus', namespace: group, read_code: true) }
+      let(:member_role) { create(:member_role, :guest, name: 'guest plus', description: 'My custom role', namespace: group, read_code: true) }
       let(:member) { GroupMemberPresenter.new(create(:group_member, :guest, group: group, member_role: member_role, user: current_user), current_user: current_user) }
 
       it_behaves_like 'member.json'
@@ -101,7 +101,7 @@
     it_behaves_like 'member.json'
 
     context 'with custom role' do
-      let_it_be(:member_role) { create(:member_role, :guest, name: 'guest plus', namespace: group, read_code: true) }
+      let_it_be(:member_role) { create(:member_role, :guest, name: 'guest plus', description: 'My custom role', namespace: group, read_code: true) }
       let_it_be(:member) { ProjectMemberPresenter.new(create(:project_member, :guest, project: project, member_role: member_role, user: current_user), current_user: current_user) }
 
       it_behaves_like 'member.json'
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 0f02c1a5683214909bcd2114bde0b7dffd71b48d..92ab60c0f9e364e128e6ecc53fd7b08d56379c64 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -30170,15 +30170,12 @@ msgstr ""
 msgid "MemberRole|Create new role"
 msgstr ""
 
-msgid "MemberRole|Custom permissions:"
+msgid "MemberRole|Custom role"
 msgstr ""
 
 msgid "MemberRole|Custom roles"
 msgstr ""
 
-msgid "MemberRole|Custom roles based on %{accessLevel}"
-msgstr ""
-
 msgid "MemberRole|Delete role"
 msgstr ""
 
diff --git a/spec/fixtures/api/schemas/entities/member.json b/spec/fixtures/api/schemas/entities/member.json
index cfbcefef1cb93898d20f2005e3995b53e910f816..63bbf1e183d6a2340b43573ae9aac810802c5a43 100644
--- a/spec/fixtures/api/schemas/entities/member.json
+++ b/spec/fixtures/api/schemas/entities/member.json
@@ -168,6 +168,9 @@
             },
             "name": {
               "type": "string"
+            },
+            "description": {
+              "type": "string"
             }
           },
           "additionalProperties": false
diff --git a/spec/frontend/members/components/table/max_role_spec.js b/spec/frontend/members/components/table/max_role_spec.js
index 75e1e05afb1fa4fd4c6e249776181509aeb76549..7bd20a9670f18357cd0d23e334f808802d5859e9 100644
--- a/spec/frontend/members/components/table/max_role_spec.js
+++ b/spec/frontend/members/components/table/max_role_spec.js
@@ -1,4 +1,4 @@
-import { GlBadge, GlCollapsibleListbox, GlListboxItem } from '@gitlab/ui';
+import { GlCollapsibleListbox, GlListboxItem } from '@gitlab/ui';
 import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
 import { mount } from '@vue/test-utils';
 import Vue, { nextTick } from 'vue';
@@ -60,7 +60,6 @@ describe('MaxRole', () => {
     });
   };
 
-  const findBadge = () => wrapper.findComponent(GlBadge);
   const findListbox = () => wrapper.findComponent(GlCollapsibleListbox);
   const findListboxItems = () => wrapper.findAllComponents(GlListboxItem);
   const findListboxItemByText = (text) =>
@@ -71,14 +70,14 @@ describe('MaxRole', () => {
   });
 
   describe('when member can not be updated', () => {
-    it('renders a badge instead of a collapsible listbox', () => {
+    it('renders the role name instead of a collapsible listbox', () => {
       createComponent({
         permissions: {
           canUpdate: false,
         },
       });
 
-      expect(findBadge().text()).toBe('Owner');
+      expect(wrapper.text()).toContain('Owner');
     });
   });