diff --git a/app/assets/javascripts/work_items/components/work_item_assignees.vue b/app/assets/javascripts/work_items/components/work_item_assignees.vue
index 4d1c171772e97393e26b426e7903daee29d156f5..4692096941571d8c22c7672ac5dbcc1ca8943509 100644
--- a/app/assets/javascripts/work_items/components/work_item_assignees.vue
+++ b/app/assets/javascripts/work_items/components/work_item_assignees.vue
@@ -1,10 +1,23 @@
 <script>
-import { GlTokenSelector, GlIcon, GlAvatar, GlLink } from '@gitlab/ui';
+import { GlTokenSelector, GlIcon, GlAvatar, GlLink, GlSkeletonLoader } from '@gitlab/ui';
+import { debounce } from 'lodash';
 import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import userSearchQuery from '~/graphql_shared/queries/users_search.query.graphql';
+import { n__ } from '~/locale';
+import SidebarParticipant from '~/sidebar/components/assignees/sidebar_participant.vue';
+import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
 import localUpdateWorkItemMutation from '../graphql/local_update_work_item.mutation.graphql';
+import { i18n } from '../constants';
 
-function isClosingIcon(el) {
-  return el?.classList.contains('gl-token-close');
+function isTokenSelectorElement(el) {
+  return el?.classList.contains('gl-token-close') || el?.classList.contains('dropdown-item');
+}
+
+function addClass(el) {
+  return {
+    ...el,
+    class: 'gl-bg-transparent',
+  };
 }
 
 export default {
@@ -13,7 +26,10 @@ export default {
     GlIcon,
     GlAvatar,
     GlLink,
+    GlSkeletonLoader,
+    SidebarParticipant,
   },
+  inject: ['fullPath'],
   props: {
     workItemId: {
       type: String,
@@ -27,45 +43,95 @@ export default {
   data() {
     return {
       isEditing: false,
-      localAssignees: this.assignees.map((assignee) => ({
-        ...assignee,
-        class: 'gl-bg-transparent!',
-      })),
+      searchStarted: false,
+      localAssignees: this.assignees.map(addClass),
+      searchKey: '',
+      searchUsers: [],
     };
   },
-  computed: {
-    assigneeIds() {
-      return this.localAssignees.map((assignee) => assignee.id);
+  apollo: {
+    searchUsers: {
+      query() {
+        return userSearchQuery;
+      },
+      variables() {
+        return {
+          fullPath: this.fullPath,
+          search: this.searchKey,
+        };
+      },
+      skip() {
+        return !this.searchStarted;
+      },
+      update(data) {
+        return data.workspace?.users?.nodes.map((node) => addClass({ ...node, ...node.user }));
+      },
+      error() {
+        this.$emit('error', i18n.fetchError);
+      },
     },
+  },
+  computed: {
     assigneeListEmpty() {
       return this.assignees.length === 0;
     },
     containerClass() {
       return !this.isEditing ? 'gl-shadow-none! gl-bg-transparent!' : '';
     },
+    isLoading() {
+      return this.$apollo.queries.searchUsers.loading;
+    },
+    assigneeText() {
+      return n__('WorkItem|Assignee', 'WorkItem|Assignees', this.localAssignees.length);
+    },
+  },
+  watch: {
+    assignees(newVal) {
+      if (!this.isEditing) {
+        this.localAssignees = newVal.map(addClass);
+      }
+    },
+  },
+  created() {
+    this.debouncedSearchKeyUpdate = debounce(this.setSearchKey, DEFAULT_DEBOUNCE_AND_THROTTLE_MS);
   },
   methods: {
     getUserId(id) {
       return getIdFromGraphQLId(id);
     },
     setAssignees(e) {
-      if (isClosingIcon(e.relatedTarget) || !this.isEditing) return;
+      if (isTokenSelectorElement(e.relatedTarget) || !this.isEditing) return;
       this.isEditing = false;
       this.$apollo.mutate({
         mutation: localUpdateWorkItemMutation,
         variables: {
           input: {
             id: this.workItemId,
-            assigneeIds: this.assigneeIds,
+            assignees: this.localAssignees,
           },
         },
       });
     },
-    async focusTokenSelector() {
+    handleFocus() {
       this.isEditing = true;
+      this.searchStarted = true;
+    },
+    async focusTokenSelector() {
+      this.handleFocus();
       await this.$nextTick();
       this.$refs.tokenSelector.focusTextInput();
     },
+    handleMouseOver() {
+      this.timeout = setTimeout(() => {
+        this.searchStarted = true;
+      }, DEFAULT_DEBOUNCE_AND_THROTTLE_MS);
+    },
+    handleMouseOut() {
+      clearTimeout(this.timeout);
+    },
+    setSearchKey(value) {
+      this.searchKey = value;
+    },
   },
 };
 </script>
@@ -73,17 +139,21 @@ export default {
 <template>
   <div class="gl-display-flex gl-mb-4 work-item-assignees gl-relative">
     <span class="gl-font-weight-bold gl-w-15 gl-pt-2" data-testid="assignees-title">{{
-      __('Assignee(s)')
+      assigneeText
     }}</span>
     <gl-token-selector
       ref="tokenSelector"
       v-model="localAssignees"
-      hide-dropdown-with-no-items
       :container-class="containerClass"
+      :dropdown-items="searchUsers"
+      :loading="isLoading"
       class="gl-w-full gl-border gl-border-white gl-hover-border-gray-200 gl-rounded-base"
-      @token-remove="focusTokenSelector"
-      @focus="isEditing = true"
+      @input="focusTokenSelector"
+      @text-input="debouncedSearchKeyUpdate"
+      @focus="handleFocus"
       @blur="setAssignees"
+      @mouseover.native="handleMouseOver"
+      @mouseout.native="handleMouseOut"
     >
       <template #empty-placeholder>
         <div
@@ -106,6 +176,17 @@ export default {
           <span class="gl-pl-2">{{ token.name }}</span>
         </gl-link>
       </template>
+      <template #dropdown-item-content="{ dropdownItem }">
+        <sidebar-participant :user="dropdownItem" />
+      </template>
+      <template #loading-content>
+        <gl-skeleton-loader :height="170">
+          <rect width="380" height="20" x="10" y="15" rx="4" />
+          <rect width="280" height="20" x="10" y="50" rx="4" />
+          <rect width="380" height="20" x="10" y="95" rx="4" />
+          <rect width="280" height="20" x="10" y="130" rx="4" />
+        </gl-skeleton-loader>
+      </template>
     </gl-token-selector>
   </div>
 </template>
diff --git a/app/assets/javascripts/work_items/graphql/provider.js b/app/assets/javascripts/work_items/graphql/provider.js
index 09d929faae20815b82ec4dc4d61c3517182fb149..9266b4cdccba3e79c588cd4700d17e186f814f0d 100644
--- a/app/assets/javascripts/work_items/graphql/provider.js
+++ b/app/assets/javascripts/work_items/graphql/provider.js
@@ -70,9 +70,7 @@ export const resolvers = {
         const assigneesWidget = draftData.workItem.mockWidgets.find(
           (widget) => widget.type === WIDGET_TYPE_ASSIGNEE,
         );
-        assigneesWidget.nodes = assigneesWidget.nodes.filter((assignee) =>
-          input.assigneeIds.includes(assignee.id),
-        );
+        assigneesWidget.nodes = [...input.assignees];
       });
 
       cache.writeQuery({
diff --git a/app/assets/javascripts/work_items/graphql/typedefs.graphql b/app/assets/javascripts/work_items/graphql/typedefs.graphql
index bfe2f0fe0ce20e6b8a7309aed73d8b3ab6ea734b..de4bdad565964322c00af8b953ad9cacf2240e98 100644
--- a/app/assets/javascripts/work_items/graphql/typedefs.graphql
+++ b/app/assets/javascripts/work_items/graphql/typedefs.graphql
@@ -23,7 +23,7 @@ extend type WorkItem {
 
 type LocalWorkItemAssigneesInput {
   id: WorkItemID!
-  assigneeIds: [ID!]
+  assignees: [UserCore!]
 }
 
 type LocalWorkItemPayload {
diff --git a/app/assets/stylesheets/_page_specific_files.scss b/app/assets/stylesheets/_page_specific_files.scss
index be72ec334658090e60e09b7582f1adb384247b61..cf4a415446e066ade264865621028481d8d88465 100644
--- a/app/assets/stylesheets/_page_specific_files.scss
+++ b/app/assets/stylesheets/_page_specific_files.scss
@@ -32,3 +32,4 @@
 @import './pages/storage_quota';
 @import './pages/tree';
 @import './pages/users';
+@import './pages/work_items';
diff --git a/app/assets/stylesheets/pages/work_items.scss b/app/assets/stylesheets/pages/work_items.scss
new file mode 100644
index 0000000000000000000000000000000000000000..b98f55df1ed01fc3ffc0b1e0847197c6b53eab84
--- /dev/null
+++ b/app/assets/stylesheets/pages/work_items.scss
@@ -0,0 +1,4 @@
+.gl-token-selector-token-container {
+  display: flex;
+  align-items: center;
+}
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index b97c4e6ec289d3af884ceab23f6375b75f9f02dd..ecea30cb802a949a44842e9f73f0b919d0084b64 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -43507,6 +43507,11 @@ msgstr ""
 msgid "WorkItem|Are you sure you want to delete the work item? This action cannot be reversed."
 msgstr ""
 
+msgid "WorkItem|Assignee"
+msgid_plural "WorkItem|Assignees"
+msgstr[0] ""
+msgstr[1] ""
+
 msgid "WorkItem|Cancel"
 msgstr ""
 
diff --git a/spec/frontend/work_items/components/work_item_assignees_spec.js b/spec/frontend/work_items/components/work_item_assignees_spec.js
index 0552fe5050e75415d396f38c8e4b38d99cf8e26c..b2678293c05928a21d7bc3c65c8e17c326eec710 100644
--- a/spec/frontend/work_items/components/work_item_assignees_spec.js
+++ b/spec/frontend/work_items/components/work_item_assignees_spec.js
@@ -1,52 +1,59 @@
-import { GlLink, GlTokenSelector } from '@gitlab/ui';
-import { nextTick } from 'vue';
+import { GlLink, GlTokenSelector, GlSkeletonLoader } from '@gitlab/ui';
+import Vue, { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
 import { mountExtended } from 'helpers/vue_test_utils_helper';
+import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
+import userSearchQuery from '~/graphql_shared/queries/users_search.query.graphql';
+import workItemQuery from '~/work_items/graphql/work_item.query.graphql';
 import WorkItemAssignees from '~/work_items/components/work_item_assignees.vue';
-import localUpdateWorkItemMutation from '~/work_items/graphql/local_update_work_item.mutation.graphql';
-
-const mockAssignees = [
-  {
-    __typename: 'UserCore',
-    id: 'gid://gitlab/User/1',
-    avatarUrl: '',
-    webUrl: '',
-    name: 'John Doe',
-    username: 'doe_I',
-  },
-  {
-    __typename: 'UserCore',
-    id: 'gid://gitlab/User/2',
-    avatarUrl: '',
-    webUrl: '',
-    name: 'Marcus Rutherford',
-    username: 'ruthfull',
-  },
-];
+import { i18n } from '~/work_items/constants';
+import { temporaryConfig, resolvers } from '~/work_items/graphql/provider';
+import { projectMembersResponse, mockAssignees, workItemQueryResponse } from '../mock_data';
 
-const workItemId = 'gid://gitlab/WorkItem/1';
+Vue.use(VueApollo);
 
-const mutate = jest.fn();
+const workItemId = 'gid://gitlab/WorkItem/1';
 
 describe('WorkItemAssignees component', () => {
   let wrapper;
 
   const findAssigneeLinks = () => wrapper.findAllComponents(GlLink);
   const findTokenSelector = () => wrapper.findComponent(GlTokenSelector);
+  const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
 
   const findEmptyState = () => wrapper.findByTestId('empty-state');
 
-  const createComponent = ({ assignees = mockAssignees } = {}) => {
+  const successSearchQueryHandler = jest.fn().mockResolvedValue(projectMembersResponse);
+  const errorHandler = jest.fn().mockRejectedValue('Houston, we have a problem');
+
+  const createComponent = ({
+    assignees = mockAssignees,
+    searchQueryHandler = successSearchQueryHandler,
+  } = {}) => {
+    const apolloProvider = createMockApollo([[userSearchQuery, searchQueryHandler]], resolvers, {
+      typePolicies: temporaryConfig.cacheConfig.typePolicies,
+    });
+
+    apolloProvider.clients.defaultClient.writeQuery({
+      query: workItemQuery,
+      variables: {
+        id: workItemId,
+      },
+      data: workItemQueryResponse.data,
+    });
+
     wrapper = mountExtended(WorkItemAssignees, {
+      provide: {
+        fullPath: 'test-project-path',
+      },
       propsData: {
         assignees,
         workItemId,
       },
-      mocks: {
-        $apollo: {
-          mutate,
-        },
-      },
       attachTo: document.body,
+      apolloProvider,
     });
   };
 
@@ -54,40 +61,114 @@ describe('WorkItemAssignees component', () => {
     wrapper.destroy();
   });
 
-  it('should pass the correct data-user-id attribute', () => {
+  it('passes the correct data-user-id attribute', () => {
     createComponent();
 
     expect(findAssigneeLinks().at(0).attributes('data-user-id')).toBe('1');
   });
 
-  describe('when there are assignees', () => {
-    beforeEach(() => {
-      createComponent();
-    });
+  it('focuses token selector on token selector input event', async () => {
+    createComponent();
+    findTokenSelector().vm.$emit('input', [mockAssignees[0]]);
+    await nextTick();
 
-    it('should focus token selector on token removal', async () => {
-      findTokenSelector().vm.$emit('token-remove', mockAssignees[0].id);
-      await nextTick();
+    expect(findEmptyState().exists()).toBe(false);
+    expect(findTokenSelector().element.contains(document.activeElement)).toBe(true);
+  });
 
-      expect(findEmptyState().exists()).toBe(false);
-      expect(findTokenSelector().element.contains(document.activeElement)).toBe(true);
-    });
+  it('calls a mutation on clicking outside the token selector', async () => {
+    createComponent();
+    findTokenSelector().vm.$emit('input', [mockAssignees[0]]);
+    findTokenSelector().vm.$emit('blur', new FocusEvent({ relatedTarget: null }));
+    await waitForPromises();
 
-    it('should call a mutation on clicking outside the token selector', async () => {
-      findTokenSelector().vm.$emit('input', [mockAssignees[0]]);
-      findTokenSelector().vm.$emit('token-remove');
-      await nextTick();
-      expect(mutate).not.toHaveBeenCalled();
-
-      findTokenSelector().vm.$emit('blur', new FocusEvent({ relatedTarget: null }));
-      await nextTick();
-
-      expect(mutate).toHaveBeenCalledWith({
-        mutation: localUpdateWorkItemMutation,
-        variables: {
-          input: { id: workItemId, assigneeIds: [mockAssignees[0].id] },
-        },
-      });
-    });
+    expect(findTokenSelector().props('selectedTokens')).toEqual([mockAssignees[0]]);
+  });
+
+  it('does not start user search by default', () => {
+    createComponent();
+
+    expect(findTokenSelector().props('loading')).toBe(false);
+    expect(findTokenSelector().props('dropdownItems')).toEqual([]);
+  });
+
+  it('starts user search on hovering for more than 250ms', async () => {
+    createComponent();
+    findTokenSelector().trigger('mouseover');
+    jest.advanceTimersByTime(DEFAULT_DEBOUNCE_AND_THROTTLE_MS);
+    await nextTick();
+
+    expect(findTokenSelector().props('loading')).toBe(true);
+  });
+
+  it('starts user search on focusing token selector', async () => {
+    createComponent();
+    findTokenSelector().vm.$emit('focus');
+    await nextTick();
+
+    expect(findTokenSelector().props('loading')).toBe(true);
+  });
+
+  it('does not start searching if token-selector was hovered for less than 250ms', async () => {
+    createComponent();
+    findTokenSelector().trigger('mouseover');
+    jest.advanceTimersByTime(100);
+    await nextTick();
+
+    expect(findTokenSelector().props('loading')).toBe(false);
+  });
+
+  it('does not start searching if cursor was moved out from token selector before 250ms passed', async () => {
+    createComponent();
+    findTokenSelector().trigger('mouseover');
+    jest.advanceTimersByTime(100);
+
+    findTokenSelector().trigger('mouseout');
+    jest.advanceTimersByTime(DEFAULT_DEBOUNCE_AND_THROTTLE_MS);
+    await nextTick();
+
+    expect(findTokenSelector().props('loading')).toBe(false);
+  });
+
+  it('shows skeleton loader on dropdown when loading users', async () => {
+    createComponent();
+    findTokenSelector().vm.$emit('focus');
+    await nextTick();
+
+    expect(findSkeletonLoader().exists()).toBe(true);
+  });
+
+  it('shows correct user list in dropdown when loaded', async () => {
+    createComponent();
+    findTokenSelector().vm.$emit('focus');
+    await nextTick();
+
+    expect(findSkeletonLoader().exists()).toBe(true);
+
+    await waitForPromises();
+
+    expect(findSkeletonLoader().exists()).toBe(false);
+    expect(findTokenSelector().props('dropdownItems')).toHaveLength(2);
+  });
+
+  it('emits error event if search users query fails', async () => {
+    createComponent({ searchQueryHandler: errorHandler });
+    findTokenSelector().vm.$emit('focus');
+    await waitForPromises();
+
+    expect(wrapper.emitted('error')).toEqual([[i18n.fetchError]]);
+  });
+
+  it('should search for users with correct key after text input', async () => {
+    const searchKey = 'Hello';
+
+    createComponent();
+    findTokenSelector().vm.$emit('focus');
+    findTokenSelector().vm.$emit('text-input', searchKey);
+    await waitForPromises();
+
+    expect(successSearchQueryHandler).toHaveBeenCalledWith(
+      expect.objectContaining({ search: searchKey }),
+    );
   });
 });
diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js
index 91dfc61198cdaa84f52b154b5eb790fe4c820288..116bf48901dec337cf55d35cf9791875edda1e1b 100644
--- a/spec/frontend/work_items/mock_data.js
+++ b/spec/frontend/work_items/mock_data.js
@@ -300,3 +300,60 @@ export const availableWorkItemsResponse = {
     },
   },
 };
+
+export const projectMembersResponse = {
+  data: {
+    workspace: {
+      id: '1',
+      __typename: 'Project',
+      users: {
+        nodes: [
+          {
+            id: 'user-1',
+            user: {
+              __typename: 'UserCore',
+              id: 'gid://gitlab/User/1',
+              avatarUrl:
+                'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+              name: 'Administrator',
+              username: 'root',
+              webUrl: '/root',
+              status: null,
+            },
+          },
+          {
+            id: 'user-2',
+            user: {
+              __typename: 'UserCore',
+              id: 'gid://gitlab/User/5',
+              avatarUrl: '/avatar2',
+              name: 'rookie',
+              username: 'rookie',
+              webUrl: 'rookie',
+              status: null,
+            },
+          },
+        ],
+      },
+    },
+  },
+};
+
+export const mockAssignees = [
+  {
+    __typename: 'UserCore',
+    id: 'gid://gitlab/User/1',
+    avatarUrl: '',
+    webUrl: '',
+    name: 'John Doe',
+    username: 'doe_I',
+  },
+  {
+    __typename: 'UserCore',
+    id: 'gid://gitlab/User/2',
+    avatarUrl: '',
+    webUrl: '',
+    name: 'Marcus Rutherford',
+    username: 'ruthfull',
+  },
+];