diff --git a/app/assets/javascripts/work_items/components/work_item_attributes_wrapper.vue b/app/assets/javascripts/work_items/components/work_item_attributes_wrapper.vue
index 662edda13f18e817c542436c09a2b6010b011d21..2225dfc3fc42ba93a8f2c18ca09391319255a2b0 100644
--- a/app/assets/javascripts/work_items/components/work_item_attributes_wrapper.vue
+++ b/app/assets/javascripts/work_items/components/work_item_attributes_wrapper.vue
@@ -11,6 +11,7 @@ import {
   WIDGET_TYPE_PARTICIPANTS,
   WIDGET_TYPE_PROGRESS,
   WIDGET_TYPE_START_AND_DUE_DATE,
+  WIDGET_TYPE_TIME_TRACKING,
   WIDGET_TYPE_WEIGHT,
   WIDGET_TYPE_COLOR,
   WORK_ITEM_TYPE_VALUE_KEY_RESULT,
@@ -26,6 +27,7 @@ import WorkItemMilestoneInline from './work_item_milestone_inline.vue';
 import WorkItemMilestoneWithEdit from './work_item_milestone_with_edit.vue';
 import WorkItemParentInline from './work_item_parent_inline.vue';
 import WorkItemParent from './work_item_parent_with_edit.vue';
+import WorkItemTimeTracking from './work_item_time_tracking.vue';
 
 export default {
   components: {
@@ -39,6 +41,7 @@ export default {
     WorkItemDueDateWithEdit,
     WorkItemParent,
     WorkItemParentInline,
+    WorkItemTimeTracking,
     WorkItemWeightInline: () =>
       import('ee_component/work_items/components/work_item_weight_inline.vue'),
     WorkItemWeight: () =>
@@ -116,6 +119,9 @@ export default {
     workItemParent() {
       return this.isWidgetPresent(WIDGET_TYPE_HIERARCHY)?.parent;
     },
+    workItemTimeTracking() {
+      return this.isWidgetPresent(WIDGET_TYPE_TIME_TRACKING);
+    },
     workItemColor() {
       return this.isWidgetPresent(WIDGET_TYPE_COLOR);
     },
@@ -309,6 +315,12 @@ export default {
       :can-update="canUpdate"
       @error="$emit('error', $event)"
     />
+    <work-item-time-tracking
+      v-if="workItemTimeTracking && glFeatures.workItemsMvc2"
+      class="gl-mb-5"
+      :time-estimate="workItemTimeTracking.timeEstimate"
+      :total-time-spent="workItemTimeTracking.totalTimeSpent"
+    />
     <participants
       v-if="workItemParticipants && glFeatures.workItemsMvc"
       class="gl-mb-5 gl-pt-5 gl-border-t gl-border-gray-50"
diff --git a/app/assets/javascripts/work_items/components/work_item_time_tracking.vue b/app/assets/javascripts/work_items/components/work_item_time_tracking.vue
new file mode 100644
index 0000000000000000000000000000000000000000..d28747fdaaef9c1200beab2bc2bd602a048a0e2b
--- /dev/null
+++ b/app/assets/javascripts/work_items/components/work_item_time_tracking.vue
@@ -0,0 +1,80 @@
+<script>
+import { GlProgressBar, GlTooltipDirective } from '@gitlab/ui';
+import { outputChronicDuration } from '~/chronic_duration';
+import { isPositiveInteger } from '~/lib/utils/number_utils';
+import { s__, sprintf } from '~/locale';
+
+const options = { format: 'short' };
+
+export default {
+  components: {
+    GlProgressBar,
+  },
+  directives: {
+    GlTooltip: GlTooltipDirective,
+  },
+  props: {
+    timeEstimate: {
+      type: Number,
+      required: false,
+      default: 0,
+    },
+    totalTimeSpent: {
+      type: Number,
+      required: false,
+      default: 0,
+    },
+  },
+  computed: {
+    humanTimeEstimate() {
+      return outputChronicDuration(this.timeEstimate, options);
+    },
+    humanTotalTimeSpent() {
+      return outputChronicDuration(this.totalTimeSpent, options) ?? '0h';
+    },
+    progressBarTooltipText() {
+      const timeDifference = this.totalTimeSpent - this.timeEstimate;
+      const time = outputChronicDuration(Math.abs(timeDifference), options);
+      return isPositiveInteger(timeDifference)
+        ? sprintf(s__('TimeTracking|%{time} over'), { time })
+        : sprintf(s__('TimeTracking|%{time} remaining'), { time });
+    },
+    progressBarVariant() {
+      return this.timeRemainingPercent > 100 ? 'danger' : 'primary';
+    },
+    timeRemainingPercent() {
+      return Math.floor((this.totalTimeSpent / this.timeEstimate) * 100);
+    },
+  },
+};
+</script>
+
+<template>
+  <div>
+    <h3 class="gl-heading-5 gl-mb-2!">
+      {{ __('Time tracking') }}
+    </h3>
+    <div
+      class="gl-display-flex gl-align-items-center gl-gap-2 gl-font-sm"
+      data-testid="time-tracking-body"
+    >
+      <template v-if="totalTimeSpent || timeEstimate">
+        <span class="gl-text-secondary">{{ s__('TimeTracking|Spent') }}</span>
+        {{ humanTotalTimeSpent }}
+        <template v-if="timeEstimate">
+          <gl-progress-bar
+            v-gl-tooltip="progressBarTooltipText"
+            class="gl-flex-grow-1 gl-mx-2"
+            :value="timeRemainingPercent"
+            :variant="progressBarVariant"
+          />
+          <span class="gl-text-secondary">{{ s__('TimeTracking|Estimate') }}</span>
+          {{ humanTimeEstimate }}
+        </template>
+      </template>
+      <span v-else class="gl-text-secondary">
+        {{ s__('TimeTracking|Use /spend or /estimate to manage time.') }}
+      </span>
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/work_items/constants.js b/app/assets/javascripts/work_items/constants.js
index 39d7fe338fcdec9f721cdabdf9bea466a2d1f67e..511b896b94ab332ecbb48715f0199d4785df9357 100644
--- a/app/assets/javascripts/work_items/constants.js
+++ b/app/assets/javascripts/work_items/constants.js
@@ -16,6 +16,7 @@ export const WIDGET_TYPE_NOTIFICATIONS = 'NOTIFICATIONS';
 export const WIDGET_TYPE_CURRENT_USER_TODOS = 'CURRENT_USER_TODOS';
 export const WIDGET_TYPE_LABELS = 'LABELS';
 export const WIDGET_TYPE_START_AND_DUE_DATE = 'START_AND_DUE_DATE';
+export const WIDGET_TYPE_TIME_TRACKING = 'TIME_TRACKING';
 export const WIDGET_TYPE_WEIGHT = 'WEIGHT';
 export const WIDGET_TYPE_PARTICIPANTS = 'PARTICIPANTS';
 export const WIDGET_TYPE_PROGRESS = 'PROGRESS';
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 90bd9be1007541e2582937cc4669253c1d00c20c..42752f2ce30294fca0e0b685418b280f8dab1288 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
@@ -41,6 +41,10 @@ fragment WorkItemWidgets on WorkItemWidget {
     dueDate
     startDate
   }
+  ... on WorkItemWidgetTimeTracking {
+    timeEstimate
+    totalTimeSpent
+  }
   ... on WorkItemWidgetHierarchy {
     hasChildren
     parent {
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 40499836a210f6604118c105f2204034e38f5fe2..08c09ccd856ea63d29bdc326308ea757a90833c5 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
@@ -42,6 +42,10 @@ fragment WorkItemWidgets on WorkItemWidget {
     dueDate
     startDate
   }
+  ... on WorkItemWidgetTimeTracking {
+    timeEstimate
+    totalTimeSpent
+  }
   ... on WorkItemWidgetWeight {
     weight
   }
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index eb1f9c3c53dec7dc5528729c66de426f7790f4df..1d219757157a0e54ead39ce4cc1de2c2cf277b75 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -51426,6 +51426,12 @@ msgstr ""
 msgid "TimeTracking|%{spentStart}Spent: %{spentEnd}"
 msgstr ""
 
+msgid "TimeTracking|%{time} over"
+msgstr ""
+
+msgid "TimeTracking|%{time} remaining"
+msgstr ""
+
 msgid "TimeTracking|An error occurred while removing the timelog."
 msgstr ""
 
@@ -51471,6 +51477,9 @@ msgstr ""
 msgid "TimeTracking|Time remaining: %{timeRemainingHumanReadable}"
 msgstr ""
 
+msgid "TimeTracking|Use /spend or /estimate to manage time."
+msgstr ""
+
 msgid "Timeago|%s days ago"
 msgstr ""
 
diff --git a/spec/frontend/work_items/components/work_item_attributes_wrapper_spec.js b/spec/frontend/work_items/components/work_item_attributes_wrapper_spec.js
index 5dfe27c41817b73a81af3ad85a8be16189e0114f..51e398da078985c8c1a03b38249862da9329777c 100644
--- a/spec/frontend/work_items/components/work_item_attributes_wrapper_spec.js
+++ b/spec/frontend/work_items/components/work_item_attributes_wrapper_spec.js
@@ -9,6 +9,7 @@ import WorkItemMilestoneInline from '~/work_items/components/work_item_milestone
 import WorkItemMilestoneWithEdit from '~/work_items/components/work_item_milestone_with_edit.vue';
 import WorkItemParentInline from '~/work_items/components/work_item_parent_inline.vue';
 import WorkItemParent from '~/work_items/components/work_item_parent_with_edit.vue';
+import WorkItemTimeTracking from '~/work_items/components/work_item_time_tracking.vue';
 import waitForPromises from 'helpers/wait_for_promises';
 import WorkItemAttributesWrapper from '~/work_items/components/work_item_attributes_wrapper.vue';
 import {
@@ -32,7 +33,8 @@ describe('WorkItemAttributesWrapper component', () => {
   const findWorkItemMilestoneInline = () => wrapper.findComponent(WorkItemMilestoneInline);
   const findWorkItemParentInline = () => wrapper.findComponent(WorkItemParentInline);
   const findWorkItemParent = () => wrapper.findComponent(WorkItemParent);
-  const findWorkItemParticipents = () => wrapper.findComponent(Participants);
+  const findWorkItemTimeTracking = () => wrapper.findComponent(WorkItemTimeTracking);
+  const findWorkItemParticipants = () => wrapper.findComponent(Participants);
 
   const createComponent = ({
     workItem = workItemQueryResponse.data.workItem,
@@ -209,6 +211,19 @@ describe('WorkItemAttributesWrapper component', () => {
     });
   });
 
+  describe('time tracking widget', () => {
+    it.each`
+      description                                               | timeTrackingWidgetPresent | exists
+      ${'renders when widget is returned from API'}             | ${true}                   | ${true}
+      ${'does not render when widget is not returned from API'} | ${false}                  | ${false}
+    `('$description', ({ timeTrackingWidgetPresent, exists }) => {
+      const response = workItemResponseFactory({ timeTrackingWidgetPresent });
+      createComponent({ workItem: response.data.workItem });
+
+      expect(findWorkItemTimeTracking().exists()).toBe(exists);
+    });
+  });
+
   describe('participants widget', () => {
     it.each`
       description                                               | participantsWidgetPresent | exists
@@ -218,7 +233,7 @@ describe('WorkItemAttributesWrapper component', () => {
       const response = workItemResponseFactory({ participantsWidgetPresent });
       createComponent({ workItem: response.data.workItem });
 
-      expect(findWorkItemParticipents().exists()).toBe(exists);
+      expect(findWorkItemParticipants().exists()).toBe(exists);
     });
   });
 });
diff --git a/spec/frontend/work_items/components/work_item_time_tracking_spec.js b/spec/frontend/work_items/components/work_item_time_tracking_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..3c40bee6325c23fe6846873f14faf563c1af536a
--- /dev/null
+++ b/spec/frontend/work_items/components/work_item_time_tracking_spec.js
@@ -0,0 +1,106 @@
+import { GlProgressBar } from '@gitlab/ui';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import WorkItemTimeTracking from '~/work_items/components/work_item_time_tracking.vue';
+
+describe('WorkItemTimeTracking component', () => {
+  let wrapper;
+
+  const findProgressBar = () => wrapper.findComponent(GlProgressBar);
+  const findTimeTrackingBody = () => wrapper.findByTestId('time-tracking-body');
+  const getTooltip = () => getBinding(findProgressBar().element, 'gl-tooltip');
+
+  const createComponent = ({ timeEstimate = 0, totalTimeSpent = 0 } = {}) => {
+    wrapper = shallowMountExtended(WorkItemTimeTracking, {
+      directives: {
+        GlTooltip: createMockDirective('gl-tooltip'),
+      },
+      propsData: {
+        timeEstimate,
+        totalTimeSpent,
+      },
+    });
+  };
+
+  it('renders heading text', () => {
+    createComponent();
+
+    expect(wrapper.find('h3').text()).toBe('Time tracking');
+  });
+
+  describe('with no time spent and no time estimate', () => {
+    it('shows help text', () => {
+      createComponent({ timeEstimate: 0, totalTimeSpent: 0 });
+
+      expect(findTimeTrackingBody().text()).toMatchInterpolatedText(
+        'Use /spend or /estimate to manage time.',
+      );
+      expect(findProgressBar().exists()).toBe(false);
+    });
+  });
+
+  describe('with time spent and no time estimate', () => {
+    it('shows only time spent', () => {
+      createComponent({ timeEstimate: 0, totalTimeSpent: 10800 });
+
+      expect(findTimeTrackingBody().text()).toMatchInterpolatedText('Spent 3h');
+      expect(findProgressBar().exists()).toBe(false);
+    });
+  });
+
+  describe('with no time spent and time estimate', () => {
+    beforeEach(() => {
+      createComponent({ timeEstimate: 10800, totalTimeSpent: 0 });
+    });
+
+    it('shows 0h time spent and time estimate', () => {
+      expect(findTimeTrackingBody().text()).toMatchInterpolatedText('Spent 0h Estimate 3h');
+    });
+
+    it('shows progress bar with tooltip', () => {
+      expect(findProgressBar().attributes()).toMatchObject({
+        value: '0',
+        variant: 'primary',
+      });
+      expect(getTooltip().value).toContain('3h remaining');
+    });
+  });
+
+  describe('with time spent and time estimate', () => {
+    describe('when time spent is less than the time estimate', () => {
+      beforeEach(() => {
+        createComponent({ timeEstimate: 18000, totalTimeSpent: 10800 });
+      });
+
+      it('shows time spent and time estimate', () => {
+        expect(findTimeTrackingBody().text()).toMatchInterpolatedText('Spent 3h Estimate 5h');
+      });
+
+      it('shows progress bar with tooltip', () => {
+        expect(findProgressBar().attributes()).toMatchObject({
+          value: '60',
+          variant: 'primary',
+        });
+        expect(getTooltip().value).toContain('2h remaining');
+      });
+    });
+
+    describe('when time spent is greater than the time estimate', () => {
+      beforeEach(() => {
+        createComponent({ timeEstimate: 10800, totalTimeSpent: 18000 });
+      });
+
+      it('shows time spent and time estimate', () => {
+        expect(findTimeTrackingBody().text()).toMatchInterpolatedText('Spent 5h Estimate 3h');
+      });
+
+      it('shows progress bar with tooltip', () => {
+        expect(findProgressBar().attributes()).toMatchObject({
+          value: '166',
+          variant: 'danger',
+        });
+        expect(getTooltip().value).toContain('2h over');
+      });
+    });
+  });
+});
diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js
index 0962bed9a4cab149cd3c0748ec1038f4756d81c3..d75daaefb22a33b9f01bf1b678236cc41c6f3916 100644
--- a/spec/frontend/work_items/mock_data.js
+++ b/spec/frontend/work_items/mock_data.js
@@ -628,6 +628,7 @@ export const workItemResponseFactory = ({
   assigneesWidgetPresent = true,
   datesWidgetPresent = true,
   weightWidgetPresent = true,
+  timeTrackingWidgetPresent = true,
   participantsWidgetPresent = true,
   progressWidgetPresent = true,
   milestoneWidgetPresent = true,
@@ -757,6 +758,14 @@ export const workItemResponseFactory = ({
               },
             }
           : { type: 'MOCK TYPE' },
+        timeTrackingWidgetPresent
+          ? {
+              __typename: 'WorkItemWidgetTimeTracking',
+              type: 'TIME_TRACKING',
+              timeEstimate: '5h',
+              totalTimeSpent: '3h',
+            }
+          : { type: 'MOCK TYPE' },
         participantsWidgetPresent
           ? {
               __typename: 'WorkItemWidgetParticipants',