diff --git a/app/assets/javascripts/work_items/components/item_state.vue b/app/assets/javascripts/work_items/components/item_state.vue
new file mode 100644
index 0000000000000000000000000000000000000000..0b6c1a75bb2a8e43d481498c9e62dab00dd40561
--- /dev/null
+++ b/app/assets/javascripts/work_items/components/item_state.vue
@@ -0,0 +1,62 @@
+<script>
+import { GlFormGroup, GlFormSelect } from '@gitlab/ui';
+import { __ } from '~/locale';
+import { STATE_OPEN, STATE_CLOSED } from '../constants';
+
+export default {
+  i18n: {
+    status: __('Status'),
+  },
+  states: [
+    {
+      value: STATE_OPEN,
+      text: __('Open'),
+    },
+    {
+      value: STATE_CLOSED,
+      text: __('Closed'),
+    },
+  ],
+  components: {
+    GlFormGroup,
+    GlFormSelect,
+  },
+  props: {
+    state: {
+      type: String,
+      required: true,
+    },
+    loading: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+  },
+  computed: {
+    currentState() {
+      return this.$options.states[this.state];
+    },
+  },
+  methods: {
+    setState(newState) {
+      if (newState !== this.state) {
+        this.$emit('changed', newState);
+      }
+    },
+  },
+  labelId: 'work-item-state-select',
+};
+</script>
+
+<template>
+  <gl-form-group :label="$options.i18n.status" :label-for="$options.labelId">
+    <gl-form-select
+      :id="$options.labelId"
+      :value="state"
+      :options="$options.states"
+      :disabled="loading"
+      class="gl-w-auto"
+      @change="setState"
+    />
+  </gl-form-group>
+</template>
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 f2fb1e3ccbc4cea44abfd2de06be27469e752031..449e17e155c8bf7d09b8fe09e29dadc9108b2950 100644
--- a/app/assets/javascripts/work_items/components/work_item_detail.vue
+++ b/app/assets/javascripts/work_items/components/work_item_detail.vue
@@ -3,6 +3,7 @@ import { GlAlert } from '@gitlab/ui';
 import { i18n } from '../constants';
 import workItemQuery from '../graphql/work_item.query.graphql';
 import workItemTitleSubscription from '../graphql/work_item_title.subscription.graphql';
+import WorkItemState from './work_item_state.vue';
 import WorkItemTitle from './work_item_title.vue';
 
 export default {
@@ -10,6 +11,7 @@ export default {
   components: {
     GlAlert,
     WorkItemTitle,
+    WorkItemState,
   },
   props: {
     workItemId: {
@@ -49,6 +51,9 @@ export default {
     },
   },
   computed: {
+    workItemLoading() {
+      return this.$apollo.queries.workItem.loading;
+    },
     workItemType() {
       return this.workItem.workItemType?.name;
     },
@@ -63,11 +68,12 @@ export default {
     </gl-alert>
 
     <work-item-title
-      :loading="$apollo.queries.workItem.loading"
+      :loading="workItemLoading"
       :work-item-id="workItem.id"
       :work-item-title="workItem.title"
       :work-item-type="workItemType"
       @error="error = $event"
     />
+    <work-item-state :loading="workItemLoading" :work-item="workItem" @error="error = $event" />
   </section>
 </template>
diff --git a/app/assets/javascripts/work_items/components/work_item_state.vue b/app/assets/javascripts/work_items/components/work_item_state.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ad92d077b25a4c082d49595bce884df7eec52612
--- /dev/null
+++ b/app/assets/javascripts/work_items/components/work_item_state.vue
@@ -0,0 +1,104 @@
+<script>
+import { GlLoadingIcon } from '@gitlab/ui';
+import * as Sentry from '@sentry/browser';
+import Tracking from '~/tracking';
+import {
+  i18n,
+  STATE_OPEN,
+  STATE_CLOSED,
+  STATE_EVENT_CLOSE,
+  STATE_EVENT_REOPEN,
+} from '../constants';
+import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql';
+import ItemState from './item_state.vue';
+
+export default {
+  components: {
+    GlLoadingIcon,
+    ItemState,
+  },
+  mixins: [Tracking.mixin()],
+  props: {
+    loading: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+    workItem: {
+      type: Object,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      updateInProgress: false,
+    };
+  },
+  computed: {
+    workItemType() {
+      return this.workItem.workItemType?.name;
+    },
+    tracking() {
+      return {
+        category: 'workItems:show',
+        label: 'item_state',
+        property: `type_${this.workItemType}`,
+      };
+    },
+  },
+  methods: {
+    async updateWorkItemState(newState) {
+      const stateEventMap = {
+        [STATE_OPEN]: STATE_EVENT_REOPEN,
+        [STATE_CLOSED]: STATE_EVENT_CLOSE,
+      };
+
+      const stateEvent = stateEventMap[newState];
+
+      await this.updateWorkItem(stateEvent);
+    },
+    async updateWorkItem(updatedState) {
+      if (!updatedState) {
+        return;
+      }
+
+      this.updateInProgress = true;
+
+      try {
+        this.track('updated_state');
+
+        const {
+          data: { workItemUpdate },
+        } = await this.$apollo.mutate({
+          mutation: updateWorkItemMutation,
+          variables: {
+            input: {
+              id: this.workItem.id,
+              stateEvent: updatedState,
+            },
+          },
+        });
+
+        if (workItemUpdate?.errors?.length) {
+          throw new Error(workItemUpdate.errors[0]);
+        }
+      } catch (error) {
+        this.$emit('error', i18n.updateError);
+        Sentry.captureException(error);
+      }
+
+      this.updateInProgress = false;
+    },
+  },
+};
+</script>
+
+<template>
+  <gl-loading-icon v-if="loading" class="gl-mt-3" size="md" />
+  <item-state
+    v-else-if="workItem.state"
+    :state="workItem.state"
+    :loading="updateInProgress"
+    @changed="updateWorkItemState"
+  />
+</template>
diff --git a/app/assets/javascripts/work_items/constants.js b/app/assets/javascripts/work_items/constants.js
index d3bcaf0f95f4da1d8b3489aab805313cef640841..a942c3f280d0a3ea4cf0b8852c8f2ffe73f00470 100644
--- a/app/assets/javascripts/work_items/constants.js
+++ b/app/assets/javascripts/work_items/constants.js
@@ -1,5 +1,11 @@
 import { s__ } from '~/locale';
 
+export const STATE_OPEN = 'OPEN';
+export const STATE_CLOSED = 'CLOSED';
+
+export const STATE_EVENT_REOPEN = 'REOPEN';
+export const STATE_EVENT_CLOSE = 'CLOSE';
+
 export const i18n = {
   fetchError: s__('WorkItem|Something went wrong when fetching the work item. Please try again.'),
   updateError: s__('WorkItem|Something went wrong while updating the work item. Please try again.'),
diff --git a/app/assets/javascripts/work_items/graphql/work_item.fragment.graphql b/app/assets/javascripts/work_items/graphql/work_item.fragment.graphql
index 2707d6bb790d93355b6c276592ee694e969403f3..4ad75425893c40e1abb32c680892d2a7a3e9e812 100644
--- a/app/assets/javascripts/work_items/graphql/work_item.fragment.graphql
+++ b/app/assets/javascripts/work_items/graphql/work_item.fragment.graphql
@@ -1,6 +1,7 @@
 fragment WorkItem on WorkItem {
   id
   title
+  state
   workItemType {
     id
     name
diff --git a/app/assets/javascripts/work_items/pages/create_work_item.vue b/app/assets/javascripts/work_items/pages/create_work_item.vue
index a95da80ac957194580b38580cfdbf07e9729bcc4..2e59912e64002efc3b250b000c7b5bd0f8317065 100644
--- a/app/assets/javascripts/work_items/pages/create_work_item.vue
+++ b/app/assets/javascripts/work_items/pages/create_work_item.vue
@@ -115,7 +115,7 @@ export default {
             },
           },
           update(store, { data: { workItemCreate } }) {
-            const { id, title, workItemType } = workItemCreate.workItem;
+            const { id, title, workItemType, state } = workItemCreate.workItem;
 
             store.writeQuery({
               query: workItemQuery,
@@ -127,6 +127,7 @@ export default {
                   __typename: 'WorkItem',
                   id,
                   title,
+                  state,
                   workItemType,
                 },
               },
diff --git a/spec/frontend/work_items/components/item_state_spec.js b/spec/frontend/work_items/components/item_state_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..79b76f3c0614436b086e9a217b40539d3b72b4cf
--- /dev/null
+++ b/spec/frontend/work_items/components/item_state_spec.js
@@ -0,0 +1,54 @@
+import { mount } from '@vue/test-utils';
+import { STATE_OPEN, STATE_CLOSED } from '~/work_items/constants';
+import ItemState from '~/work_items/components/item_state.vue';
+
+describe('ItemState', () => {
+  let wrapper;
+
+  const findLabel = () => wrapper.find('label').text();
+  const selectedValue = () => wrapper.find('option:checked').element.value;
+
+  const clickOpen = () => wrapper.findAll('option').at(0).setSelected();
+
+  const createComponent = ({ state = STATE_OPEN, disabled = false } = {}) => {
+    wrapper = mount(ItemState, {
+      propsData: {
+        state,
+        disabled,
+      },
+    });
+  };
+
+  afterEach(() => {
+    wrapper.destroy();
+  });
+
+  it('renders label and dropdown', () => {
+    createComponent();
+
+    expect(findLabel()).toBe('Status');
+    expect(selectedValue()).toBe(STATE_OPEN);
+  });
+
+  it('renders dropdown for closed', () => {
+    createComponent({ state: STATE_CLOSED });
+
+    expect(selectedValue()).toBe(STATE_CLOSED);
+  });
+
+  it('emits changed event', async () => {
+    createComponent({ state: STATE_CLOSED });
+
+    await clickOpen();
+
+    expect(wrapper.emitted('changed')).toEqual([[STATE_OPEN]]);
+  });
+
+  it('does not emits changed event if clicking selected value', async () => {
+    createComponent({ state: STATE_OPEN });
+
+    await clickOpen();
+
+    expect(wrapper.emitted('changed')).toBeUndefined();
+  });
+});
diff --git a/spec/frontend/work_items/components/work_item_state_spec.js b/spec/frontend/work_items/components/work_item_state_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..a59dea3100614d7b317cbf26b91f0e0cc0d23d53
--- /dev/null
+++ b/spec/frontend/work_items/components/work_item_state_spec.js
@@ -0,0 +1,134 @@
+import { GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { mockTracking } from 'helpers/tracking_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import ItemState from '~/work_items/components/item_state.vue';
+import WorkItemState from '~/work_items/components/work_item_state.vue';
+import {
+  i18n,
+  STATE_OPEN,
+  STATE_CLOSED,
+  STATE_EVENT_CLOSE,
+  STATE_EVENT_REOPEN,
+} from '~/work_items/constants';
+import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
+import { updateWorkItemMutationResponse, workItemQueryResponse } from '../mock_data';
+
+describe('WorkItemState component', () => {
+  let wrapper;
+
+  Vue.use(VueApollo);
+
+  const mutationSuccessHandler = jest.fn().mockResolvedValue(updateWorkItemMutationResponse);
+
+  const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+  const findItemState = () => wrapper.findComponent(ItemState);
+
+  const createComponent = ({
+    state = STATE_OPEN,
+    loading = false,
+    mutationHandler = mutationSuccessHandler,
+  } = {}) => {
+    const { id, workItemType } = workItemQueryResponse.data.workItem;
+    wrapper = shallowMount(WorkItemState, {
+      apolloProvider: createMockApollo([[updateWorkItemMutation, mutationHandler]]),
+      propsData: {
+        loading,
+        workItem: {
+          id,
+          state,
+          workItemType,
+        },
+      },
+    });
+  };
+
+  afterEach(() => {
+    wrapper.destroy();
+  });
+
+  describe('when loading', () => {
+    beforeEach(() => {
+      createComponent({ loading: true });
+    });
+
+    it('renders loading spinner', () => {
+      expect(findLoadingIcon().exists()).toBe(true);
+    });
+
+    it('does not render state', () => {
+      expect(findItemState().exists()).toBe(false);
+    });
+  });
+
+  describe('when loaded', () => {
+    beforeEach(() => {
+      createComponent({ loading: false });
+    });
+
+    it('does not render loading spinner', () => {
+      expect(findLoadingIcon().exists()).toBe(false);
+    });
+
+    it('renders state', () => {
+      expect(findItemState().props('state')).toBe(workItemQueryResponse.data.workItem.state);
+    });
+  });
+
+  describe('when updating the state', () => {
+    it('calls a mutation', () => {
+      createComponent();
+
+      findItemState().vm.$emit('changed', STATE_CLOSED);
+
+      expect(mutationSuccessHandler).toHaveBeenCalledWith({
+        input: {
+          id: workItemQueryResponse.data.workItem.id,
+          stateEvent: STATE_EVENT_CLOSE,
+        },
+      });
+    });
+
+    it('calls a mutation with REOPEN', () => {
+      createComponent({
+        state: STATE_CLOSED,
+      });
+
+      findItemState().vm.$emit('changed', STATE_OPEN);
+
+      expect(mutationSuccessHandler).toHaveBeenCalledWith({
+        input: {
+          id: workItemQueryResponse.data.workItem.id,
+          stateEvent: STATE_EVENT_REOPEN,
+        },
+      });
+    });
+
+    it('emits an error message when the mutation was unsuccessful', async () => {
+      createComponent({ mutationHandler: jest.fn().mockRejectedValue('Error!') });
+
+      findItemState().vm.$emit('changed', STATE_CLOSED);
+      await waitForPromises();
+
+      expect(wrapper.emitted('error')).toEqual([[i18n.updateError]]);
+    });
+
+    it('tracks editing the state', async () => {
+      const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+
+      createComponent();
+
+      findItemState().vm.$emit('changed', STATE_CLOSED);
+      await waitForPromises();
+
+      expect(trackingSpy).toHaveBeenCalledWith('workItems:show', 'updated_state', {
+        category: 'workItems:show',
+        label: 'item_state',
+        property: 'type_Task',
+      });
+    });
+  });
+});
diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js
index 722e1708c153205a8fb254ded847c7b24a01e283..1b2944b60780294fc4e1c7cf50ab4350af1229d7 100644
--- a/spec/frontend/work_items/mock_data.js
+++ b/spec/frontend/work_items/mock_data.js
@@ -4,6 +4,7 @@ export const workItemQueryResponse = {
       __typename: 'WorkItem',
       id: 'gid://gitlab/WorkItem/1',
       title: 'Test',
+      state: 'OPEN',
       workItemType: {
         __typename: 'WorkItemType',
         id: 'gid://gitlab/WorkItems::Type/5',
@@ -21,6 +22,7 @@ export const updateWorkItemMutationResponse = {
         __typename: 'WorkItem',
         id: 'gid://gitlab/WorkItem/1',
         title: 'Updated title',
+        state: 'OPEN',
         workItemType: {
           __typename: 'WorkItemType',
           id: 'gid://gitlab/WorkItems::Type/5',
@@ -53,6 +55,7 @@ export const createWorkItemMutationResponse = {
         __typename: 'WorkItem',
         id: 'gid://gitlab/WorkItem/1',
         title: 'Updated title',
+        state: 'OPEN',
         workItemType: {
           __typename: 'WorkItemType',
           id: 'gid://gitlab/WorkItems::Type/5',