diff --git a/ee/app/assets/javascripts/dependencies/components/filtered_search/tokens/component_token.vue b/ee/app/assets/javascripts/dependencies/components/filtered_search/tokens/component_token.vue
index b4c0df7da9cfe7da8faf9bfc14f1ff9d22e688d8..ae42247fe463a59c900f2a43acbf841fa0490a8c 100644
--- a/ee/app/assets/javascripts/dependencies/components/filtered_search/tokens/component_token.vue
+++ b/ee/app/assets/javascripts/dependencies/components/filtered_search/tokens/component_token.vue
@@ -12,6 +12,8 @@ import { NAMESPACE_GROUP, NAMESPACE_PROJECT } from 'ee/dependencies/constants';
 import groupComponentsQuery from 'ee/dependencies/graphql/group_components.query.graphql';
 import projectComponentsQuery from 'ee/dependencies/graphql/project_components.query.graphql';
 
+const MIN_CHARS = 3;
+
 export default {
   components: {
     GlIcon,
@@ -40,12 +42,18 @@ export default {
       searchTerm: '',
       components: [],
       selectedComponents: [],
+      /**
+       * Not using apollo.loading because debounce causes a UX issue where "noResult" state
+       * shows during the debounce period. Manual setting the loading state allows to show
+       * loading during the debounce period.
+       *
+       * More info here:
+       * https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181132#note_2342436669
+       */
+      isLoadingComponents: false,
     };
   },
   computed: {
-    isLoadingComponents() {
-      return this.$apollo.queries.components.loading;
-    },
     filteredComponents() {
       if (!this.searchTerm) {
         return this.components;
@@ -93,6 +101,12 @@ export default {
         { namespaceType: this.namespaceType },
       );
     },
+    isSearchTermTooShort() {
+      return this.searchTerm.length < MIN_CHARS;
+    },
+    shouldShowPlaceholder() {
+      return this.isSearchTermTooShort && !this.components.length;
+    },
   },
   apollo: {
     components: {
@@ -110,13 +124,18 @@ export default {
         // Remove __typename
         return data[this.namespaceType]?.components?.map(({ id, name }) => ({ name, id }));
       },
+      result() {
+        this.isLoadingComponents = false;
+      },
       error() {
+        this.isLoadingComponents = false;
+
         createAlert({
           message: this.fetchErrorMessage,
         });
       },
       skip() {
-        return this.searchTerm === '';
+        return this.isSearchTermTooShort;
       },
     },
   },
@@ -135,9 +154,14 @@ export default {
       // the data can be either a string or an array, in which case we don't want to perform the search
       if (typeof token.data === 'string') {
         this.searchTerm = token.data.toLowerCase();
+        this.isLoadingComponents = token.data.length >= MIN_CHARS;
       }
     },
   },
+  i18n: {
+    placeholder: s__('Dependencies|Enter at least 3 characters to view available components.'),
+    noResult: s__('Dependencies|No components found.'),
+  },
 };
 </script>
 
@@ -180,6 +204,12 @@ export default {
             {{ component.name }}
           </div>
         </gl-filtered-search-suggestion>
+        <div v-if="shouldShowPlaceholder" class="gl-p-2 gl-text-secondary">
+          {{ $options.i18n.placeholder }}
+        </div>
+        <div v-else-if="!components.length" class="gl-p-2 gl-text-secondary">
+          {{ $options.i18n.noResult }}
+        </div>
       </template>
     </template>
   </gl-filtered-search-token>
diff --git a/ee/spec/frontend/dependencies/components/filtered_search/tokens/component_token_spec.js b/ee/spec/frontend/dependencies/components/filtered_search/tokens/component_token_spec.js
index ffc8a3c36b73fda21d78661d5e43fae04c2b200e..2c86fa64ecd9e28443d2785d6a89ee8693faa1ef 100644
--- a/ee/spec/frontend/dependencies/components/filtered_search/tokens/component_token_spec.js
+++ b/ee/spec/frontend/dependencies/components/filtered_search/tokens/component_token_spec.js
@@ -251,4 +251,37 @@ describe('ee/dependencies/components/filtered_search/tokens/component_token.vue'
       expect(findSuggestions().exists()).toBe(false);
     });
   });
+
+  describe('where there is a suggestion dropdown', () => {
+    it('displays when user types less than 3 characters', async () => {
+      createComponent({ mountFn: mountExtended });
+
+      const suggestionText = 'Enter at least 3 characters to view available components.';
+
+      await searchForComponent('we');
+      expect(wrapper.text()).toBe(suggestionText);
+
+      await searchForComponent('web');
+      expect(wrapper.text()).not.toBe(suggestionText);
+    });
+
+    it('displays when no results are found', async () => {
+      createComponent({
+        handlers: {
+          getGroupComponentsHandler: jest.fn().mockResolvedValue({
+            data: {
+              group: {
+                id: 'some-group-id',
+                components: [],
+              },
+            },
+          }),
+        },
+      });
+
+      await searchForComponent('XXXXXXXX');
+
+      expect(wrapper.text()).toBe('No components found.');
+    });
+  });
 });
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index ac4d67ff14a971a71bb1d9c917e8bf194eecb426..023e6efe67189f0ee295007d9315e6a39c1d3c0d 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -19473,6 +19473,9 @@ msgstr ""
 msgid "Dependencies|Direct dependents"
 msgstr ""
 
+msgid "Dependencies|Enter at least 3 characters to view available components."
+msgstr ""
+
 msgid "Dependencies|Error exporting the dependency list. Please reload the page."
 msgstr ""
 
@@ -19500,6 +19503,9 @@ msgstr ""
 msgid "Dependencies|Location"
 msgstr ""
 
+msgid "Dependencies|No components found."
+msgstr ""
+
 msgid "Dependencies|Packager"
 msgstr ""