diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue
index 1bc300eff79d6df061ef598dfe533fdba018b3f5..d67ef01242003dc4d43437c59b8bc80ccab12458 100644
--- a/app/assets/javascripts/groups/components/group_item.vue
+++ b/app/assets/javascripts/groups/components/group_item.vue
@@ -4,7 +4,6 @@ import {
   GlLoadingIcon,
   GlBadge,
   GlButton,
-  GlIcon,
   GlLabel,
   GlPopover,
   GlLink,
@@ -13,15 +12,11 @@ import {
 import SafeHtml from '~/vue_shared/directives/safe_html';
 import { visitUrl } from '~/lib/utils/url_utility';
 import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge.vue';
+import VisibilityIcon from '~/vue_shared/components/visibility_icon.vue';
 import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants';
 import { helpPagePath } from '~/helpers/help_page_helper';
 import { __ } from '~/locale';
-import {
-  VISIBILITY_LEVELS_STRING_TO_INTEGER,
-  VISIBILITY_TYPE_ICON,
-  GROUP_VISIBILITY_TYPE,
-  PROJECT_VISIBILITY_TYPE,
-} from '~/visibility_level/constants';
+import { VISIBILITY_LEVELS_STRING_TO_INTEGER } from '~/visibility_level/constants';
 import { ITEM_TYPE, ACTIVE_TAB_SHARED } from '../constants';
 
 import eventHub from '../event_hub';
@@ -40,7 +35,6 @@ export default {
     GlBadge,
     GlButton,
     GlLoadingIcon,
-    GlIcon,
     GlLabel,
     GlPopover,
     GlLink,
@@ -48,6 +42,7 @@ export default {
     ItemTypeIcon,
     ItemActions,
     ItemStats,
+    VisibilityIcon,
   },
   inject: {
     currentGroupVisibility: {
@@ -97,13 +92,6 @@ export default {
     isGroup() {
       return this.group.type === ITEM_TYPE.GROUP;
     },
-    visibilityIcon() {
-      return VISIBILITY_TYPE_ICON[this.group.visibility];
-    },
-    visibilityTooltip() {
-      if (this.isGroup) return GROUP_VISIBILITY_TYPE[this.group.visibility];
-      return PROJECT_VISIBILITY_TYPE[this.group.visibility];
-    },
     microdata() {
       return this.group.microdata || {};
     },
@@ -226,12 +214,12 @@ export default {
               <!-- link hover text-decoration from over-extending -->
               {{ group.name }}
             </a>
-            <gl-icon
-              v-gl-tooltip.bottom
-              class="gl-display-inline-flex gl-align-items-center gl-mr-3 gl-text-gray-500"
-              :name="visibilityIcon"
-              :title="visibilityTooltip"
+            <visibility-icon
+              :is-group="isGroup"
+              :visibility-level="group.visibility"
+              class="gl-mr-3"
               data-testid="group-visibility-icon"
+              tooltip-placement="bottom"
             />
             <template v-if="shouldShowVisibilityWarning">
               <gl-button
diff --git a/app/assets/javascripts/vue_shared/components/visibility_icon.vue b/app/assets/javascripts/vue_shared/components/visibility_icon.vue
new file mode 100644
index 0000000000000000000000000000000000000000..a05651be1ca7dbc6729eef07954fabbbbab653a4
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/visibility_icon.vue
@@ -0,0 +1,55 @@
+<script>
+import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
+import {
+  GROUP_VISIBILITY_TYPE,
+  PROJECT_VISIBILITY_TYPE,
+  VISIBILITY_TYPE_ICON,
+} from '~/visibility_level/constants';
+
+export default {
+  components: {
+    GlIcon,
+  },
+  directives: {
+    GlTooltip: GlTooltipDirective,
+  },
+  props: {
+    isGroup: {
+      default: false,
+      required: false,
+      type: Boolean,
+    },
+    tooltipPlacement: {
+      default: 'top',
+      required: false,
+      type: String,
+    },
+    visibilityLevel: {
+      required: true,
+      type: String,
+    },
+  },
+  computed: {
+    visibilityIcon() {
+      return VISIBILITY_TYPE_ICON[this.visibilityLevel];
+    },
+    visibilityTooltip() {
+      if (this.isGroup) {
+        return GROUP_VISIBILITY_TYPE[this.visibilityLevel];
+      }
+
+      return PROJECT_VISIBILITY_TYPE[this.visibilityLevel];
+    },
+  },
+};
+</script>
+
+<template>
+  <gl-icon
+    v-gl-tooltip="{ placement: tooltipPlacement }"
+    :aria-label="visibilityTooltip"
+    :name="visibilityIcon"
+    :title="visibilityTooltip"
+    class="gl-display-inline-flex gl-text-secondary"
+  />
+</template>
diff --git a/spec/frontend/groups/components/group_item_spec.js b/spec/frontend/groups/components/group_item_spec.js
index 454f691f5b94718caa591feb0db91906b09184c6..9a7b740f2efe2f40a63e51631dfc11dd746ef289 100644
--- a/spec/frontend/groups/components/group_item_spec.js
+++ b/spec/frontend/groups/components/group_item_spec.js
@@ -3,6 +3,7 @@ import waitForPromises from 'helpers/wait_for_promises';
 import GroupFolder from '~/groups/components/group_folder.vue';
 import GroupItem from 'jh_else_ce/groups/components/group_item.vue';
 import ItemActions from '~/groups/components/item_actions.vue';
+import VisibilityIcon from '~/vue_shared/components/visibility_icon.vue';
 import eventHub from '~/groups/event_hub';
 import { getGroupItemMicrodata } from '~/groups/store/utils';
 import * as urlUtilities from '~/lib/utils/url_utility';
@@ -11,8 +12,6 @@ import {
   VISIBILITY_LEVEL_PRIVATE_STRING,
   VISIBILITY_LEVEL_INTERNAL_STRING,
   VISIBILITY_LEVEL_PUBLIC_STRING,
-  GROUP_VISIBILITY_TYPE,
-  PROJECT_VISIBILITY_TYPE,
 } from '~/visibility_level/constants';
 import { helpPagePath } from '~/helpers/help_page_helper';
 import { mountExtended, extendedWrapper } from 'helpers/vue_test_utils_helper';
@@ -117,49 +116,39 @@ describe('GroupItemComponent', () => {
         wrapper.destroy();
       });
     });
+  });
 
-    describe('visibilityTooltip', () => {
-      describe('if item represents group', () => {
-        it.each`
-          visibilityLevel                     | visibilityTooltip
-          ${VISIBILITY_LEVEL_PUBLIC_STRING}   | ${GROUP_VISIBILITY_TYPE[VISIBILITY_LEVEL_PUBLIC_STRING]}
-          ${VISIBILITY_LEVEL_INTERNAL_STRING} | ${GROUP_VISIBILITY_TYPE[VISIBILITY_LEVEL_INTERNAL_STRING]}
-          ${VISIBILITY_LEVEL_PRIVATE_STRING}  | ${GROUP_VISIBILITY_TYPE[VISIBILITY_LEVEL_PRIVATE_STRING]}
-        `(
-          'should return corresponding text when visibility level is $visibilityLevel',
-          ({ visibilityLevel, visibilityTooltip }) => {
-            const group = { ...mockParentGroupItem };
-
-            group.type = 'group';
-            group.visibility = visibilityLevel;
-            wrapper = createComponent({ group });
-
-            expect(wrapper.vm.visibilityTooltip).toBe(visibilityTooltip);
-            wrapper.destroy();
-          },
-        );
+  describe('visibilityIcon', () => {
+    describe('if item represents a group', () => {
+      it('should display the visibility icon with appropriate props', () => {
+        const group = { ...mockParentGroupItem };
+
+        group.type = 'group';
+        group.visibility = VISIBILITY_LEVEL_PRIVATE_STRING;
+        wrapper = createComponent({ group });
+
+        expect(wrapper.findComponent(VisibilityIcon).props()).toEqual({
+          isGroup: true,
+          tooltipPlacement: 'bottom',
+          visibilityLevel: VISIBILITY_LEVEL_PRIVATE_STRING,
+        });
       });
+    });
 
-      describe('if item represents project', () => {
-        it.each`
-          visibilityLevel                     | visibilityTooltip
-          ${VISIBILITY_LEVEL_PUBLIC_STRING}   | ${PROJECT_VISIBILITY_TYPE[VISIBILITY_LEVEL_PUBLIC_STRING]}
-          ${VISIBILITY_LEVEL_INTERNAL_STRING} | ${PROJECT_VISIBILITY_TYPE[VISIBILITY_LEVEL_INTERNAL_STRING]}
-          ${VISIBILITY_LEVEL_PRIVATE_STRING}  | ${PROJECT_VISIBILITY_TYPE[VISIBILITY_LEVEL_PRIVATE_STRING]}
-        `(
-          'should return corresponding text when visibility level is $visibilityLevel',
-          ({ visibilityLevel, visibilityTooltip }) => {
-            const group = { ...mockParentGroupItem };
-
-            group.type = 'project';
-            group.lastActivityAt = '2017-04-09T18:40:39.101Z';
-            group.visibility = visibilityLevel;
-            wrapper = createComponent({ group });
-
-            expect(wrapper.vm.visibilityTooltip).toBe(visibilityTooltip);
-            wrapper.destroy();
-          },
-        );
+    describe('if item represents a project', () => {
+      it('should display the visibility icon with appropriate props', () => {
+        const group = { ...mockParentGroupItem };
+
+        group.type = 'project';
+        group.lastActivityAt = '2017-04-09T18:40:39.101Z';
+        group.visibility = VISIBILITY_LEVEL_PRIVATE_STRING;
+        wrapper = createComponent({ group });
+
+        expect(wrapper.findComponent(VisibilityIcon).props()).toEqual({
+          isGroup: false,
+          tooltipPlacement: 'bottom',
+          visibilityLevel: VISIBILITY_LEVEL_PRIVATE_STRING,
+        });
       });
     });
   });
@@ -279,7 +268,6 @@ describe('GroupItemComponent', () => {
       expect(vm.$el.querySelector('.title a.no-expand')).toBeDefined();
 
       expect(visibilityIconEl).not.toBe(null);
-      expect(visibilityIconEl.getAttribute('title')).toBe(vm.visibilityTooltip);
 
       expect(vm.$el.querySelector('.access-type')).toBeDefined();
       expect(vm.$el.querySelector('.description')).toBeDefined();
diff --git a/spec/frontend/vue_shared/components/visibility_icon_spec.js b/spec/frontend/vue_shared/components/visibility_icon_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..deff7556f601421fd252eca37eb121917264fc92
--- /dev/null
+++ b/spec/frontend/vue_shared/components/visibility_icon_spec.js
@@ -0,0 +1,81 @@
+import { GlIcon } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import VisibilityIcon from '~/vue_shared/components/visibility_icon.vue';
+import {
+  GROUP_VISIBILITY_TYPE,
+  PROJECT_VISIBILITY_TYPE,
+  VISIBILITY_LEVEL_INTERNAL_STRING,
+  VISIBILITY_LEVEL_PRIVATE_STRING,
+  VISIBILITY_LEVEL_PUBLIC_STRING,
+  VISIBILITY_TYPE_ICON,
+} from '~/visibility_level/constants';
+
+describe('Visibility icon', () => {
+  let glTooltipDirectiveMock;
+  let wrapper;
+
+  const createComponent = (props = {}) => {
+    glTooltipDirectiveMock = jest.fn();
+
+    wrapper = shallowMountExtended(VisibilityIcon, {
+      directives: {
+        GlTooltip: glTooltipDirectiveMock,
+      },
+      propsData: {
+        ...props,
+      },
+    });
+  };
+
+  const findIcon = () => wrapper.findComponent(GlIcon);
+
+  describe('visibilityTooltip', () => {
+    describe('if item represents group', () => {
+      it.each`
+        visibilityLevel                     | visibilityTooltip                                          | visibilityIcon                                            | tooltipPlacement
+        ${VISIBILITY_LEVEL_PUBLIC_STRING}   | ${GROUP_VISIBILITY_TYPE[VISIBILITY_LEVEL_PUBLIC_STRING]}   | ${VISIBILITY_TYPE_ICON[VISIBILITY_LEVEL_PUBLIC_STRING]}   | ${'top'}
+        ${VISIBILITY_LEVEL_INTERNAL_STRING} | ${GROUP_VISIBILITY_TYPE[VISIBILITY_LEVEL_INTERNAL_STRING]} | ${VISIBILITY_TYPE_ICON[VISIBILITY_LEVEL_INTERNAL_STRING]} | ${'bottom'}
+        ${VISIBILITY_LEVEL_PRIVATE_STRING}  | ${GROUP_VISIBILITY_TYPE[VISIBILITY_LEVEL_PRIVATE_STRING]}  | ${VISIBILITY_TYPE_ICON[VISIBILITY_LEVEL_PRIVATE_STRING]}  | ${'right'}
+      `(
+        'should return corresponding text when visibility level is $visibilityLevel',
+        ({ visibilityLevel, visibilityTooltip, visibilityIcon, tooltipPlacement }) => {
+          createComponent({ isGroup: true, visibilityLevel, tooltipPlacement });
+
+          expect(findIcon().attributes()).toMatchObject({
+            arialabel: visibilityTooltip,
+            name: visibilityIcon,
+            title: visibilityTooltip,
+          });
+
+          expect(glTooltipDirectiveMock.mock.calls[0][1].value).toEqual({
+            placement: tooltipPlacement,
+          });
+        },
+      );
+    });
+
+    describe('if item represents project', () => {
+      it.each`
+        visibilityLevel                     | visibilityTooltip                                            | visibilityIcon                                            | tooltipPlacement
+        ${VISIBILITY_LEVEL_PUBLIC_STRING}   | ${PROJECT_VISIBILITY_TYPE[VISIBILITY_LEVEL_PUBLIC_STRING]}   | ${VISIBILITY_TYPE_ICON[VISIBILITY_LEVEL_PUBLIC_STRING]}   | ${'top'}
+        ${VISIBILITY_LEVEL_INTERNAL_STRING} | ${PROJECT_VISIBILITY_TYPE[VISIBILITY_LEVEL_INTERNAL_STRING]} | ${VISIBILITY_TYPE_ICON[VISIBILITY_LEVEL_INTERNAL_STRING]} | ${'bottom'}
+        ${VISIBILITY_LEVEL_PRIVATE_STRING}  | ${PROJECT_VISIBILITY_TYPE[VISIBILITY_LEVEL_PRIVATE_STRING]}  | ${VISIBILITY_TYPE_ICON[VISIBILITY_LEVEL_PRIVATE_STRING]}  | ${'left'}
+      `(
+        'should return corresponding text when visibility level is $visibilityLevel',
+        ({ visibilityLevel, visibilityTooltip, visibilityIcon, tooltipPlacement }) => {
+          createComponent({ visibilityLevel, tooltipPlacement });
+
+          expect(findIcon().attributes()).toMatchObject({
+            arialabel: visibilityTooltip,
+            name: visibilityIcon,
+            title: visibilityTooltip,
+          });
+
+          expect(glTooltipDirectiveMock.mock.calls[0][1].value).toEqual({
+            placement: tooltipPlacement,
+          });
+        },
+      );
+    });
+  });
+});