diff --git a/app/assets/javascripts/work_items/components/shared/work_item_link_child_contents.vue b/app/assets/javascripts/work_items/components/shared/work_item_link_child_contents.vue
index 4672773b2fdd7401a0d394ea9aed8ff22cbbcd92..ed1bd120d2575ffc5b56933a494c917a88529426 100644
--- a/app/assets/javascripts/work_items/components/shared/work_item_link_child_contents.vue
+++ b/app/assets/javascripts/work_items/components/shared/work_item_link_child_contents.vue
@@ -17,15 +17,7 @@ import WorkItemLinkChildMetadata from 'ee_else_ce/work_items/components/shared/w
 import WorkItemTypeIcon from '../work_item_type_icon.vue';
 import WorkItemStateBadge from '../work_item_state_badge.vue';
 import { findLinkedItemsWidget } from '../../utils';
-import {
-  STATE_OPEN,
-  WIDGET_TYPE_PROGRESS,
-  WIDGET_TYPE_HIERARCHY,
-  WIDGET_TYPE_HEALTH_STATUS,
-  WIDGET_TYPE_MILESTONE,
-  WIDGET_TYPE_ASSIGNEES,
-  WIDGET_TYPE_LABELS,
-} from '../../constants';
+import { STATE_OPEN, WIDGET_TYPE_ASSIGNEES, WIDGET_TYPE_LABELS } from '../../constants';
 import WorkItemRelationshipIcons from './work_item_relationship_icons.vue';
 
 export default {
@@ -84,8 +76,7 @@ export default {
     },
     metadataWidgets() {
       return this.childItem.widgets?.reduce((metadataWidgets, widget) => {
-        // Skip Hierarchy widget as it is not part of metadata.
-        if (widget.type && widget.type !== WIDGET_TYPE_HIERARCHY) {
+        if (widget.type) {
           // eslint-disable-next-line no-param-reassign
           metadataWidgets[widget.type] = widget;
         }
@@ -121,18 +112,6 @@ export default {
     childItemTypeColorClass() {
       return this.isChildItemOpen ? 'gl-text-secondary' : 'gl-text-gray-300';
     },
-    hasMetadata() {
-      if (this.metadataWidgets) {
-        return (
-          Number.isInteger(this.metadataWidgets[WIDGET_TYPE_PROGRESS]?.progress) ||
-          Boolean(this.metadataWidgets[WIDGET_TYPE_HEALTH_STATUS]?.healthStatus) ||
-          Boolean(this.metadataWidgets[WIDGET_TYPE_MILESTONE]?.milestone) ||
-          this.metadataWidgets[WIDGET_TYPE_ASSIGNEES]?.assignees?.nodes.length > 0 ||
-          this.metadataWidgets[WIDGET_TYPE_LABELS]?.labels?.nodes.length > 0
-        );
-      }
-      return false;
-    },
     displayLabels() {
       return this.showLabels && this.labels.length;
     },
diff --git a/app/assets/javascripts/work_items/components/shared/work_item_link_child_metadata.vue b/app/assets/javascripts/work_items/components/shared/work_item_link_child_metadata.vue
index dabef0386ecdf263c9e2748f07eb40c3fb0134b5..8acfe5825cf1ce2f8d470572876849e8cb6fb732 100644
--- a/app/assets/javascripts/work_items/components/shared/work_item_link_child_metadata.vue
+++ b/app/assets/javascripts/work_items/components/shared/work_item_link_child_metadata.vue
@@ -2,12 +2,14 @@
 import { GlTooltipDirective } from '@gitlab/ui';
 
 import ItemMilestone from '~/issuable/components/issue_milestone.vue';
+import WorkItemRolledUpCount from '~/work_items/components/work_item_links/work_item_rolled_up_count.vue';
 
-import { WIDGET_TYPE_MILESTONE } from '../../constants';
+import { WIDGET_TYPE_MILESTONE, WIDGET_TYPE_HIERARCHY } from '../../constants';
 
 export default {
   components: {
     ItemMilestone,
+    WorkItemRolledUpCount,
   },
   directives: {
     GlTooltip: GlTooltipDirective,
@@ -31,6 +33,15 @@ export default {
     milestone() {
       return this.metadataWidgets[WIDGET_TYPE_MILESTONE]?.milestone;
     },
+    hierarchyWidget() {
+      return this.metadataWidgets[WIDGET_TYPE_HIERARCHY];
+    },
+    showRolledUpCounts() {
+      return this.hierarchyWidget && this.rolledUpCountsByType.length > 0;
+    },
+    rolledUpCountsByType() {
+      return this.hierarchyWidget?.rolledUpCountsByType || [];
+    },
   },
 };
 </script>
@@ -39,6 +50,11 @@ export default {
   <div class="gl-flex gl-justify-between">
     <div class="gl-flex gl-flex-wrap gl-items-center gl-gap-3 gl-text-sm gl-text-secondary">
       <span>{{ reference }}</span>
+      <work-item-rolled-up-count
+        v-if="showRolledUpCounts"
+        :rolled-up-counts-by-type="rolledUpCountsByType"
+        info-type="detailed"
+      />
       <item-milestone
         v-if="milestone"
         :milestone="milestone"
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_children_wrapper.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_children_wrapper.vue
index e8fc5bb7f35060bf649debd14338e034d0dbc403..12e60cef93ff10cd6cca3a53d29c73e657ca0192 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_children_wrapper.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_children_wrapper.vue
@@ -365,6 +365,7 @@ export default {
                     __typename: 'WorkItemWidgetHierarchy',
                     type: 'HIERARCHY',
                     hasChildren: false,
+                    rolledUpCountsByType: [],
                     parent: { id: toParentId },
                     children: [],
                   },
@@ -382,6 +383,7 @@ export default {
                     __typename: 'WorkItemWidgetHierarchy',
                     type: 'HIERARCHY',
                     hasChildren: true,
+                    rolledUpCountsByType: [],
                     parent: null,
                     children: {
                       __typename: 'WorkItemConnection',
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_rolled_up_count.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_rolled_up_count.vue
new file mode 100644
index 0000000000000000000000000000000000000000..d47e377b5235ab6a70fff640490d965cf80e5aba
--- /dev/null
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_rolled_up_count.vue
@@ -0,0 +1,120 @@
+<script>
+import { GlPopover, GlBadge, GlIcon } from '@gitlab/ui';
+import { __ } from '~/locale';
+import WorkItemTypeIcon from '~/work_items/components/work_item_type_icon.vue';
+import WorkItemRolledUpCountInfo from './work_item_rolled_up_count_info.vue';
+
+export default {
+  i18n: {
+    countPermissionText: __('Roll up totals may reflect child items you don’t have access to.'),
+    noChildItemsText: __('No child items are currently assigned.'),
+  },
+  components: {
+    GlPopover,
+    GlBadge,
+    GlIcon,
+    WorkItemTypeIcon,
+    WorkItemRolledUpCountInfo,
+  },
+  props: {
+    infoType: {
+      type: String,
+      required: false,
+      default: 'badge',
+    },
+    rolledUpCountsByType: {
+      type: Array,
+      required: true,
+      default: () => [],
+    },
+  },
+  computed: {
+    totalCountAllByType() {
+      return [...this.rolledUpCountsByType].reduce(
+        (total, rollUpCounts) => total + rollUpCounts.countsByState.all,
+        0,
+      );
+    },
+    showDetailedCount() {
+      return this.infoType === 'detailed';
+    },
+    filteredRollUpCountsByType() {
+      return this.rolledUpCountsByType.filter((rollUpCount) =>
+        this.rolledUpCountExists(rollUpCount),
+      );
+    },
+  },
+  methods: {
+    workItemTypeCount(workItemTypeName) {
+      return this.rolledUpCountsByType.find(
+        (rollUpCount) => rollUpCount?.workItemType?.name === workItemTypeName,
+      );
+    },
+    rolledUpCountExists(rolledUpCount) {
+      return rolledUpCount?.countsByState?.all > 0;
+    },
+  },
+};
+</script>
+<template>
+  <div>
+    <span
+      v-if="showDetailedCount"
+      ref="info"
+      tabindex="0"
+      class="gl-flex gl-gap-3 gl-text-sm"
+      data-testid="work-item-rolled-up-detailed-count"
+    >
+      <span
+        v-for="rolledUpCount in filteredRollUpCountsByType"
+        :key="rolledUpCount.workItemType.name"
+      >
+        <work-item-type-icon :work-item-icon-name="rolledUpCount.workItemType.iconName" />
+        {{ rolledUpCount.countsByState.all }}
+      </span>
+    </span>
+
+    <span
+      v-else
+      ref="countBadge"
+      tabindex="0"
+      class="gl-inline-block"
+      data-testid="work-item-rolled-up-badge-count"
+    >
+      <gl-badge variant="muted">{{ totalCountAllByType }}</gl-badge>
+    </span>
+
+    <gl-popover
+      v-if="showDetailedCount"
+      triggers="hover focus"
+      :target="() => $refs.info"
+      data-testid="detailed-popover"
+    >
+      <work-item-rolled-up-count-info
+        :filtered-roll-up-counts-by-type="filteredRollUpCountsByType"
+      />
+    </gl-popover>
+
+    <gl-popover
+      v-else
+      triggers="hover focus"
+      :target="() => $refs.countBadge"
+      data-testid="badge-popover"
+    >
+      <work-item-rolled-up-count-info
+        :filtered-roll-up-counts-by-type="filteredRollUpCountsByType"
+      />
+      <div
+        class="gl-text-secondary"
+        :class="{ 'gl-mt-3': totalCountAllByType > 0 }"
+        data-testid="badge-warning"
+      >
+        <gl-icon v-if="totalCountAllByType > 0" name="information-o" class="gl-mr-2" :size="16" />{{
+          totalCountAllByType > 0
+            ? $options.i18n.countPermissionText
+            : $options.i18n.noChildItemsText
+        }}
+      </div>
+    </gl-popover>
+  </div>
+</template>
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_rolled_up_count_info.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_rolled_up_count_info.vue
new file mode 100644
index 0000000000000000000000000000000000000000..964a2c3c7ae501fb00a4a46fe83f19331cac8e40
--- /dev/null
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_rolled_up_count_info.vue
@@ -0,0 +1,42 @@
+<script>
+import { s__ } from '~/locale';
+import { sprintfWorkItem } from '~/work_items/constants';
+import WorkItemTypeIcon from '~/work_items/components/work_item_type_icon.vue';
+
+export default {
+  components: {
+    WorkItemTypeIcon,
+  },
+  props: {
+    filteredRollUpCountsByType: {
+      type: Array,
+      required: true,
+    },
+  },
+  methods: {
+    getItemsClosedLabel(workItemTypeName) {
+      return sprintfWorkItem(s__('WorkItem| %{workItemType}s closed'), workItemTypeName);
+    },
+  },
+};
+</script>
+
+<template>
+  <div
+    v-if="filteredRollUpCountsByType.length > 0"
+    class="gl-flex gl-flex-col gl-gap-y-2"
+    data-testid="rolled-up-count-info"
+  >
+    <div
+      v-for="rolledUpCount in filteredRollUpCountsByType"
+      :key="rolledUpCount.workItemType.name"
+      data-testid="rolled-up-type-info"
+    >
+      <work-item-type-icon :work-item-icon-name="rolledUpCount.workItemType.iconName" />
+      <span class="gl-font-bold"
+        >{{ rolledUpCount.countsByState.closed }}/{{ rolledUpCount.countsByState.all }}</span
+      >
+      {{ getItemsClosedLabel(rolledUpCount.workItemType.name) }}
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_rolled_up_data.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_rolled_up_data.vue
index 7ec390eaafe56561c524ff3052eb9454d6fa626d..38d2ca02f35ec8116ff7b820a8028c3ff148d810 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_rolled_up_data.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_rolled_up_data.vue
@@ -4,12 +4,14 @@ import { s__, __ } from '~/locale';
 import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
 import { findWidget } from '~/issues/list/utils';
 import { i18n, WIDGET_TYPE_WEIGHT, WORK_ITEM_TYPE_VALUE_EPIC } from '../../constants';
+import WorkItemRolledUpCount from './work_item_rolled_up_count.vue';
 
 export default {
   components: {
     GlIcon,
     GlTooltip,
     GlPopover,
+    WorkItemRolledUpCount,
   },
   i18n: {
     progressLabel: s__('WorkItem|Progress'),
@@ -33,9 +35,18 @@ export default {
       required: false,
       default: null,
     },
+    rolledUpCountsByType: {
+      type: Array,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      workItem: {},
+      error: null,
+    };
   },
   apollo: {
-    // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
     workItem: {
       query: workItemByIidQuery,
       variables() {
@@ -87,6 +98,10 @@ export default {
 
 <template>
   <div class="gl-flex">
+    <!-- Rolled up count -->
+    <work-item-rolled-up-count :rolled-up-counts-by-type="rolledUpCountsByType" />
+    <!-- END Rolled up count -->
+
     <!-- Rolled up weight -->
     <span
       v-if="shouldRolledUpWeightBeVisible"
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue
index 3b8eb8c33e868f97adc7dd6b878af207b6c61422..bf1c44aebd75ad32fcab65b0b0d84beb8e31be1e 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue
@@ -3,6 +3,7 @@ import { GlAlert } from '@gitlab/ui';
 import { sprintf, s__ } from '~/locale';
 import { createAlert } from '~/alert';
 import CrudComponent from '~/vue_shared/components/crud_component.vue';
+import { findWidget } from '~/issues/list/utils';
 import {
   FORM_TYPES,
   WORK_ITEMS_TREE_TEXT,
@@ -14,6 +15,7 @@ import {
   CHILD_ITEMS_ANCHOR,
   WORKITEM_TREE_SHOWLABELS_LOCALSTORAGEKEY,
   WORK_ITEM_TYPE_VALUE_EPIC,
+  WIDGET_TYPE_HIERARCHY,
 } from '../../constants';
 import {
   findHierarchyWidgets,
@@ -139,6 +141,12 @@ export default {
     },
   },
   computed: {
+    workItemHierarchy() {
+      return findWidget(WIDGET_TYPE_HIERARCHY, this.workItem);
+    },
+    rolledUpCountsByType() {
+      return this.workItemHierarchy?.rolledUpCountsByType || [];
+    },
     childrenIds() {
       return this.children.map((c) => c.id);
     },
@@ -276,6 +284,7 @@ export default {
         :work-item-id="workItemId"
         :work-item-iid="workItemIid"
         :work-item-type="workItemType"
+        :rolled-up-counts-by-type="rolledUpCountsByType"
         :full-path="fullPath"
       />
     </template>
diff --git a/app/assets/javascripts/work_items/graphql/cache_utils.js b/app/assets/javascripts/work_items/graphql/cache_utils.js
index 31cd9e1b052e33a9d51fd4e637011dc15092473c..bd18032417e3739fdca608f34eabf8096911c5cf 100644
--- a/app/assets/javascripts/work_items/graphql/cache_utils.js
+++ b/app/assets/javascripts/work_items/graphql/cache_utils.js
@@ -459,6 +459,7 @@ export const setNewWorkItemCache = async (
           hasChildren: false,
           hasParent: false,
           parent: null,
+          rolledUpCountsByType: [],
           children: {
             nodes: [],
             __typename: 'WorkItemConnection',
diff --git a/app/assets/javascripts/work_items/graphql/work_item_hierarchy.fragment.graphql b/app/assets/javascripts/work_items/graphql/work_item_hierarchy.fragment.graphql
index dd027573aac722f92ac595599cefecaf7757f269..024453272bf82ed27f1219e33ecc742fad12d070 100644
--- a/app/assets/javascripts/work_items/graphql/work_item_hierarchy.fragment.graphql
+++ b/app/assets/javascripts/work_items/graphql/work_item_hierarchy.fragment.graphql
@@ -23,6 +23,17 @@ fragment WorkItemHierarchy on WorkItem {
     ... on WorkItemWidgetHierarchy {
       type
       hasChildren
+      rolledUpCountsByType {
+        countsByState {
+          all
+          closed
+        }
+        workItemType {
+          id
+          name
+          iconName
+        }
+      }
       parent {
         id
       }
@@ -55,6 +66,17 @@ fragment WorkItemHierarchy on WorkItem {
             ... on WorkItemWidgetHierarchy {
               type
               hasChildren
+              rolledUpCountsByType {
+                countsByState {
+                  all
+                  closed
+                }
+                workItemType {
+                  id
+                  name
+                  iconName
+                }
+              }
             }
             ...WorkItemMetadataWidgets
           }
diff --git a/ee/spec/frontend/work_items/components/work_item_links/work_item_rolled_up_data_spec.js b/ee/spec/frontend/work_items/components/work_item_links/work_item_rolled_up_data_spec.js
index 588b6553f31f45c4ec4b4b85f1773a2e565617cb..6c2bdbed7f805d3d845afe635a3c0ba18d16aa41 100644
--- a/ee/spec/frontend/work_items/components/work_item_links/work_item_rolled_up_data_spec.js
+++ b/ee/spec/frontend/work_items/components/work_item_links/work_item_rolled_up_data_spec.js
@@ -3,6 +3,7 @@ import { GlIcon } from '@gitlab/ui';
 import VueApollo from 'vue-apollo';
 import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
 import WorkItemRolledUpData from '~/work_items/components/work_item_links/work_item_rolled_up_data.vue';
+import WorkItemRolledUpCount from '~/work_items/components/work_item_links/work_item_rolled_up_count.vue';
 import createMockApollo from 'helpers/mock_apollo_helper';
 import waitForPromises from 'helpers/wait_for_promises';
 import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
@@ -17,6 +18,7 @@ describe('WorkItemRollUpData', () => {
   const findRolledUpWeightValue = () => wrapper.findByTestId('work-item-weight-value');
   const findRolledUpProgress = () => wrapper.findByTestId('work-item-rollup-progress');
   const findRolledUpProgressValue = () => wrapper.findByTestId('work-item-progress-value');
+  const findRolledUpCount = () => wrapper.findComponent(WorkItemRolledUpCount);
 
   const workItemQueryResponse = workItemByIidResponseFactory({
     canUpdate: true,
@@ -31,6 +33,7 @@ describe('WorkItemRollUpData', () => {
   } = {}) => {
     wrapper = shallowMountExtended(WorkItemRolledUpData, {
       propsData: {
+        rolledUpCountsByType: [],
         fullPath: 'test/project',
         workItemType,
         workItemIid,
@@ -40,6 +43,12 @@ describe('WorkItemRollUpData', () => {
     });
   };
 
+  it('renders rolled up count component', () => {
+    createComponent();
+
+    expect(findRolledUpCount().exists()).toBe(true);
+  });
+
   describe('rolled up weight', () => {
     it.each`
       isRollUp | rolledUpWeight | rollUpWeightVisible | expected
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 252505561c62e3d7365289d57fbf37043791c65f..d5542cf38963ed396118e5b26cbd881640ada2e4 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -35814,6 +35814,9 @@ msgstr ""
 msgid "No child epics match applied filters"
 msgstr ""
 
+msgid "No child items are currently assigned."
+msgstr ""
+
 msgid "No comment templates found."
 msgstr ""
 
@@ -46148,6 +46151,9 @@ msgstr ""
 msgid "Roles and Permissions"
 msgstr ""
 
+msgid "Roll up totals may reflect child items you don’t have access to."
+msgstr ""
+
 msgid "Rollback"
 msgstr ""
 
@@ -61500,6 +61506,9 @@ msgstr ""
 msgid "WorkItems|Ancestors not available"
 msgstr ""
 
+msgid "WorkItem| %{workItemType}s closed"
+msgstr ""
+
 msgid "WorkItem|%{count} more assignees"
 msgstr ""
 
diff --git a/spec/frontend/work_items/components/shared/work_item_link_child_contents_spec.js b/spec/frontend/work_items/components/shared/work_item_link_child_contents_spec.js
index c95c45d244bedadd281517dac8ca7a97c04272a8..82cda902b2cb1dd9bf72a9f3cef524e6471425f8 100644
--- a/spec/frontend/work_items/components/shared/work_item_link_child_contents_spec.js
+++ b/spec/frontend/work_items/components/shared/work_item_link_child_contents_spec.js
@@ -19,6 +19,7 @@ import {
   closedWorkItemTask,
   otherNamespaceChild,
   workItemObjectiveMetadataWidgets,
+  workItemObjectiveWithoutChild,
 } from '../../mock_data';
 
 jest.mock('~/alert');
@@ -133,7 +134,7 @@ describe('WorkItemLinkChildContents', () => {
   describe('item metadata', () => {
     it('renders item metadata component when item has metadata present', () => {
       createComponent({
-        childItem: workItemObjectiveWithChild,
+        childItem: workItemObjectiveWithoutChild,
         workItemType: WORK_ITEM_TYPE_VALUE_OBJECTIVE,
       });
 
diff --git a/spec/frontend/work_items/components/shared/work_item_link_child_metadata_spec.js b/spec/frontend/work_items/components/shared/work_item_link_child_metadata_spec.js
index a0d927b41746d73e75f44653ff28ec5c746d5e59..ab4de9ceffde334ba89c6f77461ad911a689ffce 100644
--- a/spec/frontend/work_items/components/shared/work_item_link_child_metadata_spec.js
+++ b/spec/frontend/work_items/components/shared/work_item_link_child_metadata_spec.js
@@ -2,6 +2,7 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
 
 import ItemMilestone from '~/issuable/components/issue_milestone.vue';
 import WorkItemLinkChildMetadata from '~/work_items/components/shared/work_item_link_child_metadata.vue';
+import WorkItemRolledUpCount from '~/work_items/components/work_item_links/work_item_rolled_up_count.vue';
 
 import { workItemObjectiveMetadataWidgets } from '../../mock_data';
 
@@ -11,6 +12,8 @@ describe('WorkItemLinkChildMetadata', () => {
 
   let wrapper;
 
+  const findRolledUpCount = () => wrapper.findComponent(WorkItemRolledUpCount);
+
   const createComponent = ({ metadataWidgets = workItemObjectiveMetadataWidgets } = {}) => {
     wrapper = shallowMountExtended(WorkItemLinkChildMetadata, {
       propsData: {
@@ -40,4 +43,39 @@ describe('WorkItemLinkChildMetadata', () => {
     expect(milestoneLink.exists()).toBe(true);
     expect(milestoneLink.props('milestone')).toEqual(mockMilestone);
   });
+
+  it('does not render rolled up count if there are no rolled up items', () => {
+    expect(findRolledUpCount().exists()).toBe(false);
+  });
+
+  it('renders rolled up count if there are rolled up items', () => {
+    createComponent({
+      metadataWidgets: {
+        ...workItemObjectiveMetadataWidgets,
+        HIERARCHY: {
+          type: 'HIERARCHY',
+          hasChildren: false,
+          rolledUpCountsByType: [
+            {
+              countsByState: {
+                all: 4,
+                closed: 0,
+                __typename: 'WorkItemStateCountsType',
+              },
+              workItemType: {
+                id: 'gid://gitlab/WorkItems::Type/8',
+                name: 'Epic',
+                iconName: 'issue-type-epic',
+                __typename: 'WorkItemType',
+              },
+              __typename: 'WorkItemTypeCountsByState',
+            },
+          ],
+          __typename: 'WorkItemWidgetHierarchy',
+        },
+      },
+    });
+
+    expect(findRolledUpCount().exists()).toBe(true);
+  });
 });
diff --git a/spec/frontend/work_items/components/work_item_links/work_item_rolled_up_count_info_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_rolled_up_count_info_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..c7166d7d4bac9cca79a28a20226eb8897dd9e55a
--- /dev/null
+++ b/spec/frontend/work_items/components/work_item_links/work_item_rolled_up_count_info_spec.js
@@ -0,0 +1,36 @@
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import WorkItemRolledUpCountInfo from '~/work_items/components/work_item_links/work_item_rolled_up_count_info.vue';
+import { mockRolledUpCountsByType } from 'jest/work_items/mock_data';
+
+describe('Work item rolled up count info', () => {
+  let wrapper;
+
+  const createComponent = ({ filteredRollUpCountsByType = mockRolledUpCountsByType } = {}) => {
+    wrapper = shallowMountExtended(WorkItemRolledUpCountInfo, {
+      propsData: {
+        filteredRollUpCountsByType,
+      },
+    });
+  };
+
+  const findRolledUpCountInfo = () => wrapper.findByTestId('rolled-up-count-info');
+  const findCountInfo = () => wrapper.findAllByTestId('rolled-up-type-info');
+
+  it('renders the info in detail', () => {
+    createComponent();
+
+    expect(findRolledUpCountInfo().exists()).toBe(true);
+  });
+
+  it('does not render the info if there are no counts', () => {
+    createComponent({ filteredRollUpCountsByType: [] });
+
+    expect(findRolledUpCountInfo().exists()).toBe(false);
+  });
+
+  it('renders the correct number of counts', () => {
+    createComponent();
+
+    expect(findCountInfo().length).toBe(mockRolledUpCountsByType.length);
+  });
+});
diff --git a/spec/frontend/work_items/components/work_item_links/work_item_rolled_up_count_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_rolled_up_count_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..fc6e90209ae420ac1331200e0bd7acfb24a98955
--- /dev/null
+++ b/spec/frontend/work_items/components/work_item_links/work_item_rolled_up_count_spec.js
@@ -0,0 +1,89 @@
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import WorkItemRolledUpCount from '~/work_items/components/work_item_links/work_item_rolled_up_count.vue';
+import WorkItemRolledUpCountInfo from '~/work_items/components/work_item_links/work_item_rolled_up_count_info.vue';
+import { mockRolledUpCountsByType } from 'jest/work_items/mock_data';
+
+describe('Work Item rolled up count', () => {
+  let wrapper;
+
+  const createComponent = ({
+    infoType = 'badge',
+    rolledUpCountsByType = mockRolledUpCountsByType,
+  } = {}) => {
+    wrapper = shallowMountExtended(WorkItemRolledUpCount, {
+      propsData: {
+        infoType,
+        rolledUpCountsByType,
+      },
+    });
+  };
+
+  const findRolledUpCountBadgeView = () => wrapper.findByTestId('work-item-rolled-up-badge-count');
+  const findRolledUpCountDetailedView = () =>
+    wrapper.findByTestId('work-item-rolled-up-detailed-count');
+  const findBadgePopover = () => wrapper.findByTestId('badge-popover');
+  const findDetailedPopover = () => wrapper.findByTestId('detailed-popover');
+  const findBadgePopoverWarning = () => wrapper.findByTestId('badge-warning');
+  const findBadgePopoverRolledUpCountInfo = () =>
+    findBadgePopover().findComponent(WorkItemRolledUpCountInfo);
+  const findDetailedPopoverRolledUpCountInfo = () =>
+    findDetailedPopover().findComponent(WorkItemRolledUpCountInfo);
+
+  describe('Default', () => {
+    it('renders count in `badge` view by default', () => {
+      createComponent();
+
+      expect(findRolledUpCountBadgeView().exists()).toBe(true);
+      expect(findRolledUpCountDetailedView().exists()).toBe(false);
+    });
+
+    it('renders count in `detailed` view when passed appropriate props', () => {
+      createComponent({ infoType: 'detailed' });
+
+      expect(findRolledUpCountBadgeView().exists()).toBe(false);
+      expect(findRolledUpCountDetailedView().exists()).toBe(true);
+    });
+  });
+
+  describe('badge view', () => {
+    beforeEach(() => {
+      createComponent();
+    });
+
+    it('renders the badge popover', () => {
+      expect(findBadgePopover().exists()).toBe(true);
+    });
+
+    it('renders the rolled up count info component', () => {
+      expect(findBadgePopoverRolledUpCountInfo().exists()).toBe(true);
+    });
+
+    it('renders the default badge popover warning when rolled up counts exist in header', () => {
+      expect(findBadgePopoverWarning().exists()).toBe(true);
+      expect(findBadgePopoverWarning().text()).toBe(
+        'Roll up totals may reflect child items you don’t have access to.',
+      );
+    });
+
+    it('when the rolled up count is zero shows a different warning', () => {
+      createComponent({ rolledUpCountsByType: [] });
+
+      expect(findBadgePopoverWarning().exists()).toBe(true);
+      expect(findBadgePopoverWarning().text()).toBe('No child items are currently assigned.');
+    });
+  });
+
+  describe('detailed view', () => {
+    beforeEach(() => {
+      createComponent({ infoType: 'detailed' });
+    });
+
+    it('renders the detailed info popover', () => {
+      expect(findDetailedPopover().exists()).toBe(true);
+    });
+
+    it('renders the rolled up count info component and not badge popover info component', () => {
+      expect(findDetailedPopoverRolledUpCountInfo().exists()).toBe(true);
+    });
+  });
+});
diff --git a/spec/frontend/work_items/components/work_item_links/work_item_tree_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_tree_spec.js
index 1c2e0420326a08c35837b5ad6279aa86c5501ff1..037941475c3e8c639e53b5158c08723d2f91fd38 100644
--- a/spec/frontend/work_items/components/work_item_links/work_item_tree_spec.js
+++ b/spec/frontend/work_items/components/work_item_links/work_item_tree_spec.js
@@ -11,6 +11,7 @@ import WorkItemChildrenWrapper from '~/work_items/components/work_item_links/wor
 import WorkItemLinksForm from '~/work_items/components/work_item_links/work_item_links_form.vue';
 import WorkItemActionsSplitButton from '~/work_items/components/work_item_links/work_item_actions_split_button.vue';
 import WorkItemMoreActions from '~/work_items/components/shared/work_item_more_actions.vue';
+import WorkItemRolledUpData from '~/work_items/components/work_item_links/work_item_rolled_up_data.vue';
 import getWorkItemTreeQuery from '~/work_items/graphql/work_item_tree.query.graphql';
 import {
   FORM_TYPES,
@@ -50,6 +51,7 @@ describe('WorkItemTree', () => {
   const findWorkItemLinkChildrenWrapper = () => wrapper.findComponent(WorkItemChildrenWrapper);
   const findMoreActions = () => wrapper.findComponent(WorkItemMoreActions);
   const findCrudComponent = () => wrapper.findComponent(CrudComponent);
+  const findRolledUpData = () => wrapper.findComponent(WorkItemRolledUpData);
 
   const createComponent = async ({
     workItemType = 'Objective',
@@ -340,4 +342,17 @@ describe('WorkItemTree', () => {
 
     expect(findCrudComponent().exists()).toBe(true);
   });
+
+  it('renders rolled up data', () => {
+    createComponent();
+
+    expect(findRolledUpData().exists()).toBe(true);
+    expect(findRolledUpData().props()).toEqual({
+      workItemId: 'gid://gitlab/WorkItem/2',
+      workItemIid: '2',
+      workItemType: 'Objective',
+      rolledUpCountsByType: [],
+      fullPath: 'test/project',
+    });
+  });
 });
diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js
index 4249b152dcfbf2d37e97da8fb2fdd0f3f4053edc..fd1a76de78fbea99cbdc532a2ce1933bc9c34c14 100644
--- a/spec/frontend/work_items/mock_data.js
+++ b/spec/frontend/work_items/mock_data.js
@@ -1635,6 +1635,12 @@ export const workItemObjectiveMetadataWidgets = {
     __typename: 'WorkItemWidgetLinkedItems',
     ...mockLinkedItems,
   },
+  HIERARCHY: {
+    type: 'HIERARCHY',
+    hasChildren: false,
+    rolledUpCountsByType: [],
+    __typename: 'WorkItemWidgetHierarchy',
+  },
 };
 
 export const confidentialWorkItemTask = {
@@ -1717,6 +1723,7 @@ export const workItemTask = {
     {
       type: 'HIERARCHY',
       hasChildren: false,
+      rolledUpCountsByType: [],
       __typename: 'WorkItemWidgetHierarchy',
     },
   ],
@@ -1835,6 +1842,7 @@ export const childrenWorkItemsObjectives = [
       {
         type: 'HIERARCHY',
         hasChildren: false,
+        rolledUpCountsByType: [],
         __typename: 'WorkItemWidgetHierarchy',
       },
     ],
@@ -1861,6 +1869,7 @@ export const childrenWorkItemsObjectives = [
       {
         type: 'HIERARCHY',
         hasChildren: false,
+        rolledUpCountsByType: [],
         __typename: 'WorkItemWidgetHierarchy',
       },
     ],
@@ -1917,6 +1926,7 @@ export const workItemHierarchyResponse = {
             type: 'HIERARCHY',
             parent: null,
             hasChildren: true,
+            rolledUpCountsByType: [],
             children: {
               nodes: childrenWorkItems,
               __typename: 'WorkItemConnection',
@@ -1971,6 +1981,62 @@ export const workItemObjectiveWithChild = {
       type: 'HIERARCHY',
       hasChildren: true,
       parent: null,
+      rolledUpCountsByType: [],
+      children: {
+        nodes: [],
+      },
+      __typename: 'WorkItemWidgetHierarchy',
+    },
+    workItemObjectiveMetadataWidgets.MILESTONE,
+    workItemObjectiveMetadataWidgets.ASSIGNEES,
+    workItemObjectiveMetadataWidgets.LABELS,
+    workItemObjectiveMetadataWidgets.LINKED_ITEMS,
+  ],
+  __typename: 'WorkItem',
+};
+
+export const workItemObjectiveWithoutChild = {
+  id: 'gid://gitlab/WorkItem/12',
+  iid: '12',
+  archived: false,
+  workItemType: {
+    id: 'gid://gitlab/WorkItems::Type/2411',
+    name: 'Objective',
+    iconName: 'issue-type-objective',
+    __typename: 'WorkItemType',
+  },
+  namespace: {
+    __typename: 'Project',
+    id: '1',
+    fullPath: 'test-project-path',
+    name: 'Project name',
+  },
+  userPermissions: {
+    deleteWorkItem: true,
+    updateWorkItem: true,
+    setWorkItemMetadata: true,
+    adminParentLink: true,
+    createNote: true,
+    adminWorkItemLink: true,
+    __typename: 'WorkItemPermissions',
+  },
+  author: {
+    ...mockAssignees[0],
+  },
+  title: 'Objective',
+  description: 'Objective description',
+  state: 'OPEN',
+  confidential: false,
+  reference: 'test-project-path#12',
+  createdAt: '2022-08-03T12:41:54Z',
+  updatedAt: null,
+  closedAt: null,
+  widgets: [
+    {
+      type: 'HIERARCHY',
+      hasChildren: false,
+      parent: null,
+      rolledUpCountsByType: [],
       children: {
         nodes: [],
       },
@@ -2024,6 +2090,7 @@ export const workItemHierarchyTreeEmptyResponse = {
           type: 'HIERARCHY',
           parent: null,
           hasChildren: true,
+          rolledUpCountsByType: [],
           children: {
             pageInfo: {
               hasNextPage: false,
@@ -2071,6 +2138,7 @@ export const mockHierarchyChildren = [
       {
         type: 'HIERARCHY',
         hasChildren: true,
+        rolledUpCountsByType: [],
         __typename: 'WorkItemWidgetHierarchy',
       },
     ],
@@ -2078,10 +2146,56 @@ export const mockHierarchyChildren = [
   },
 ];
 
+export const mockRolledUpCountsByType = [
+  {
+    countsByState: {
+      all: 3,
+      closed: 0,
+      __typename: 'WorkItemStateCountsType',
+    },
+    workItemType: {
+      id: 'gid://gitlab/WorkItems::Type/8',
+      name: 'Epic',
+      iconName: 'issue-type-epic',
+      __typename: 'WorkItemType',
+    },
+    __typename: 'WorkItemTypeCountsByState',
+  },
+  {
+    countsByState: {
+      all: 5,
+      closed: 2,
+      __typename: 'WorkItemStateCountsType',
+    },
+    workItemType: {
+      id: 'gid://gitlab/WorkItems::Type/1',
+      name: 'Issue',
+      iconName: 'issue-type-issue',
+      __typename: 'WorkItemType',
+    },
+    __typename: 'WorkItemTypeCountsByState',
+  },
+  {
+    countsByState: {
+      all: 2,
+      closed: 1,
+      __typename: 'WorkItemStateCountsType',
+    },
+    workItemType: {
+      id: 'gid://gitlab/WorkItems::Type/5',
+      name: 'Task',
+      iconName: 'issue-type-task',
+      __typename: 'WorkItemType',
+    },
+    __typename: 'WorkItemTypeCountsByState',
+  },
+];
+
 export const mockHierarchyWidget = {
   type: 'HIERARCHY',
   parent: null,
   hasChildren: true,
+  rolledUpCountsByType: mockRolledUpCountsByType,
   children: {
     pageInfo: {
       hasNextPage: false,
@@ -2228,6 +2342,7 @@ export const changeWorkItemParentMutationResponse = {
             hasParent: false,
             parent: null,
             hasChildren: false,
+            rolledUpCountsByType: [],
             children: {
               nodes: [],
             },