diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue
index 376d71c7b31c1e3990fb899665af1fa79d5d30f8..e0ebc714dbb41498a1800918c16a0ac3b28d3230 100644
--- a/app/assets/javascripts/work_items/components/work_item_detail.vue
+++ b/app/assets/javascripts/work_items/components/work_item_detail.vue
@@ -1,5 +1,6 @@
 <script>
 import { isEmpty } from 'lodash';
+import { produce } from 'immer';
 import {
   GlAlert,
   GlSkeletonLoader,
@@ -11,6 +12,7 @@ import {
   GlEmptyState,
 } from '@gitlab/ui';
 import noAccessSvg from '@gitlab/svgs/dist/illustrations/analytics/no-access.svg';
+import * as Sentry from '@sentry/browser';
 import { s__ } from '~/locale';
 import { parseBoolean } from '~/lib/utils/common_utils';
 import { getParameterByName } from '~/lib/utils/url_utility';
@@ -269,6 +271,12 @@ export default {
             id: this.workItemId,
           };
     },
+    children() {
+      const widgetHierarchy = this.workItem.widgets.find(
+        (widget) => widget.type === WIDGET_TYPE_HIERARCHY,
+      );
+      return widgetHierarchy.children.nodes;
+    },
   },
   methods: {
     isWidgetPresent(type) {
@@ -326,6 +334,74 @@ export default {
       this.error = this.$options.i18n.fetchError;
       document.title = s__('404|Not found');
     },
+    addChild(child) {
+      const { defaultClient: client } = this.$apollo.provider.clients;
+      this.toggleChildFromCache(child, child.id, client);
+    },
+    toggleChildFromCache(workItem, childId, store) {
+      const sourceData = store.readQuery({
+        query: getWorkItemQuery(this.fetchByIid),
+        variables: this.queryVariables,
+      });
+
+      const newData = produce(sourceData, (draftState) => {
+        const widgetHierarchy = draftState.workItem.widgets.find(
+          (widget) => widget.type === WIDGET_TYPE_HIERARCHY,
+        );
+
+        const index = widgetHierarchy.children.nodes.findIndex((child) => child.id === childId);
+
+        if (index >= 0) {
+          widgetHierarchy.children.nodes.splice(index, 1);
+        } else {
+          widgetHierarchy.children.nodes.unshift(workItem);
+        }
+      });
+
+      store.writeQuery({
+        query: getWorkItemQuery(this.fetchByIid),
+        variables: this.queryVariables,
+        data: newData,
+      });
+    },
+    async updateWorkItem(workItem, childId, parentId) {
+      return this.$apollo.mutate({
+        mutation: updateWorkItemMutation,
+        variables: { input: { id: childId, hierarchyWidget: { parentId } } },
+        update: (store) => this.toggleChildFromCache(workItem, childId, store),
+      });
+    },
+    async undoChildRemoval(workItem, childId) {
+      try {
+        const { data } = await this.updateWorkItem(workItem, childId, this.workItem.id);
+
+        if (data.workItemUpdate.errors.length === 0) {
+          this.activeToast?.hide();
+        }
+      } catch (error) {
+        this.updateError = s__('WorkItem|Something went wrong while undoing child removal.');
+        Sentry.captureException(error);
+      } finally {
+        this.activeToast?.hide();
+      }
+    },
+    async removeChild(childId) {
+      try {
+        const { data } = await this.updateWorkItem(null, childId, null);
+
+        if (data.workItemUpdate.errors.length === 0) {
+          this.activeToast = this.$toast.show(s__('WorkItem|Child removed'), {
+            action: {
+              text: s__('WorkItem|Undo'),
+              onClick: this.undoChildRemoval.bind(this, data.workItemUpdate.workItem, childId),
+            },
+          });
+        }
+      } catch (error) {
+        this.updateError = s__('WorkItem|Something went wrong while removing child.');
+        Sentry.captureException(error);
+      }
+    },
   },
   WORK_ITEM_TYPE_VALUE_OBJECTIVE,
 };
@@ -507,6 +583,11 @@ export default {
         v-if="workItemType === $options.WORK_ITEM_TYPE_VALUE_OBJECTIVE"
         :work-item-type="workItemType"
         :work-item-id="workItem.id"
+        :children="children"
+        :can-update="canUpdate"
+        :project-path="fullPath"
+        @addWorkItemChild="addChild"
+        @removeChild="removeChild"
       />
       <gl-empty-state
         v-if="error"
diff --git a/app/assets/javascripts/work_items/components/work_item_links/okr_actions_split_button.vue b/app/assets/javascripts/work_items/components/work_item_links/okr_actions_split_button.vue
index a91133ce1accdcf0ed5f62439044415a7761964b..cbe67aa622cabcf379739a0065c3b02e3cdc62fe 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/okr_actions_split_button.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/okr_actions_split_button.vue
@@ -1,5 +1,5 @@
 <script>
-import { GlDropdown, GlDropdownDivider, GlDropdownSectionHeader, GlDropdownItem } from '@gitlab/ui';
+import { GlDropdown, GlDropdownSectionHeader, GlDropdownItem } from '@gitlab/ui';
 
 import { s__ } from '~/locale';
 
@@ -30,7 +30,6 @@ export default {
   objectiveActionItems,
   components: {
     GlDropdown,
-    GlDropdownDivider,
     GlDropdownSectionHeader,
     GlDropdownItem,
   },
@@ -53,6 +52,10 @@ export default {
       {{ item.title }}
     </gl-dropdown-item>
 
+    <!-- TODO: Uncomment once following two issues addressed -->
+    <!-- https://gitlab.com/gitlab-org/gitlab/-/issues/381833 -->
+    <!-- https://gitlab.com/gitlab-org/gitlab/-/issues/385084 -->
+    <!--
     <gl-dropdown-divider />
     <gl-dropdown-section-header>{{ __('Key result') }}</gl-dropdown-section-header>
     <gl-dropdown-item
@@ -62,5 +65,6 @@ export default {
     >
       {{ item.title }}
     </gl-dropdown-item>
+    -->
   </gl-dropdown>
 </template>
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child.vue
index 7141d9c6f89367eba53c7c3d14aa01747c48ecbc..b04da53bf89385f57ace82b7a14465917773967b 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child.vue
@@ -1,12 +1,21 @@
 <script>
 import { GlButton, GlIcon, GlTooltipDirective } from '@gitlab/ui';
 
-import { __ } from '~/locale';
+import { __, s__ } from '~/locale';
+import { createAlert } from '~/flash';
 import { getIdFromGraphQLId } from '~/graphql_shared/utils';
 import RichTimestampTooltip from '~/vue_shared/components/rich_timestamp_tooltip.vue';
 
-import { STATE_OPEN } from '../../constants';
+import {
+  STATE_OPEN,
+  TASK_TYPE_NAME,
+  WORK_ITEM_TYPE_VALUE_OBJECTIVE,
+  WIDGET_TYPE_HIERARCHY,
+  WORK_ITEM_NAME_TO_ICON_MAP,
+} from '../../constants';
+import getWorkItemTreeQuery from '../../graphql/work_item_tree.query.graphql';
 import WorkItemLinksMenu from './work_item_links_menu.vue';
+import WorkItemTreeChildren from './work_item_tree_children.vue';
 
 export default {
   components: {
@@ -14,6 +23,7 @@ export default {
     GlIcon,
     RichTimestampTooltip,
     WorkItemLinksMenu,
+    WorkItemTreeChildren,
   },
   directives: {
     GlTooltip: GlTooltipDirective,
@@ -35,16 +45,45 @@ export default {
       type: Object,
       required: true,
     },
+    hasIndirectChildren: {
+      type: Boolean,
+      required: false,
+      default: true,
+    },
+    workItemType: {
+      type: String,
+      required: false,
+      default: '',
+    },
+  },
+  data() {
+    return {
+      isExpanded: false,
+      children: [],
+      isLoadingChildren: false,
+    };
   },
   computed: {
+    canHaveChildren() {
+      return this.workItemType === WORK_ITEM_TYPE_VALUE_OBJECTIVE;
+    },
     isItemOpen() {
       return this.childItem.state === STATE_OPEN;
     },
-    iconClass() {
-      return this.isItemOpen ? 'gl-text-green-500' : 'gl-text-blue-500';
+    childItemType() {
+      return this.childItem.workItemType.name;
     },
     iconName() {
-      return this.isItemOpen ? 'issue-open-m' : 'issue-close';
+      if (this.childItemType === TASK_TYPE_NAME) {
+        return this.isItemOpen ? 'issue-open-m' : 'issue-close';
+      }
+      return WORK_ITEM_NAME_TO_ICON_MAP[this.childItemType];
+    },
+    iconClass() {
+      if (this.childItemType === TASK_TYPE_NAME) {
+        return this.isItemOpen ? 'gl-text-green-500' : 'gl-text-blue-500';
+      }
+      return '';
     },
     stateTimestamp() {
       return this.isItemOpen ? this.childItem.createdAt : this.childItem.closedAt;
@@ -55,55 +94,132 @@ export default {
     childPath() {
       return `/${this.projectPath}/-/work_items/${getIdFromGraphQLId(this.childItem.id)}`;
     },
+    hasChildren() {
+      return this.getWidgetHierarchyForChild(this.childItem)?.hasChildren;
+    },
+    chevronType() {
+      return this.isExpanded ? 'chevron-down' : 'chevron-right';
+    },
+    chevronTooltip() {
+      return this.isExpanded ? __('Collapse') : __('Expand');
+    },
+  },
+  methods: {
+    toggleItem() {
+      this.isExpanded = !this.isExpanded;
+      if (this.children.length === 0 && this.hasChildren) {
+        this.fetchChildren();
+      }
+    },
+    getWidgetHierarchyForChild(workItem) {
+      const widgetHierarchy = workItem?.widgets?.find(
+        (widget) => widget.type === WIDGET_TYPE_HIERARCHY,
+      );
+      return widgetHierarchy || {};
+    },
+    async fetchChildren() {
+      this.isLoadingChildren = true;
+      try {
+        const { data } = await this.$apollo.query({
+          query: getWorkItemTreeQuery,
+          variables: {
+            id: this.childItem.id,
+          },
+        });
+        this.children = this.getWidgetHierarchyForChild(data?.workItem).children.nodes;
+      } catch (error) {
+        this.isExpanded = !this.isExpanded;
+        createAlert({
+          message: s__('Hierarchy|Something went wrong while fetching children.'),
+          captureError: true,
+          error,
+        });
+      } finally {
+        this.isLoadingChildren = false;
+      }
+    },
   },
 };
 </script>
 
 <template>
-  <div
-    class="gl-relative gl-display-flex gl-overflow-break-word gl-min-w-0 gl-bg-white gl-mb-3 gl-py-3 gl-px-4 gl-border gl-border-gray-100 gl-rounded-base gl-line-height-32"
-    data-testid="links-child"
-  >
-    <div class="gl-overflow-hidden gl-display-flex gl-align-items-center gl-flex-grow-1">
-      <span :id="`stateIcon-${childItem.id}`" class="gl-mr-3" data-testid="item-status-icon">
-        <gl-icon :name="iconName" :class="iconClass" :aria-label="stateTimestampTypeText" />
-      </span>
-      <rich-timestamp-tooltip
-        :target="`stateIcon-${childItem.id}`"
-        :raw-timestamp="stateTimestamp"
-        :timestamp-type-text="stateTimestampTypeText"
-      />
-      <gl-icon
-        v-if="childItem.confidential"
-        v-gl-tooltip.top
-        name="eye-slash"
-        class="gl-mr-2 gl-text-orange-500"
-        data-testid="confidential-icon"
-        :aria-label="__('Confidential')"
-        :title="__('Confidential')"
-      />
-      <gl-button
-        :href="childPath"
-        category="tertiary"
-        variant="link"
-        class="gl-text-truncate gl-max-w-80 gl-text-black-normal!"
-        @click="$emit('click', $event)"
-        @mouseover="$emit('mouseover')"
-        @mouseout="$emit('mouseout')"
-      >
-        {{ childItem.title }}
-      </gl-button>
-    </div>
+  <div>
     <div
-      v-if="canUpdate"
-      class="gl-ml-0 gl-sm-ml-auto! gl-display-inline-flex gl-align-items-center"
+      class="gl-display-flex gl-align-items-center gl-mb-3"
+      :class="{ 'gl-ml-6': canHaveChildren && !hasChildren && hasIndirectChildren }"
     >
-      <work-item-links-menu
-        :work-item-id="childItem.id"
-        :parent-work-item-id="issuableGid"
-        data-testid="links-menu"
-        @removeChild="$emit('remove', childItem.id)"
+      <gl-button
+        v-if="hasChildren"
+        v-gl-tooltip.viewport
+        :title="chevronTooltip"
+        :aria-label="chevronTooltip"
+        :icon="chevronType"
+        category="tertiary"
+        :loading="isLoadingChildren"
+        class="gl-px-0! gl-py-4! gl-mr-3"
+        data-testid="expand-child"
+        @click="toggleItem"
       />
+      <div
+        class="gl-relative gl-display-flex gl-flex-grow-1 gl-overflow-break-word gl-min-w-0 gl-bg-white gl-py-3 gl-px-4 gl-border gl-border-gray-100 gl-rounded-base gl-line-height-32"
+        data-testid="links-child"
+      >
+        <div class="gl-overflow-hidden gl-display-flex gl-align-items-center gl-flex-grow-1">
+          <span :id="`stateIcon-${childItem.id}`" class="gl-mr-3" data-testid="item-status-icon">
+            <gl-icon
+              class="gl-text-secondary"
+              :class="iconClass"
+              :name="iconName"
+              :aria-label="stateTimestampTypeText"
+            />
+          </span>
+          <rich-timestamp-tooltip
+            :target="`stateIcon-${childItem.id}`"
+            :raw-timestamp="stateTimestamp"
+            :timestamp-type-text="stateTimestampTypeText"
+          />
+          <gl-icon
+            v-if="childItem.confidential"
+            v-gl-tooltip.top
+            name="eye-slash"
+            class="gl-mr-2 gl-text-orange-500"
+            data-testid="confidential-icon"
+            :aria-label="__('Confidential')"
+            :title="__('Confidential')"
+          />
+          <gl-button
+            :href="childPath"
+            category="tertiary"
+            variant="link"
+            class="gl-text-truncate gl-max-w-80 gl-text-black-normal!"
+            @click="$emit('click', $event)"
+            @mouseover="$emit('mouseover')"
+            @mouseout="$emit('mouseout')"
+          >
+            {{ childItem.title }}
+          </gl-button>
+        </div>
+        <div
+          v-if="canUpdate"
+          class="gl-ml-0 gl-sm-ml-auto! gl-display-inline-flex gl-align-items-center"
+        >
+          <work-item-links-menu
+            :work-item-id="childItem.id"
+            :parent-work-item-id="issuableGid"
+            data-testid="links-menu"
+            @removeChild="$emit('removeChild', childItem.id)"
+          />
+        </div>
+      </div>
     </div>
+    <work-item-tree-children
+      v-if="isExpanded"
+      :project-path="projectPath"
+      :can-update="canUpdate"
+      :work-item-id="issuableGid"
+      :work-item-type="workItemType"
+      :children="children"
+      @removeChild="fetchChildren"
+    />
   </div>
 </template>
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue
index e96b56f13a9d0fce5faad2ae86bef740bf5159fd..faadb5fa6faab44adef2f833b36103af884a9679 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue
@@ -412,7 +412,7 @@ export default {
           @click="openChild(child, $event)"
           @mouseover="prefetchWorkItem(child)"
           @mouseout="clearPrefetching"
-          @remove="removeChild"
+          @removeChild="removeChild"
         />
         <work-item-detail-modal
           ref="modal"
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 9c09ee3a66a69ad0bf867975b3c9d96d83307fed..b4bb8a99452907436530d727b5a4d8c635cea34d 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
@@ -1,15 +1,26 @@
 <script>
 import { GlButton } from '@gitlab/ui';
-import { s__ } from '~/locale';
+import { isEmpty } from 'lodash';
+import { __ } from '~/locale';
+import { convertToGraphQLId } from '~/graphql_shared/utils';
+import { TYPE_WORK_ITEM } from '~/graphql_shared/constants';
+import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
+import { parseBoolean } from '~/lib/utils/common_utils';
+import { getParameterByName } from '~/lib/utils/url_utility';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
 
 import {
   FORM_TYPES,
+  WIDGET_TYPE_HIERARCHY,
   WORK_ITEMS_TREE_TEXT_MAP,
   WORK_ITEM_TYPE_ENUM_OBJECTIVE,
   WORK_ITEM_TYPE_ENUM_KEY_RESULT,
 } from '../../constants';
+import workItemQuery from '../../graphql/work_item.query.graphql';
+import workItemByIidQuery from '../../graphql/work_item_by_iid.query.graphql';
 import OkrActionsSplitButton from './okr_actions_split_button.vue';
 import WorkItemLinksForm from './work_item_links_form.vue';
+import WorkItemLinkChild from './work_item_link_child.vue';
 
 export default {
   FORM_TYPES,
@@ -20,7 +31,9 @@ export default {
     GlButton,
     OkrActionsSplitButton,
     WorkItemLinksForm,
+    WorkItemLinkChild,
   },
+  mixins: [glFeatureFlagMixin()],
   props: {
     workItemType: {
       type: String,
@@ -30,6 +43,20 @@ export default {
       type: String,
       required: true,
     },
+    children: {
+      type: Array,
+      required: false,
+      default: () => [],
+    },
+    canUpdate: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+    projectPath: {
+      type: String,
+      required: true,
+    },
   },
   data() {
     return {
@@ -38,6 +65,7 @@ export default {
       error: null,
       formType: null,
       childType: null,
+      prefetchedWorkItem: null,
     };
   },
   computed: {
@@ -45,8 +73,41 @@ export default {
       return this.isOpen ? 'chevron-lg-up' : 'chevron-lg-down';
     },
     toggleLabel() {
-      return this.isOpen ? s__('WorkItem|Collapse tasks') : s__('WorkItem|Expand tasks');
+      return this.isOpen ? __('Collapse') : __('Expand');
+    },
+    fetchByIid() {
+      return this.glFeatures.useIidInWorkItemsPath && parseBoolean(getParameterByName('iid_path'));
+    },
+    childrenIds() {
+      return this.children.map((c) => c.id);
+    },
+    hasIndirectChildren() {
+      return this.children
+        .map(
+          (child) => child.widgets?.find((widget) => widget.type === WIDGET_TYPE_HIERARCHY) || {},
+        )
+        .some((hierarchy) => hierarchy.hasChildren);
     },
+    childUrlParams() {
+      const params = {};
+      if (this.fetchByIid) {
+        const iid = getParameterByName('work_item_iid');
+        if (iid) {
+          params.iid = iid;
+        }
+      } else {
+        const workItemId = getParameterByName('work_item_id');
+        if (workItemId) {
+          params.id = convertToGraphQLId(TYPE_WORK_ITEM, workItemId);
+        }
+      }
+      return params;
+    },
+  },
+  mounted() {
+    if (!isEmpty(this.childUrlParams)) {
+      this.addWorkItemQuery(this.childUrlParams);
+    }
   },
   methods: {
     toggle() {
@@ -64,6 +125,37 @@ export default {
     hideAddForm() {
       this.isShownAddForm = false;
     },
+    addWorkItemQuery({ id, iid }) {
+      const variables = this.fetchByIid
+        ? {
+            fullPath: this.projectPath,
+            iid,
+          }
+        : {
+            id,
+          };
+      this.$apollo.addSmartQuery('prefetchedWorkItem', {
+        query() {
+          return this.fetchByIid ? workItemByIidQuery : workItemQuery;
+        },
+        variables,
+        update(data) {
+          return this.fetchByIid ? data.workspace.workItems.nodes[0] : data.workItem;
+        },
+        context: {
+          isSingleRequest: true,
+        },
+      });
+    },
+    prefetchWorkItem({ id, iid }) {
+      this.prefetch = setTimeout(
+        () => this.addWorkItemQuery({ id, iid }),
+        DEFAULT_DEBOUNCE_AND_THROTTLE_MS,
+      );
+    },
+    clearPrefetching() {
+      clearTimeout(this.prefetch);
+    },
   },
 };
 </script>
@@ -113,7 +205,7 @@ export default {
       :class="{ 'gl-p-5 gl-pb-3': !error }"
       data-testid="tree-body"
     >
-      <div v-if="!isShownAddForm && !error" data-testid="tree-empty">
+      <div v-if="!isShownAddForm && !error && children.length === 0" data-testid="tree-empty">
         <p class="gl-mb-3">
           {{ $options.WORK_ITEMS_TREE_TEXT_MAP[workItemType].empty }}
         </p>
@@ -125,8 +217,23 @@ export default {
         :issuable-gid="workItemId"
         :form-type="formType"
         :children-type="childType"
+        :children-ids="childrenIds"
+        @addWorkItemChild="$emit('addWorkItemChild', $event)"
         @cancel="hideAddForm"
       />
+      <work-item-link-child
+        v-for="child in children"
+        :key="child.id"
+        :project-path="projectPath"
+        :can-update="canUpdate"
+        :issuable-gid="workItemId"
+        :child-item="child"
+        :work-item-type="workItemType"
+        :has-indirect-children="hasIndirectChildren"
+        @mouseover="prefetchWorkItem(child)"
+        @mouseout="clearPrefetching"
+        @removeChild="$emit('removeChild', $event)"
+      />
     </div>
   </div>
 </template>
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_tree_children.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_tree_children.vue
new file mode 100644
index 0000000000000000000000000000000000000000..911cac4de88432711242dee2d25bbbbcff0a1347
--- /dev/null
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_tree_children.vue
@@ -0,0 +1,68 @@
+<script>
+import { createAlert } from '~/flash';
+import { s__ } from '~/locale';
+
+import updateWorkItemMutation from '../../graphql/update_work_item.mutation.graphql';
+
+export default {
+  components: {
+    WorkItemLinkChild: () => import('./work_item_link_child.vue'),
+  },
+  props: {
+    workItemType: {
+      type: String,
+      required: true,
+    },
+    workItemId: {
+      type: String,
+      required: true,
+    },
+    children: {
+      type: Array,
+      required: false,
+      default: () => [],
+    },
+    canUpdate: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+    projectPath: {
+      type: String,
+      required: true,
+    },
+  },
+  methods: {
+    async updateWorkItem(childId) {
+      try {
+        await this.$apollo.mutate({
+          mutation: updateWorkItemMutation,
+          variables: { input: { id: childId, hierarchyWidget: { parentId: null } } },
+        });
+        this.$emit('removeChild');
+      } catch (error) {
+        createAlert({
+          message: s__('Hierarchy|Something went wrong while removing a child item.'),
+          captureError: true,
+          error,
+        });
+      }
+    },
+  },
+};
+</script>
+
+<template>
+  <div class="gl-ml-6">
+    <work-item-link-child
+      v-for="child in children"
+      :key="child.id"
+      :project-path="projectPath"
+      :can-update="canUpdate"
+      :issuable-gid="workItemId"
+      :child-item="child"
+      :work-item-type="workItemType"
+      @removeChild="updateWorkItem"
+    />
+  </div>
+</template>
diff --git a/app/assets/javascripts/work_items/constants.js b/app/assets/javascripts/work_items/constants.js
index 939cc416b9eaa6f0c374a3c2ade977e9274b3daa..368bb6a85a478550d27faf95c6ec75650eaa4f3b 100644
--- a/app/assets/javascripts/work_items/constants.js
+++ b/app/assets/javascripts/work_items/constants.js
@@ -116,7 +116,7 @@ export const WORK_ITEMS_TYPE_MAP = {
   },
   [WORK_ITEM_TYPE_ENUM_KEY_RESULT]: {
     icon: `issue-type-issue`,
-    name: s__('WorkItem|Key result'),
+    name: s__('WorkItem|Key Result'),
   },
 };
 
@@ -127,6 +127,14 @@ export const WORK_ITEMS_TREE_TEXT_MAP = {
   },
 };
 
+export const WORK_ITEM_NAME_TO_ICON_MAP = {
+  Issue: 'issue-type-issue',
+  Task: 'issue-type-task',
+  Objective: 'issue-type-objective',
+  // eslint-disable-next-line @gitlab/require-i18n-strings
+  'Key Result': 'issue-type-key-result',
+};
+
 export const FORM_TYPES = {
   create: 'create',
   add: 'add',
diff --git a/app/assets/javascripts/work_items/graphql/work_item_links.query.graphql b/app/assets/javascripts/work_items/graphql/work_item_links.query.graphql
index a37e5b869f6da62c43a2ef5d9a2ad831439bbfb0..7fcf622cdb2c83150ac5d1cf6e1c9c2e51b26d8b 100644
--- a/app/assets/javascripts/work_items/graphql/work_item_links.query.graphql
+++ b/app/assets/javascripts/work_items/graphql/work_item_links.query.graphql
@@ -24,6 +24,8 @@ query workItemLinksQuery($id: WorkItemID!) {
             confidential
             workItemType {
               id
+              name
+              iconName
             }
             title
             state
diff --git a/app/assets/javascripts/work_items/graphql/work_item_tree.query.graphql b/app/assets/javascripts/work_items/graphql/work_item_tree.query.graphql
new file mode 100644
index 0000000000000000000000000000000000000000..a850d002de8769c5a810b26983416fe0305d8781
--- /dev/null
+++ b/app/assets/javascripts/work_items/graphql/work_item_tree.query.graphql
@@ -0,0 +1,47 @@
+query workItemTreeQuery($id: WorkItemID!) {
+  workItem(id: $id) {
+    id
+    workItemType {
+      id
+      name
+      iconName
+    }
+    title
+    userPermissions {
+      deleteWorkItem
+      updateWorkItem
+    }
+    confidential
+    widgets {
+      type
+      ... on WorkItemWidgetHierarchy {
+        type
+        parent {
+          id
+        }
+        children {
+          nodes {
+            id
+            iid
+            confidential
+            workItemType {
+              id
+              name
+              iconName
+            }
+            title
+            state
+            createdAt
+            closedAt
+            widgets {
+              ... on WorkItemWidgetHierarchy {
+                type
+                hasChildren
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/app/assets/javascripts/work_items/graphql/work_item_widgets.fragment.graphql b/app/assets/javascripts/work_items/graphql/work_item_widgets.fragment.graphql
index 025a9d3673b87b628890c997cab142c98a2b3ec6..9b802a8e8fc0754f273d040a81a3de9398e7c516 100644
--- a/app/assets/javascripts/work_items/graphql/work_item_widgets.fragment.graphql
+++ b/app/assets/javascripts/work_items/graphql/work_item_widgets.fragment.graphql
@@ -38,6 +38,7 @@ fragment WorkItemWidgets on WorkItemWidget {
   }
   ... on WorkItemWidgetHierarchy {
     type
+    hasChildren
     parent {
       id
       iid
@@ -56,11 +57,19 @@ fragment WorkItemWidgets on WorkItemWidget {
         confidential
         workItemType {
           id
+          name
+          iconName
         }
         title
         state
         createdAt
         closedAt
+        widgets {
+          ... on WorkItemWidgetHierarchy {
+            type
+            hasChildren
+          }
+        }
       }
     }
   }
diff --git a/ee/app/assets/javascripts/work_items/graphql/work_item_widgets.fragment.graphql b/ee/app/assets/javascripts/work_items/graphql/work_item_widgets.fragment.graphql
index 702ba9d7994b4dab95f6854dd52178f10352cf58..2debddf842b654ed5b9115b5325c9a760ff42446 100644
--- a/ee/app/assets/javascripts/work_items/graphql/work_item_widgets.fragment.graphql
+++ b/ee/app/assets/javascripts/work_items/graphql/work_item_widgets.fragment.graphql
@@ -43,6 +43,7 @@ fragment WorkItemWidgets on WorkItemWidget {
   }
   ... on WorkItemWidgetHierarchy {
     type
+    hasChildren
     parent {
       id
       iid
@@ -61,11 +62,19 @@ fragment WorkItemWidgets on WorkItemWidget {
         confidential
         workItemType {
           id
+          name
+          iconName
         }
         title
         state
         createdAt
         closedAt
+        widgets {
+          ... on WorkItemWidgetHierarchy {
+            type
+            hasChildren
+          }
+        }
       }
     }
   }
diff --git a/ee/spec/features/work_items/okr_hierarchy_spec.rb b/ee/spec/features/work_items/okr_hierarchy_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cc7e6049f75608f34ac85ba15af76f7a630cc4de
--- /dev/null
+++ b/ee/spec/features/work_items/okr_hierarchy_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'OKR hierarchy', :js, feature_category: :product_planning do
+  let_it_be(:user) { create(:user) }
+  let_it_be(:group) { create(:group, :public) }
+  let_it_be(:project) { create(:project, namespace: group) }
+  let_it_be(:type_objective) { WorkItems::Type.default_by_type(:objective) }
+  let_it_be(:objective) { create(:work_item, work_item_type: type_objective, project: project) }
+
+  context 'for signed in user' do
+    before do
+      group.add_developer(user)
+
+      sign_in(user)
+
+      stub_licensed_features(okrs: true)
+
+      stub_feature_flags(work_items: true)
+      stub_feature_flags(okrs_mvc: true)
+      stub_feature_flags(hierarchy_db_restrictions: true)
+
+      visit project_work_items_path(project, work_items_path: objective.id)
+    end
+
+    it 'shows no children', :aggregate_failures do
+      page.within('[data-testid="work-item-tree"]') do
+        expect(page).to have_content('Child objectives and key results')
+        expect(page).to have_content('No objectives or key results are currently assigned.')
+      end
+    end
+
+    it 'toggles widget body', :aggregate_failures do
+      page.within('[data-testid="work-item-tree"]') do
+        expect(page).to have_selector('[data-testid="tree-body"]')
+
+        click_button 'Collapse'
+
+        expect(page).not_to have_selector('[data-testid="tree-body"]')
+
+        click_button 'Expand'
+
+        expect(page).to have_selector('[data-testid="tree-body"]')
+      end
+    end
+
+    it 'toggles forms', :aggregate_failures do
+      page.within('[data-testid="work-item-tree"]') do
+        expect(page).not_to have_selector('[data-testid="add-tree-form"]')
+
+        click_button 'Add'
+        click_button 'New objective'
+
+        expect(page).to have_selector('[data-testid="add-tree-form"]')
+        expect(find('[data-testid="add-tree-form"]')).to have_button('Create objective', disabled: true)
+
+        click_button 'Add'
+        click_button 'Existing objective'
+
+        expect(find('[data-testid="add-tree-form"]')).to have_button('Add objective', disabled: true)
+
+        # TODO: Uncomment once following two issues addressed
+        # https://gitlab.com/gitlab-org/gitlab/-/issues/381833
+        # https://gitlab.com/gitlab-org/gitlab/-/issues/385084
+        # click_button 'Add'
+        # click_button 'New key result'
+
+        # expect(find('[data-testid="add-tree-form"]')).to have_button('Create key result', disabled: true)
+
+        # click_button 'Add'
+        # click_button 'Existing key result'
+
+        # expect(find('[data-testid="add-tree-form"]')).to have_button('Add key result', disabled: true)
+
+        click_button 'Cancel'
+
+        expect(page).not_to have_selector('[data-testid="add-tree-form"]')
+      end
+    end
+  end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index a0cfd270916892d38805ef48d6875f2578d3265e..5fbd4651fcf1b7a8ace7d90c4cd1cdc2a69bcf0e 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -20302,6 +20302,12 @@ msgstr ""
 msgid "Hierarchy|Planning hierarchy"
 msgstr ""
 
+msgid "Hierarchy|Something went wrong while fetching children."
+msgstr ""
+
+msgid "Hierarchy|Something went wrong while removing a child item."
+msgstr ""
+
 msgid "Hierarchy|Take the work items survey"
 msgstr ""
 
@@ -23903,9 +23909,6 @@ msgstr ""
 msgid "Key (PEM)"
 msgstr ""
 
-msgid "Key result"
-msgstr ""
-
 msgid "Key:"
 msgstr ""
 
@@ -46877,7 +46880,7 @@ msgstr ""
 msgid "WorkItem|Iteration"
 msgstr ""
 
-msgid "WorkItem|Key result"
+msgid "WorkItem|Key Result"
 msgstr ""
 
 msgid "WorkItem|Milestone"
@@ -46964,6 +46967,12 @@ msgstr ""
 msgid "WorkItem|Something went wrong while fetching milestones. Please try again."
 msgstr ""
 
+msgid "WorkItem|Something went wrong while removing child."
+msgstr ""
+
+msgid "WorkItem|Something went wrong while undoing child removal."
+msgstr ""
+
 msgid "WorkItem|Something went wrong while updating the %{workItemType}. Please try again."
 msgstr ""
 
diff --git a/spec/frontend/work_items/components/work_item_detail_spec.js b/spec/frontend/work_items/components/work_item_detail_spec.js
index 30475b3656120494fa4d92a78603179d8d4c8946..a2b34fe38a95f1bb789d8b1fec3a2d8992ba83f2 100644
--- a/spec/frontend/work_items/components/work_item_detail_spec.js
+++ b/spec/frontend/work_items/components/work_item_detail_spec.js
@@ -21,6 +21,7 @@ import WorkItemTitle from '~/work_items/components/work_item_title.vue';
 import WorkItemAssignees from '~/work_items/components/work_item_assignees.vue';
 import WorkItemLabels from '~/work_items/components/work_item_labels.vue';
 import WorkItemMilestone from '~/work_items/components/work_item_milestone.vue';
+import WorkItemTree from '~/work_items/components/work_item_links/work_item_tree.vue';
 import { i18n } from '~/work_items/constants';
 import workItemQuery from '~/work_items/graphql/work_item.query.graphql';
 import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
@@ -38,6 +39,7 @@ import {
   workItemAssigneesSubscriptionResponse,
   workItemMilestoneSubscriptionResponse,
   projectWorkItemResponse,
+  objectiveType,
 } from '../mock_data';
 
 describe('WorkItemDetail component', () => {
@@ -78,6 +80,7 @@ describe('WorkItemDetail component', () => {
   const findParentButton = () => findParent().findComponent(GlButton);
   const findCloseButton = () => wrapper.find('[data-testid="work-item-close"]');
   const findWorkItemType = () => wrapper.find('[data-testid="work-item-type"]');
+  const findHierarchyTree = () => wrapper.findComponent(WorkItemTree);
 
   const createComponent = ({
     isModal = false,
@@ -638,4 +641,24 @@ describe('WorkItemDetail component', () => {
       iid: '1',
     });
   });
+
+  describe('hierarchy widget', () => {
+    it('does not render children tree by default', async () => {
+      createComponent();
+      await waitForPromises();
+
+      expect(findHierarchyTree().exists()).toBe(false);
+    });
+
+    it('renders children tree when work item is an Objective', async () => {
+      const objectiveWorkItem = workItemResponseFactory({
+        workItemType: objectiveType,
+      });
+      const handler = jest.fn().mockResolvedValue(objectiveWorkItem);
+      createComponent({ handler });
+      await waitForPromises();
+
+      expect(findHierarchyTree().exists()).toBe(true);
+    });
+  });
 });
diff --git a/spec/frontend/work_items/components/work_item_links/okr_actions_split_button_spec.js b/spec/frontend/work_items/components/work_item_links/okr_actions_split_button_spec.js
index 5563ba12a45f8460b58e726dfc0bb3cb09b07379..48711ddf15d489ac69125bba6bc0f838b1e2c0e7 100644
--- a/spec/frontend/work_items/components/work_item_links/okr_actions_split_button_spec.js
+++ b/spec/frontend/work_items/components/work_item_links/okr_actions_split_button_spec.js
@@ -25,9 +25,13 @@ describe('RelatedItemsTree', () => {
         expect(wrapper.findAllComponents(GlDropdownSectionHeader).at(0).text()).toContain(
           'Objective',
         );
-        expect(wrapper.findAllComponents(GlDropdownSectionHeader).at(1).text()).toContain(
-          'Key result',
-        );
+
+        // TODO: Uncomment once following two issues addressed
+        // https://gitlab.com/gitlab-org/gitlab/-/issues/381833
+        // https://gitlab.com/gitlab-org/gitlab/-/issues/385084
+        // expect(wrapper.findAllComponents(GlDropdownSectionHeader).at(1).text()).toContain(
+        //   'Key result',
+        // );
       });
     });
   });
diff --git a/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js
index e345e5fc7cd27690ea667fa6950eeab83d04543d..3a8e785bc8063143afec3280a392fc8e75d731b0 100644
--- a/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js
+++ b/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js
@@ -1,33 +1,68 @@
 import { GlButton, GlIcon } from '@gitlab/ui';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
 import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
 
+import { createAlert } from '~/flash';
 import RichTimestampTooltip from '~/vue_shared/components/rich_timestamp_tooltip.vue';
 
+import getWorkItemTreeQuery from '~/work_items/graphql/work_item_tree.query.graphql';
 import WorkItemLinkChild from '~/work_items/components/work_item_links/work_item_link_child.vue';
 import WorkItemLinksMenu from '~/work_items/components/work_item_links/work_item_links_menu.vue';
-
-import { workItemTask, confidentialWorkItemTask, closedWorkItemTask } from '../../mock_data';
+import WorkItemTreeChildren from '~/work_items/components/work_item_links/work_item_tree_children.vue';
+import {
+  WIDGET_TYPE_HIERARCHY,
+  TASK_TYPE_NAME,
+  WORK_ITEM_TYPE_VALUE_OBJECTIVE,
+} from '~/work_items/constants';
+
+import {
+  workItemTask,
+  workItemObjectiveWithChild,
+  confidentialWorkItemTask,
+  closedWorkItemTask,
+  workItemHierarchyTreeResponse,
+  workItemHierarchyTreeFailureResponse,
+} from '../../mock_data';
+
+jest.mock('~/flash');
 
 describe('WorkItemLinkChild', () => {
   const WORK_ITEM_ID = 'gid://gitlab/WorkItem/2';
   let wrapper;
+  let getWorkItemTreeQueryHandler;
+
+  Vue.use(VueApollo);
 
   const createComponent = ({
     projectPath = 'gitlab-org/gitlab-test',
     canUpdate = true,
     issuableGid = WORK_ITEM_ID,
     childItem = workItemTask,
+    workItemType = TASK_TYPE_NAME,
+    apolloProvider = null,
   } = {}) => {
+    getWorkItemTreeQueryHandler = jest.fn().mockResolvedValue(workItemHierarchyTreeResponse);
+
     wrapper = shallowMountExtended(WorkItemLinkChild, {
+      apolloProvider:
+        apolloProvider || createMockApollo([[getWorkItemTreeQuery, getWorkItemTreeQueryHandler]]),
       propsData: {
         projectPath,
         canUpdate,
         issuableGid,
         childItem,
+        workItemType,
       },
     });
   };
 
+  beforeEach(() => {
+    createAlert.mockClear();
+  });
+
   afterEach(() => {
     wrapper.destroy();
   });
@@ -121,7 +156,78 @@ describe('WorkItemLinkChild', () => {
     it('removeChild event on menu triggers `click-remove-child` event', () => {
       itemMenuEl.vm.$emit('removeChild');
 
-      expect(wrapper.emitted('remove')).toEqual([[workItemTask.id]]);
+      expect(wrapper.emitted('removeChild')).toEqual([[workItemTask.id]]);
+    });
+  });
+
+  describe('nested children', () => {
+    const findExpandButton = () => wrapper.findByTestId('expand-child');
+    const findTreeChildren = () => wrapper.findComponent(WorkItemTreeChildren);
+
+    beforeEach(() => {
+      getWorkItemTreeQueryHandler.mockClear();
+      createComponent({
+        childItem: workItemObjectiveWithChild,
+        workItemType: WORK_ITEM_TYPE_VALUE_OBJECTIVE,
+      });
+    });
+
+    it('displays expand button when item has children, children are not displayed by default', () => {
+      expect(findExpandButton().exists()).toBe(true);
+      expect(findTreeChildren().exists()).toBe(false);
+    });
+
+    it('fetches and displays children of item when clicking on expand button', async () => {
+      await findExpandButton().vm.$emit('click');
+
+      expect(findExpandButton().props('loading')).toBe(true);
+      await waitForPromises();
+
+      expect(getWorkItemTreeQueryHandler).toHaveBeenCalled();
+      expect(findTreeChildren().exists()).toBe(true);
+
+      const widgetHierarchy = workItemHierarchyTreeResponse.data.workItem.widgets.find(
+        (widget) => widget.type === WIDGET_TYPE_HIERARCHY,
+      );
+      expect(findTreeChildren().props('children')).toEqual(widgetHierarchy.children.nodes);
+    });
+
+    it('does not fetch children if already fetched once while clicking expand button', async () => {
+      findExpandButton().vm.$emit('click'); // Expand for the first time
+      await waitForPromises();
+
+      expect(findTreeChildren().exists()).toBe(true);
+
+      await findExpandButton().vm.$emit('click'); // Collapse
+      findExpandButton().vm.$emit('click'); // Expand again
+      await waitForPromises();
+
+      expect(getWorkItemTreeQueryHandler).toHaveBeenCalledTimes(1); // ensure children were fetched only once.
+      expect(findTreeChildren().exists()).toBe(true);
+    });
+
+    it('calls createAlert when children fetch request fails on clicking expand button', async () => {
+      const getWorkItemTreeQueryFailureHandler = jest
+        .fn()
+        .mockRejectedValue(workItemHierarchyTreeFailureResponse);
+      const apolloProvider = createMockApollo([
+        [getWorkItemTreeQuery, getWorkItemTreeQueryFailureHandler],
+      ]);
+
+      createComponent({
+        childItem: workItemObjectiveWithChild,
+        workItemType: WORK_ITEM_TYPE_VALUE_OBJECTIVE,
+        apolloProvider,
+      });
+
+      findExpandButton().vm.$emit('click');
+      await waitForPromises();
+
+      expect(createAlert).toHaveBeenCalledWith({
+        captureError: true,
+        error: expect.any(Object),
+        message: 'Something went wrong while fetching children.',
+      });
     });
   });
 });
diff --git a/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js
index fe95a9851773f7731d8daa81d4bbd9bf8321f5d8..a61de78c623a6170a64630a518feb45ff3ce4d3a 100644
--- a/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js
+++ b/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js
@@ -257,7 +257,7 @@ describe('WorkItemLinks', () => {
     });
 
     it('calls correct mutation with correct variables', async () => {
-      firstChild.vm.$emit('remove', firstChild.vm.childItem.id);
+      firstChild.vm.$emit('removeChild', firstChild.vm.childItem.id);
 
       await waitForPromises();
 
@@ -272,7 +272,7 @@ describe('WorkItemLinks', () => {
     });
 
     it('shows toast when mutation succeeds', async () => {
-      firstChild.vm.$emit('remove', firstChild.vm.childItem.id);
+      firstChild.vm.$emit('removeChild', firstChild.vm.childItem.id);
 
       await waitForPromises();
 
@@ -284,7 +284,7 @@ describe('WorkItemLinks', () => {
     it('renders correct number of children after removal', async () => {
       expect(findWorkItemLinkChildItems()).toHaveLength(4);
 
-      firstChild.vm.$emit('remove', firstChild.vm.childItem.id);
+      firstChild.vm.$emit('removeChild', firstChild.vm.childItem.id);
       await waitForPromises();
 
       expect(findWorkItemLinkChildItems()).toHaveLength(3);
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 9c1e9ccb6e86e670223594428df60224b64a103f..cc2e174dfdadeee5645bd6b32e7213b469f17ea1 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
@@ -2,12 +2,14 @@ import { nextTick } from 'vue';
 import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
 import WorkItemTree from '~/work_items/components/work_item_links/work_item_tree.vue';
 import WorkItemLinksForm from '~/work_items/components/work_item_links/work_item_links_form.vue';
+import WorkItemLinkChild from '~/work_items/components/work_item_links/work_item_link_child.vue';
 import OkrActionsSplitButton from '~/work_items/components/work_item_links/okr_actions_split_button.vue';
 import {
   FORM_TYPES,
   WORK_ITEM_TYPE_ENUM_OBJECTIVE,
   WORK_ITEM_TYPE_ENUM_KEY_RESULT,
 } from '~/work_items/constants';
+import { childrenWorkItems } from '../../mock_data';
 
 describe('WorkItemTree', () => {
   let wrapper;
@@ -17,10 +19,16 @@ describe('WorkItemTree', () => {
   const findEmptyState = () => wrapper.findByTestId('tree-empty');
   const findToggleFormSplitButton = () => wrapper.findComponent(OkrActionsSplitButton);
   const findForm = () => wrapper.findComponent(WorkItemLinksForm);
+  const findWorkItemLinkChildItems = () => wrapper.findAllComponents(WorkItemLinkChild);
 
-  const createComponent = () => {
+  const createComponent = ({ children = childrenWorkItems } = {}) => {
     wrapper = shallowMountExtended(WorkItemTree, {
-      propsData: { workItemType: 'Objective', workItemId: 'gid://gitlab/WorkItem/515' },
+      propsData: {
+        workItemType: 'Objective',
+        workItemId: 'gid://gitlab/WorkItem/515',
+        children,
+        projectPath: 'test/project',
+      },
     });
   };
 
@@ -47,9 +55,14 @@ describe('WorkItemTree', () => {
   });
 
   it('displays empty state if there are no children', () => {
+    createComponent({ children: [] });
     expect(findEmptyState().exists()).toBe(true);
   });
 
+  it('renders all hierarchy widget children', () => {
+    expect(findWorkItemLinkChildItems()).toHaveLength(4);
+  });
+
   it('does not display form by default', () => {
     expect(findForm().exists()).toBe(false);
   });
@@ -71,4 +84,11 @@ describe('WorkItemTree', () => {
       expect(findForm().props('childrenType')).toBe(childType);
     },
   );
+
+  it('remove event on child triggers `removeChild` event', () => {
+    const firstChild = findWorkItemLinkChildItems().at(0);
+    firstChild.vm.$emit('removeChild', 'gid://gitlab/WorkItem/2');
+
+    expect(wrapper.emitted('removeChild')).toEqual([['gid://gitlab/WorkItem/2']]);
+  });
 });
diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js
index a4c16e014efa9712b3b526d58e319aca5a12edbb..7bade7345868b621a5dd0ec540b2e59c6a6470ce 100644
--- a/spec/frontend/work_items/mock_data.js
+++ b/spec/frontend/work_items/mock_data.js
@@ -85,6 +85,7 @@ export const workItemQueryResponse = {
         {
           __typename: 'WorkItemWidgetHierarchy',
           type: 'HIERARCHY',
+          hasChildren: true,
           parent: {
             id: 'gid://gitlab/Issue/1',
             iid: '5',
@@ -108,7 +109,15 @@ export const workItemQueryResponse = {
                 state: 'OPEN',
                 workItemType: {
                   id: '1',
+                  name: 'Task',
+                  iconName: 'issue-type-task',
                 },
+                widgets: [
+                  {
+                    type: 'HIERARCHY',
+                    hasChildren: false,
+                  },
+                ],
               },
             ],
           },
@@ -150,6 +159,7 @@ export const updateWorkItemMutationResponse = {
         },
         widgets: [
           {
+            type: 'HIERARCHY',
             children: {
               nodes: [
                 {
@@ -161,10 +171,13 @@ export const updateWorkItemMutationResponse = {
                   state: 'OPEN',
                   workItemType: {
                     id: '1',
+                    name: 'Task',
+                    iconName: 'issue-type-task',
                   },
                 },
               ],
             },
+            __typename: 'WorkItemConnection',
           },
           {
             __typename: 'WorkItemWidgetAssignees',
@@ -219,6 +232,20 @@ export const descriptionHtmlWithCheckboxes = `
   </ul>
 `;
 
+const taskType = {
+  __typename: 'WorkItemType',
+  id: 'gid://gitlab/WorkItems::Type/5',
+  name: 'Task',
+  iconName: 'issue-type-task',
+};
+
+export const objectiveType = {
+  __typename: 'WorkItemType',
+  id: 'gid://gitlab/WorkItems::Type/2411',
+  name: 'Objective',
+  iconName: 'issue-type-objective',
+};
+
 export const workItemResponseFactory = ({
   canUpdate = false,
   canDelete = false,
@@ -236,6 +263,7 @@ export const workItemResponseFactory = ({
   lastEditedBy = null,
   withCheckboxes = false,
   parent = mockParent.parent,
+  workItemType = taskType,
 } = {}) => ({
   data: {
     workItem: {
@@ -253,12 +281,7 @@ export const workItemResponseFactory = ({
         id: '1',
         fullPath: 'test-project-path',
       },
-      workItemType: {
-        __typename: 'WorkItemType',
-        id: 'gid://gitlab/WorkItems::Type/5',
-        name: 'Task',
-        iconName: 'issue-type-task',
-      },
+      workItemType,
       userPermissions: {
         deleteWorkItem: canDelete,
         updateWorkItem: canUpdate,
@@ -338,6 +361,7 @@ export const workItemResponseFactory = ({
         {
           __typename: 'WorkItemWidgetHierarchy',
           type: 'HIERARCHY',
+          hasChildren: true,
           children: {
             nodes: [
               {
@@ -349,7 +373,15 @@ export const workItemResponseFactory = ({
                 state: 'OPEN',
                 workItemType: {
                   id: '1',
+                  name: 'Task',
+                  iconName: 'issue-type-task',
                 },
+                widgets: [
+                  {
+                    type: 'HIERARCHY',
+                    hasChildren: false,
+                  },
+                ],
               },
             ],
           },
@@ -669,6 +701,8 @@ export const workItemHierarchyEmptyResponse = {
       id: 'gid://gitlab/WorkItem/1',
       workItemType: {
         id: 'gid://gitlab/WorkItems::Type/6',
+        name: 'Issue',
+        iconName: 'issue-type-issue',
         __typename: 'WorkItemType',
       },
       title: 'New title',
@@ -692,6 +726,7 @@ export const workItemHierarchyEmptyResponse = {
         {
           type: 'HIERARCHY',
           parent: null,
+          hasChildren: false,
           children: {
             nodes: [],
             __typename: 'WorkItemConnection',
@@ -710,6 +745,8 @@ export const workItemHierarchyNoUpdatePermissionResponse = {
       id: 'gid://gitlab/WorkItem/1',
       workItemType: {
         id: 'gid://gitlab/WorkItems::Type/6',
+        name: 'Issue',
+        iconName: 'issue-type-issue',
         __typename: 'WorkItemType',
       },
       title: 'New title',
@@ -731,6 +768,7 @@ export const workItemHierarchyNoUpdatePermissionResponse = {
         {
           type: 'HIERARCHY',
           parent: null,
+          hasChildren: true,
           children: {
             nodes: [
               {
@@ -738,6 +776,8 @@ export const workItemHierarchyNoUpdatePermissionResponse = {
                 iid: '2',
                 workItemType: {
                   id: 'gid://gitlab/WorkItems::Type/5',
+                  name: 'Task',
+                  iconName: 'issue-type-task',
                   __typename: 'WorkItemType',
                 },
                 title: 'xyz',
@@ -745,6 +785,12 @@ export const workItemHierarchyNoUpdatePermissionResponse = {
                 confidential: false,
                 createdAt: '2022-08-03T12:41:54Z',
                 closedAt: null,
+                widgets: [
+                  {
+                    type: 'HIERARCHY',
+                    hasChildren: false,
+                  },
+                ],
                 __typename: 'WorkItem',
               },
             ],
@@ -763,6 +809,8 @@ export const workItemTask = {
   iid: '4',
   workItemType: {
     id: 'gid://gitlab/WorkItems::Type/5',
+    name: 'Task',
+    iconName: 'issue-type-task',
     __typename: 'WorkItemType',
   },
   title: 'bar',
@@ -778,6 +826,8 @@ export const confidentialWorkItemTask = {
   iid: '2',
   workItemType: {
     id: 'gid://gitlab/WorkItems::Type/5',
+    name: 'Task',
+    iconName: 'issue-type-task',
     __typename: 'WorkItemType',
   },
   title: 'xyz',
@@ -793,6 +843,8 @@ export const closedWorkItemTask = {
   iid: '3',
   workItemType: {
     id: 'gid://gitlab/WorkItems::Type/5',
+    name: 'Task',
+    iconName: 'issue-type-task',
     __typename: 'WorkItemType',
   },
   title: 'abc',
@@ -803,6 +855,28 @@ export const closedWorkItemTask = {
   __typename: 'WorkItem',
 };
 
+export const childrenWorkItems = [
+  confidentialWorkItemTask,
+  closedWorkItemTask,
+  workItemTask,
+  {
+    id: 'gid://gitlab/WorkItem/5',
+    iid: '5',
+    workItemType: {
+      id: 'gid://gitlab/WorkItems::Type/5',
+      name: 'Task',
+      iconName: 'issue-type-task',
+      __typename: 'WorkItemType',
+    },
+    title: 'foobar',
+    state: 'OPEN',
+    confidential: false,
+    createdAt: '2022-08-03T12:41:54Z',
+    closedAt: null,
+    __typename: 'WorkItem',
+  },
+];
+
 export const workItemHierarchyResponse = {
   data: {
     workItem: {
@@ -810,6 +884,8 @@ export const workItemHierarchyResponse = {
       iid: '1',
       workItemType: {
         id: 'gid://gitlab/WorkItems::Type/6',
+        name: 'Objective',
+        iconName: 'issue-type-objective',
         __typename: 'WorkItemType',
       },
       title: 'New title',
@@ -831,23 +907,97 @@ export const workItemHierarchyResponse = {
         {
           type: 'HIERARCHY',
           parent: null,
+          hasChildren: true,
+          children: {
+            nodes: childrenWorkItems,
+            __typename: 'WorkItemConnection',
+          },
+          __typename: 'WorkItemWidgetHierarchy',
+        },
+      ],
+      __typename: 'WorkItem',
+    },
+  },
+};
+
+export const workItemObjectiveWithChild = {
+  id: 'gid://gitlab/WorkItem/12',
+  iid: '12',
+  workItemType: {
+    id: 'gid://gitlab/WorkItems::Type/2411',
+    name: 'Objective',
+    iconName: 'issue-type-objective',
+    __typename: 'WorkItemType',
+  },
+  title: 'Objective',
+  state: 'OPEN',
+  confidential: false,
+  createdAt: '2022-08-03T12:41:54Z',
+  closedAt: null,
+  widgets: [
+    {
+      type: 'HIERARCHY',
+      hasChildren: true,
+      __typename: 'WorkItemWidgetHierarchy',
+    },
+  ],
+  __typename: 'WorkItem',
+};
+
+export const workItemHierarchyTreeResponse = {
+  data: {
+    workItem: {
+      id: 'gid://gitlab/WorkItem/2',
+      iid: '2',
+      workItemType: {
+        id: 'gid://gitlab/WorkItems::Type/2411',
+        name: 'Objective',
+        iconName: 'issue-type-objective',
+        __typename: 'WorkItemType',
+      },
+      title: 'New title',
+      userPermissions: {
+        deleteWorkItem: true,
+        updateWorkItem: true,
+      },
+      confidential: false,
+      project: {
+        __typename: 'Project',
+        id: '1',
+        fullPath: 'test-project-path',
+      },
+      widgets: [
+        {
+          type: 'DESCRIPTION',
+          __typename: 'WorkItemWidgetDescription',
+        },
+        {
+          type: 'HIERARCHY',
+          parent: null,
+          hasChildren: true,
           children: {
             nodes: [
-              confidentialWorkItemTask,
-              closedWorkItemTask,
-              workItemTask,
               {
-                id: 'gid://gitlab/WorkItem/5',
-                iid: '5',
+                id: 'gid://gitlab/WorkItem/13',
+                iid: '13',
                 workItemType: {
-                  id: 'gid://gitlab/WorkItems::Type/5',
+                  id: 'gid://gitlab/WorkItems::Type/2411',
+                  name: 'Objective',
+                  iconName: 'issue-type-objective',
                   __typename: 'WorkItemType',
                 },
-                title: 'foobar',
+                title: 'Objective 2',
                 state: 'OPEN',
                 confidential: false,
                 createdAt: '2022-08-03T12:41:54Z',
                 closedAt: null,
+                widgets: [
+                  {
+                    type: 'HIERARCHY',
+                    hasChildren: true,
+                    __typename: 'WorkItemWidgetHierarchy',
+                  },
+                ],
                 __typename: 'WorkItem',
               },
             ],
@@ -861,6 +1011,15 @@ export const workItemHierarchyResponse = {
   },
 };
 
+export const workItemHierarchyTreeFailureResponse = {
+  data: {},
+  errors: [
+    {
+      message: 'Something went wrong',
+    },
+  ],
+};
+
 export const changeWorkItemParentMutationResponse = {
   data: {
     workItemUpdate: {
@@ -894,6 +1053,7 @@ export const changeWorkItemParentMutationResponse = {
             __typename: 'WorkItemWidgetHierarchy',
             type: 'HIERARCHY',
             parent: null,
+            hasChildren: false,
             children: {
               nodes: [],
             },