diff --git a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_header.vue b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_header.vue
index 3d4eebb9524da48d4ea60a6d71c6367f0d77c3ed..53e976d698b12da309d986d25d3892b9f894f2c5 100644
--- a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_header.vue
+++ b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_header.vue
@@ -9,14 +9,16 @@ import {
 } from '@gitlab/ui';
 
 import { getIdFromGraphQLId } from '~/graphql_shared/utils';
-import { STATUS_OPEN } from '~/issues/constants';
+import { issuableStatusText, STATUS_OPEN } from '~/issues/constants';
 import { isExternal } from '~/lib/utils/url_utility';
 import { n__, sprintf } from '~/locale';
+import ConfidentialityBadge from '~/vue_shared/components/confidentiality_badge.vue';
 import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
 import WorkItemTypeIcon from '~/work_items/components/work_item_type_icon.vue';
 
 export default {
   components: {
+    ConfidentialityBadge,
     GlIcon,
     GlBadge,
     GlButton,
@@ -77,8 +79,16 @@ export default {
       required: false,
       default: false,
     },
+    workspaceType: {
+      type: String,
+      required: false,
+      default: '',
+    },
   },
   computed: {
+    badgeText() {
+      return issuableStatusText[this.issuableState];
+    },
     badgeVariant() {
       return this.issuableState === STATUS_OPEN ? 'success' : 'info';
     },
@@ -109,6 +119,7 @@ export default {
   },
   methods: {
     handleRightSidebarToggleClick() {
+      this.$emit('toggle');
       if (this.toggleSidebarButtonEl) {
         this.toggleSidebarButtonEl.dispatchEvent(new Event('click'));
       }
@@ -118,21 +129,23 @@ export default {
 </script>
 
 <template>
-  <div class="detail-page-header">
+  <div class="detail-page-header gl-flex-direction-column gl-sm-flex-direction-row">
     <div class="detail-page-header-body">
       <gl-badge class="issuable-status-badge gl-mr-3" :variant="badgeVariant">
         <gl-icon v-if="statusIcon" :name="statusIcon" :class="statusIconClass" />
-        <span class="gl-display-none gl-sm-display-block"><slot name="status-badge"></slot></span>
+        <span class="gl-display-none gl-sm-display-block gl-ml-2">
+          <slot name="status-badge">{{ badgeText }}</slot>
+        </span>
       </gl-badge>
-      <div class="issuable-meta gl-display-flex! gl-align-items-center">
-        <div v-if="blocked || confidential" class="gl-display-inline-block">
-          <div v-if="blocked" data-testid="blocked" class="issuable-warning-icon inline">
-            <gl-icon name="lock" :aria-label="__('Blocked')" />
-          </div>
-          <div v-if="confidential" data-testid="confidential" class="issuable-warning-icon inline">
-            <gl-icon name="eye-slash" :aria-label="__('Confidential')" />
-          </div>
+      <div class="issuable-meta gl-display-flex! gl-align-items-center gl-flex-wrap">
+        <div v-if="blocked" data-testid="blocked" class="issuable-warning-icon inline">
+          <gl-icon name="lock" :aria-label="__('Blocked')" />
         </div>
+        <confidentiality-badge
+          v-if="confidential"
+          :issuable-type="issuableType"
+          :workspace-type="workspaceType"
+        />
         <span>
           <template v-if="showWorkItemTypeIcon">
             <work-item-type-icon :work-item-type="issuableType" show-text />
@@ -182,10 +195,7 @@ export default {
         @click="handleRightSidebarToggleClick"
       />
     </div>
-    <div
-      data-testid="header-actions"
-      class="detail-page-header-actions gl-display-flex gl-md-display-block"
-    >
+    <div data-testid="header-actions" class="detail-page-header-actions gl-display-flex">
       <slot name="header-actions"></slot>
     </div>
   </div>
diff --git a/ee/app/assets/javascripts/epic/components/epic_header.vue b/ee/app/assets/javascripts/epic/components/epic_header.vue
index 2399016cb0f4daf1656feead3cff87fed243493f..46dba944b61ca17d81d61d5b071ef6a213209524 100644
--- a/ee/app/assets/javascripts/epic/components/epic_header.vue
+++ b/ee/app/assets/javascripts/epic/components/epic_header.vue
@@ -1,12 +1,8 @@
 <script>
-import { GlButton, GlBadge, GlIcon } from '@gitlab/ui';
 import { mapState, mapGetters, mapActions } from 'vuex';
 import { EVENT_ISSUABLE_VUE_APP_CHANGE } from '~/issuable/constants';
-import { __ } from '~/locale';
-import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
-import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
-import ConfidentialityBadge from '~/vue_shared/components/confidentiality_badge.vue';
 import { STATUS_CLOSED, STATUS_OPEN, TYPE_EPIC, WORKSPACE_GROUP } from '~/issues/constants';
+import IssuableHeader from '~/vue_shared/issuable/show/components/issuable_header.vue';
 import epicUtils from '../utils/epic_utils';
 import EpicHeaderActions from './epic_header_actions.vue';
 
@@ -15,22 +11,23 @@ export default {
   WORKSPACE_GROUP,
   components: {
     EpicHeaderActions,
-    GlIcon,
-    GlBadge,
-    GlButton,
-    UserAvatarLink,
-    TimeagoTooltip,
-    ConfidentialityBadge,
+    IssuableHeader,
   },
   computed: {
-    ...mapState(['sidebarCollapsed', 'author', 'created', 'confidential']),
+    ...mapState(['sidebarCollapsed', 'author', 'created', 'confidential', 'state']),
     ...mapGetters(['isEpicOpen']),
+    formattedAuthor() {
+      const { src, url, username } = this.author;
+      return {
+        ...this.author,
+        avatarUrl: src,
+        username: username.startsWith('@') ? username.substring(1) : username,
+        webUrl: url,
+      };
+    },
     statusIcon() {
       return this.isEpicOpen ? 'epic' : 'epic-closed';
     },
-    statusText() {
-      return this.isEpicOpen ? __('Open') : __('Closed');
-    },
   },
   mounted() {
     /**
@@ -56,44 +53,18 @@ export default {
 </script>
 
 <template>
-  <div class="detail-page-header gl-flex-wrap">
-    <div class="detail-page-header-body">
-      <gl-badge class="issuable-status-badge gl-mr-3" :variant="isEpicOpen ? 'success' : 'info'">
-        <gl-icon :name="statusIcon" />
-        <span class="gl-display-none gl-sm-display-block gl-ml-2">{{ statusText }}</span>
-      </gl-badge>
-      <div class="issuable-meta">
-        <confidentiality-badge
-          v-if="confidential"
-          :workspace-type="$options.WORKSPACE_GROUP"
-          :issuable-type="$options.TYPE_EPIC"
-        />
-        {{ __('Created') }}
-        <timeago-tooltip :time="created" />
-        {{ __('by') }}
-        <strong class="text-nowrap">
-          <user-avatar-link
-            :link-href="author.url"
-            :img-src="author.src"
-            :img-size="24"
-            :tooltip-text="author.username"
-            :username="author.name"
-            img-css-classes="avatar-inline"
-          />
-        </strong>
-      </div>
-    </div>
-    <gl-button
-      :aria-label="__('Toggle sidebar')"
-      type="button"
-      class="float-right gl-display-block gl-sm-display-none! gl-align-self-center gutter-toggle issuable-gutter-toggle"
-      icon="chevron-double-lg-left"
-      @click="toggleSidebar({ sidebarCollapsed })"
-    />
-    <div
-      class="detail-page-header-actions gl-display-flex gl-flex-wrap gl-align-items-center gl-w-full gl-sm-w-auto"
-    >
+  <issuable-header
+    :author="formattedAuthor"
+    :confidential="confidential"
+    :created-at="created"
+    :issuable-state="state"
+    :issuable-type="$options.TYPE_EPIC"
+    :status-icon="statusIcon"
+    :workspace-type="$options.WORKSPACE_GROUP"
+    @toggle="toggleSidebar({ sidebarCollapsed })"
+  >
+    <template #header-actions>
       <epic-header-actions />
-    </div>
-  </div>
+    </template>
+  </issuable-header>
 </template>
diff --git a/ee/app/assets/javascripts/integrations/jira/issues_show/components/jira_issues_show_root.vue b/ee/app/assets/javascripts/integrations/jira/issues_show/components/jira_issues_show_root.vue
index 3761bb8f58d2ba3df262cf0d034e0cdd06549d78..2454a82a7f71ba8ee8d1245110f8a24269944b0d 100644
--- a/ee/app/assets/javascripts/integrations/jira/issues_show/components/jira_issues_show_root.vue
+++ b/ee/app/assets/javascripts/integrations/jira/issues_show/components/jira_issues_show_root.vue
@@ -6,7 +6,7 @@ import ExternalIssueAlert from 'ee/external_issues_show/components/external_issu
 import { fetchIssue } from 'ee/integrations/jira/issues_show/api';
 
 import JiraIssueSidebar from 'ee/integrations/jira/issues_show/components/sidebar/jira_issues_sidebar_root.vue';
-import { issuableStatusText, STATUS_OPEN } from '~/issues/constants';
+import { STATUS_OPEN } from '~/issues/constants';
 import IssuableShow from '~/vue_shared/issuable/show/components/issuable_show_root.vue';
 import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
 import { s__ } from '~/locale';
@@ -41,9 +41,6 @@ export default {
     isIssueOpen() {
       return this.issue.state === STATUS_OPEN;
     },
-    statusBadgeText() {
-      return issuableStatusText[this.issue.state];
-    },
     statusIcon() {
       return this.isIssueOpen ? 'issue-open-m' : 'mobile-issue-close';
     },
@@ -89,8 +86,6 @@ export default {
         :status-icon="statusIcon"
         status-icon-class="gl-sm-display-none"
       >
-        <template #status-badge>{{ statusBadgeText }}</template>
-
         <template #right-sidebar-items="{ sidebarExpanded, toggleSidebar }">
           <jira-issue-sidebar
             :sidebar-expanded="sidebarExpanded"
diff --git a/ee/app/assets/javascripts/integrations/zentao/issues_show/components/zentao_issues_show_root.vue b/ee/app/assets/javascripts/integrations/zentao/issues_show/components/zentao_issues_show_root.vue
index f99cd763f366c74f742b2a43c7abf6f9535ae192..e86e6a8572907474444166fec63d9666c3b1d069 100644
--- a/ee/app/assets/javascripts/integrations/zentao/issues_show/components/zentao_issues_show_root.vue
+++ b/ee/app/assets/javascripts/integrations/zentao/issues_show/components/zentao_issues_show_root.vue
@@ -5,7 +5,7 @@ import Note from 'ee/external_issues_show/components/note.vue';
 import ExternalIssueAlert from 'ee/external_issues_show/components/external_issue_alert.vue';
 import { fetchIssue } from 'ee/integrations/zentao/issues_show/api';
 import ZentaoIssueSidebar from 'ee/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root.vue';
-import { issuableStatusText, STATUS_OPEN } from '~/issues/constants';
+import { STATUS_OPEN } from '~/issues/constants';
 
 import IssuableShow from '~/vue_shared/issuable/show/components/issuable_show_root.vue';
 import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
@@ -41,9 +41,6 @@ export default {
     isIssueOpen() {
       return this.issue.state === STATUS_OPEN;
     },
-    statusBadgeText() {
-      return issuableStatusText[this.issue?.state];
-    },
     statusIcon() {
       return this.isIssueOpen ? 'issue-open-m' : 'mobile-issue-close';
     },
@@ -89,8 +86,6 @@ export default {
       <external-issue-alert issue-tracker-name="ZenTao" :issue-url="issue.webUrl" />
 
       <issuable-show :issuable="issue" :enable-edit="false" :status-icon="statusIcon">
-        <template v-if="statusBadgeText" #status-badge>{{ statusBadgeText }}</template>
-
         <template #right-sidebar-items>
           <zentao-issue-sidebar :issue="issue" />
         </template>
diff --git a/ee/spec/features/epics/epic_show_spec.rb b/ee/spec/features/epics/epic_show_spec.rb
index 464df416e2da24a5ad110a2aa7c76fb27977cd54..eeba159ab5a7bb766105520ec06a669282627834 100644
--- a/ee/spec/features/epics/epic_show_spec.rb
+++ b/ee/spec/features/epics/epic_show_spec.rb
@@ -186,10 +186,10 @@ def open_colors_dropdown
     it_behaves_like 'page meta description', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nos commodius agimus. Ex rebus enim timiditas, non ex vocabulis nascitur. Ita prorsus, inquam; Duo...'
 
     it 'shows epic status, date and author in header' do
-      page.within('.epic-page-container .detail-page-header-body') do
-        expect(find('.issuable-status-badge > span')).to have_content('Open')
-        expect(find('.issuable-meta')).to have_content('Created')
-        expect(find('.issuable-meta [data-testid="user-avatar-link-username"]')).to have_content('Rick Sanchez')
+      within('.detail-page-header-body') do
+        expect(page).to have_css('.issuable-status-badge', text: 'Open')
+        expect(page).to have_css('.issuable-meta', text: 'Created')
+        expect(page).to have_link('Rick Sanchez')
       end
     end
 
diff --git a/ee/spec/frontend/epic/components/epic_header_spec.js b/ee/spec/frontend/epic/components/epic_header_spec.js
index c3d0ad3316353e930c94b25fc94e5bbbc78c91fe..acea7d9c08be0623bd90ae07fe14554b8cbb93f1 100644
--- a/ee/spec/frontend/epic/components/epic_header_spec.js
+++ b/ee/spec/frontend/epic/components/epic_header_spec.js
@@ -1,93 +1,38 @@
-import { GlBadge, GlButton, GlIcon } from '@gitlab/ui';
 import { shallowMount } from '@vue/test-utils';
 import EpicHeader from 'ee/epic/components/epic_header.vue';
 import EpicHeaderActions from 'ee/epic/components/epic_header_actions.vue';
 import createStore from 'ee/epic/store';
-import { STATUS_CLOSED, STATUS_OPEN } from '~/issues/constants';
-import ConfidentialityBadge from '~/vue_shared/components/confidentiality_badge.vue';
-import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
-import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
-import { mockEpicMeta, mockEpicData } from '../mock_data';
+import IssuableHeader from '~/vue_shared/issuable/show/components/issuable_header.vue';
+import { mockEpicMeta } from '../mock_data';
 
 describe('EpicHeader component', () => {
   let wrapper;
 
-  const createComponent = (state = {}) => {
+  const createComponent = () => {
     const store = createStore();
     store.dispatch('setEpicMeta', mockEpicMeta);
-    store.dispatch('setEpicData', { ...mockEpicData, ...state });
-
     wrapper = shallowMount(EpicHeader, { store });
   };
 
-  const findConfidentialityBadge = () => wrapper.findComponent(ConfidentialityBadge);
   const findEpicHeaderActions = () => wrapper.findComponent(EpicHeaderActions);
-  const findStatusBadge = () => wrapper.findComponent(GlBadge);
-  const findStatusBadgeIcon = () => wrapper.findComponent(GlIcon);
-  const findTimeagoTooltip = () => wrapper.findComponent(TimeagoTooltip);
-  const findToggleSidebarButton = () => wrapper.findComponent(GlButton);
-  const findUserAvatarLink = () => wrapper.findComponent(UserAvatarLink);
-
-  describe('status badge', () => {
-    describe('when epic is open', () => {
-      beforeEach(() => {
-        createComponent({ state: STATUS_OPEN });
-      });
-
-      it('renders `Open` text', () => {
-        expect(findStatusBadge().text()).toBe('Open');
-      });
-
-      it('renders correct icon', () => {
-        expect(findStatusBadgeIcon().props('name')).toBe('epic');
-      });
-    });
-
-    describe('when epic is closed', () => {
-      beforeEach(() => {
-        createComponent({ state: STATUS_CLOSED });
-      });
-
-      it('renders `Closed` text', () => {
-        expect(findStatusBadge().text()).toBe('Closed');
-      });
+  const findIssuableHeader = () => wrapper.findComponent(IssuableHeader);
 
-      it('renders correct icon', () => {
-        expect(findStatusBadgeIcon().props('name')).toBe('epic-closed');
-      });
-    });
+  beforeEach(() => {
+    createComponent();
   });
 
-  it('renders correct badge when epic is confidential', () => {
-    createComponent({ confidential: true });
-
-    expect(findConfidentialityBadge().props()).toMatchObject({
-      workspaceType: 'group',
+  it('renders IssuableHeader component', () => {
+    expect(findIssuableHeader().props()).toMatchObject({
+      confidential: false,
+      createdAt: '2015-07-03T10:00:00.000Z',
+      issuableState: 'opened',
       issuableType: 'epic',
+      statusIcon: 'epic',
+      workspaceType: 'group',
     });
   });
 
-  it('renders timeago tooltip', () => {
-    createComponent();
-
-    expect(findTimeagoTooltip().exists()).toBe(true);
-  });
-
-  it('renders user avatar link', () => {
-    createComponent();
-
-    expect(findUserAvatarLink().exists()).toBe(true);
-  });
-
-  it('renders toggle sidebar button', () => {
-    createComponent();
-
-    expect(findToggleSidebarButton().attributes('aria-label')).toBe('Toggle sidebar');
-  });
-
   it('renders actions dropdown', () => {
-    createComponent();
-
     expect(findEpicHeaderActions().exists()).toBe(true);
   });
 });
diff --git a/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js b/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js
index fa38ab8d44d3116390095a11bd16d0e8b7b83b9e..d2b7b2e89c87c6c4f4a78e37feb2fc05e6547f47 100644
--- a/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js
+++ b/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js
@@ -1,13 +1,16 @@
 import { GlButton, GlBadge, GlIcon, GlAvatarLabeled, GlAvatarLink } from '@gitlab/ui';
 import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
 import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
+import { TYPE_ISSUE, WORKSPACE_PROJECT } from '~/issues/constants';
+import ConfidentialityBadge from '~/vue_shared/components/confidentiality_badge.vue';
 import IssuableHeader from '~/vue_shared/issuable/show/components/issuable_header.vue';
-
 import { mockIssuableShowProps, mockIssuable } from '../mock_data';
 
 const issuableHeaderProps = {
   ...mockIssuable,
   ...mockIssuableShowProps,
+  issuableType: TYPE_ISSUE,
+  workspaceType: WORKSPACE_PROJECT,
 };
 
 describe('IssuableHeader', () => {
@@ -53,6 +56,14 @@ describe('IssuableHeader', () => {
       setHTMLFixture('<button class="js-toggle-right-sidebar-button">Collapse sidebar</button>');
     });
 
+    it('emits a "toggle" event', () => {
+      createComponent();
+
+      findButton().vm.$emit('click');
+
+      expect(wrapper.emitted('toggle')).toEqual([[]]);
+    });
+
     it('dispatches `click` event on sidebar toggle button', () => {
       createComponent();
       const toggleSidebarButtonEl = document.querySelector('.js-toggle-right-sidebar-button');
@@ -94,14 +105,12 @@ describe('IssuableHeader', () => {
     });
 
     it('renders confidential icon when issuable is confidential', () => {
-      createComponent({
-        confidential: true,
-      });
+      createComponent({ confidential: true });
 
-      const confidentialEl = wrapper.findByTestId('confidential');
-
-      expect(confidentialEl.exists()).toBe(true);
-      expect(confidentialEl.findComponent(GlIcon).props('name')).toBe('eye-slash');
+      expect(wrapper.findComponent(ConfidentialityBadge).props()).toEqual({
+        issuableType: 'issue',
+        workspaceType: 'project',
+      });
     });
 
     it('renders issuable author avatar', () => {