diff --git a/ee/app/assets/javascripts/dependencies/components/filtered_search/group_dependencies_filtered_search.vue b/ee/app/assets/javascripts/dependencies/components/filtered_search/group_dependencies_filtered_search.vue
index 908da197e6a4e2b974e8ef24ea592bac2b1286f2..1dd6a6727607202d403cbf9ccd422080e9091bbb 100644
--- a/ee/app/assets/javascripts/dependencies/components/filtered_search/group_dependencies_filtered_search.vue
+++ b/ee/app/assets/javascripts/dependencies/components/filtered_search/group_dependencies_filtered_search.vue
@@ -5,8 +5,10 @@ import { mapActions, mapState } from 'vuex';
 import { __, s__ } from '~/locale';
 import { helpPagePath } from '~/helpers/help_page_helper';
 import { OPERATORS_IS } from '~/vue_shared/components/filtered_search_bar/constants';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
 import LicenseToken from './tokens/license_token.vue';
 import ProjectToken from './tokens/project_token.vue';
+import ComponentToken from './tokens/component_token.vue';
 
 export default {
   components: {
@@ -15,6 +17,7 @@ export default {
     GlLink,
     GlSprintf,
   },
+  mixins: [glFeatureFlagsMixin()],
   inject: ['belowGroupLimit'],
   data() {
     return {
@@ -42,6 +45,18 @@ export default {
           token: ProjectToken,
           operators: OPERATORS_IS,
         },
+        ...(this.glFeatures.groupLevelDependenciesFilteringByComponent
+          ? [
+              {
+                type: 'component_ids',
+                title: __('Component'),
+                multiSelect: true,
+                unique: true,
+                token: ComponentToken,
+                operators: OPERATORS_IS,
+              },
+            ]
+          : []),
       ];
     },
   },
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
new file mode 100644
index 0000000000000000000000000000000000000000..f4c48d75a2afaf785893db353bd2aea44e888005
--- /dev/null
+++ b/ee/app/assets/javascripts/dependencies/components/filtered_search/tokens/component_token.vue
@@ -0,0 +1,168 @@
+<script>
+import {
+  GlIcon,
+  GlFilteredSearchToken,
+  GlFilteredSearchSuggestion,
+  GlLoadingIcon,
+  GlIntersperse,
+} from '@gitlab/ui';
+import { createAlert } from '~/alert';
+import { s__ } from '~/locale';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+
+export default {
+  components: {
+    GlIcon,
+    GlFilteredSearchToken,
+    GlFilteredSearchSuggestion,
+    GlLoadingIcon,
+    GlIntersperse,
+  },
+  props: {
+    config: {
+      type: Object,
+      required: true,
+    },
+    value: {
+      type: Object,
+      required: true,
+    },
+    active: {
+      type: Boolean,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      searchTerm: '',
+      components: [],
+      selectedComponents: [],
+      isLoadingComponents: true,
+    };
+  },
+  computed: {
+    filteredComponents() {
+      if (!this.searchTerm) {
+        return this.components;
+      }
+
+      const nameIncludesSearchTerm = (component) =>
+        component.name.toLowerCase().includes(this.searchTerm);
+      const isSelected = (component) => this.selectedComponentNames.includes(component.name);
+
+      return this.components.filter(
+        (component) => nameIncludesSearchTerm(component) || isSelected(component),
+      );
+    },
+    selectedComponentNames() {
+      return this.selectedComponents.map(({ name }) => name);
+    },
+    selectedComponentIds() {
+      return this.selectedComponents.map(({ id }) => getIdFromGraphQLId(id));
+    },
+    tokenValue() {
+      return {
+        ...this.value,
+        // when the token is active (dropdown is open), we set the value to null to prevent an UX issue
+        // in which only the last selected item is being displayed.
+        // more information: https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2381
+        data: this.active ? null : this.selectedComponentNames,
+      };
+    },
+  },
+  created() {
+    this.fetchComponents();
+  },
+  methods: {
+    async fetchComponents() {
+      try {
+        this.isLoadingComponents = true;
+        // Note: This is just a placeholder. Adding the actual fetch logic will be addressed in a seperate issue:
+        // https://gitlab.com/gitlab-org/gitlab/-/issues/442407
+        this.components = await new Promise((resolve) => {
+          resolve([
+            { id: 'gid://gitlab/Component/1', name: 'ComponentOne' },
+            { id: 'gid://gitlab/Component/2', name: 'ComponentTwo' },
+            { id: 'gid://gitlab/Component/3', name: 'ComponentThree' },
+          ]);
+        });
+      } catch {
+        createAlert({
+          message: this.$options.i18n.fetchErrorMessage,
+        });
+      } finally {
+        this.isLoadingComponents = false;
+      }
+    },
+    isComponentSelected(component) {
+      return this.selectedComponents.some((c) => c.id === component.id);
+    },
+    toggleSelectedComponent(component) {
+      if (this.isComponentSelected(component)) {
+        this.selectedComponents = this.selectedComponents.filter((c) => c.id !== component.id);
+      } else {
+        this.selectedComponents.push(component);
+      }
+    },
+    handleInput(token) {
+      // the dropdown shows a list of component names but we need to emit the project ids for filtering
+      this.$emit('input', { ...token, data: this.selectedComponentIds });
+    },
+    setSearchTerm(token) {
+      // 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();
+      }
+    },
+  },
+  i18n: {
+    fetchErrorMessage: s__(
+      'Dependencies|There was an error fetching the components for this group. Please try again later.',
+    ),
+  },
+};
+</script>
+
+<template>
+  <gl-filtered-search-token
+    :config="config"
+    v-bind="{ ...$props, ...$attrs }"
+    :multi-select-values="selectedComponentNames"
+    :value="tokenValue"
+    v-on="{ ...$listeners, input: handleInput }"
+    @select="toggleSelectedComponent"
+    @input="setSearchTerm"
+  >
+    <template #view>
+      <gl-intersperse data-testid="selected-components">
+        <span
+          v-for="selectedComponentName in selectedComponentNames"
+          :key="selectedComponentName"
+          >{{ selectedComponentName }}</span
+        >
+      </gl-intersperse>
+    </template>
+    <template #suggestions>
+      <gl-loading-icon v-if="isLoadingComponents" size="sm" />
+      <template v-else>
+        <gl-filtered-search-suggestion
+          v-for="component in filteredComponents"
+          :key="component.id"
+          :value="component"
+        >
+          <div class="gl-display-flex gl-align-items-center">
+            <gl-icon
+              v-if="config.multiSelect"
+              name="check"
+              class="gl-mr-3 gl-flex-shrink-0 gl-text-gray-700"
+              :class="{
+                'gl-visibility-hidden': !selectedComponentNames.includes(component.name),
+              }"
+            />
+            {{ component.name }}
+          </div>
+        </gl-filtered-search-suggestion>
+      </template>
+    </template>
+  </gl-filtered-search-token>
+</template>
diff --git a/ee/app/controllers/groups/dependencies_controller.rb b/ee/app/controllers/groups/dependencies_controller.rb
index acb4b232d26eab3ecf5e5a98e0436ced1fe2aa26..cfadee01e8e6a66a2086dd6419d2b7c3e7aeaef5 100644
--- a/ee/app/controllers/groups/dependencies_controller.rb
+++ b/ee/app/controllers/groups/dependencies_controller.rb
@@ -4,6 +4,10 @@ module Groups
   class DependenciesController < Groups::ApplicationController
     include GovernUsageGroupTracking
 
+    before_action only: :index do
+      push_frontend_feature_flag(:group_level_dependencies_filtering_by_component, group)
+    end
+
     before_action :authorize_read_dependency_list!
     before_action :validate_project_ids_limit!, only: :index
 
diff --git a/ee/config/feature_flags/wip/group_level_dependencies_filtering_by_component.yml b/ee/config/feature_flags/wip/group_level_dependencies_filtering_by_component.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c23416e76f440deba399539274767afa9f1f6dfc
--- /dev/null
+++ b/ee/config/feature_flags/wip/group_level_dependencies_filtering_by_component.yml
@@ -0,0 +1,9 @@
+---
+name: group_level_dependencies_filtering_by_component
+feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/442406
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/148257
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/454589
+milestone: '16.11'
+group: group::threat insights
+type: wip
+default_enabled: false
diff --git a/ee/spec/frontend/dependencies/components/filtered_search/group_dependencies_filtered_search_spec.js b/ee/spec/frontend/dependencies/components/filtered_search/group_dependencies_filtered_search_spec.js
index 4784086413b013c4da9d590fd3fa4413499b61ca..b86a3ef48566d9d4d192e768c2a5ada6b41f8233 100644
--- a/ee/spec/frontend/dependencies/components/filtered_search/group_dependencies_filtered_search_spec.js
+++ b/ee/spec/frontend/dependencies/components/filtered_search/group_dependencies_filtered_search_spec.js
@@ -3,6 +3,7 @@ import { GlFilteredSearch, GlPopover, GlSprintf } from '@gitlab/ui';
 import GroupDependenciesFilteredSearch from 'ee/dependencies/components/filtered_search/group_dependencies_filtered_search.vue';
 import LicenseToken from 'ee/dependencies/components/filtered_search/tokens/license_token.vue';
 import ProjectToken from 'ee/dependencies/components/filtered_search/tokens/project_token.vue';
+import ComponentToken from 'ee/dependencies/components/filtered_search/tokens/component_token.vue';
 import createStore from 'ee/dependencies/store';
 
 describe('GroupDependenciesFilteredSearch', () => {
@@ -17,7 +18,10 @@ describe('GroupDependenciesFilteredSearch', () => {
   const createComponent = (mountOptions = {}) => {
     wrapper = shallowMount(GroupDependenciesFilteredSearch, {
       store,
-      provide: { belowGroupLimit: true },
+      provide: {
+        belowGroupLimit: true,
+        glFeatures: { groupLevelDependenciesFilteringByComponent: true },
+      },
       stubs: {
         GlSprintf,
       },
@@ -47,9 +51,10 @@ describe('GroupDependenciesFilteredSearch', () => {
       });
 
       it.each`
-        tokenTitle   | tokenConfig
-        ${'License'} | ${{ title: 'License', type: 'licenses', multiSelect: true, token: LicenseToken }}
-        ${'Project'} | ${{ title: 'Project', type: 'project_ids', multiSelect: true, token: ProjectToken }}
+        tokenTitle     | tokenConfig
+        ${'License'}   | ${{ title: 'License', type: 'licenses', multiSelect: true, token: LicenseToken }}
+        ${'Project'}   | ${{ title: 'Project', type: 'project_ids', multiSelect: true, token: ProjectToken }}
+        ${'Component'} | ${{ title: 'Component', type: 'component_ids', multiSelect: true, token: ComponentToken }}
       `('contains a "$tokenTitle" search token', ({ tokenConfig }) => {
         expect(findFilteredSearch().props('availableTokens')).toMatchObject(
           expect.arrayContaining([
@@ -102,4 +107,23 @@ describe('GroupDependenciesFilteredSearch', () => {
       );
     });
   });
+
+  describe('with "groupLevelDependenciesFilteringByComponent" feature flag disabled', () => {
+    beforeEach(() => {
+      createComponent({
+        provide: {
+          belowGroupLimit: true,
+          glFeatures: { groupLevelDependenciesFilteringByComponent: false },
+        },
+      });
+    });
+
+    it('does not show the Component token', () => {
+      expect(findFilteredSearch().props('availableTokens')).not.toContainEqual(
+        expect.objectContaining({
+          title: 'Component',
+        }),
+      );
+    });
+  });
 });
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
new file mode 100644
index 0000000000000000000000000000000000000000..d2e1c67f95b9a9f0e049e0fb17318e9d5403c6cc
--- /dev/null
+++ b/ee/spec/frontend/dependencies/components/filtered_search/tokens/component_token_spec.js
@@ -0,0 +1,172 @@
+import {
+  GlFilteredSearchSuggestion,
+  GlFilteredSearchToken,
+  GlIcon,
+  GlIntersperse,
+  GlLoadingIcon,
+} from '@gitlab/ui';
+import { nextTick } from 'vue';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import ComponentToken from 'ee/dependencies/components/filtered_search/tokens/component_token.vue';
+import waitForPromises from 'helpers/wait_for_promises';
+import { createAlert } from '~/alert';
+
+const TEST_COMPONENTS = [
+  { id: 'gid://gitlab/Component/1', name: 'ComponentOne' },
+  { id: 'gid://gitlab/Component/2', name: 'ComponentTwo' },
+  { id: 'gid://gitlab/Component/3', name: 'ComponentThree' },
+];
+
+jest.mock('~/alert');
+
+describe('ee/dependencies/components/filtered_search/tokens/component_token.vue', () => {
+  let wrapper;
+
+  const createComponent = ({ propsData = {} } = {}) => {
+    wrapper = shallowMountExtended(ComponentToken, {
+      propsData: {
+        config: {
+          multiSelect: true,
+        },
+        value: {},
+        active: false,
+        ...propsData,
+      },
+      stubs: {
+        GlIntersperse,
+      },
+    });
+  };
+
+  const isLoadingSuggestions = () => wrapper.findComponent(GlLoadingIcon).exists();
+  const findSuggestions = () => wrapper.findAllComponents(GlFilteredSearchSuggestion);
+  const findFirstSearchSuggestionIcon = () => findSuggestions().at(0).findComponent(GlIcon);
+  const findFilteredSearchToken = () => wrapper.findComponent(GlFilteredSearchToken);
+  const selectComponent = (component) => {
+    findFilteredSearchToken().vm.$emit('select', component);
+    return nextTick();
+  };
+  const searchForComponent = (searchTerm = '') => {
+    findFilteredSearchToken().vm.$emit('input', { data: searchTerm });
+    return waitForPromises();
+  };
+
+  describe('when the component is initially rendered', () => {
+    it('shows a loading indicator while fetching the list of licenses', () => {
+      createComponent();
+
+      expect(isLoadingSuggestions()).toBe(true);
+    });
+
+    it.each([
+      { active: true, expectedValue: null },
+      { active: false, expectedValue: { data: [] } },
+    ])(
+      'passes "$expectedValue" to the search-token when the dropdown is open: "$active"',
+      async ({ active, expectedValue }) => {
+        createComponent({
+          propsData: {
+            active,
+            value: { data: [] },
+          },
+        });
+
+        await waitForPromises();
+
+        expect(findFilteredSearchToken().props('value')).toEqual(
+          expect.objectContaining(expectedValue),
+        );
+      },
+    );
+  });
+
+  describe('when the list of components have been fetched successfully', () => {
+    beforeEach(async () => {
+      createComponent();
+
+      await waitForPromises();
+    });
+
+    it('does not show an error message', () => {
+      expect(createAlert).not.toHaveBeenCalled();
+    });
+
+    it('does not show a loading indicator', () => {
+      expect(isLoadingSuggestions()).toBe(false);
+    });
+
+    it('shows a list of project suggestions', () => {
+      const suggestions = findSuggestions();
+
+      expect(suggestions).toHaveLength(TEST_COMPONENTS.length);
+
+      expect(suggestions.at(0).text()).toBe(TEST_COMPONENTS[0].name);
+      expect(suggestions.at(1).text()).toBe(TEST_COMPONENTS[1].name);
+      expect(suggestions.at(2).text()).toBe(TEST_COMPONENTS[2].name);
+    });
+
+    describe('when a user selects projects to be filtered', () => {
+      it('displays a check-icon next to the selected project', async () => {
+        expect(findFirstSearchSuggestionIcon().classes()).toContain('gl-visibility-hidden');
+
+        await selectComponent(TEST_COMPONENTS[0]);
+
+        expect(findFirstSearchSuggestionIcon().classes()).not.toContain('gl-visibility-hidden');
+      });
+
+      it('shows a comma seperated list of selected projects', async () => {
+        await selectComponent(TEST_COMPONENTS[0]);
+        await selectComponent(TEST_COMPONENTS[1]);
+
+        expect(wrapper.findByTestId('selected-components').text()).toMatchInterpolatedText(
+          `${TEST_COMPONENTS[0].name}, ${TEST_COMPONENTS[1].name}`,
+        );
+      });
+
+      it(`emits the selected project's IDs without the GraphQL prefix`, async () => {
+        const tokenData = {
+          id: 'component_id',
+          type: 'component',
+          operator: '=',
+        };
+
+        const expectedIds = TEST_COMPONENTS.map((component) =>
+          Number(component.id.replace('gid://gitlab/Component/', '')),
+        );
+
+        await selectComponent(TEST_COMPONENTS[0]);
+        await selectComponent(TEST_COMPONENTS[1]);
+        await selectComponent(TEST_COMPONENTS[2]);
+
+        findFilteredSearchToken().vm.$emit('input', tokenData);
+
+        expect(wrapper.emitted('input')).toEqual([
+          [
+            {
+              ...tokenData,
+              data: expectedIds,
+            },
+          ],
+        ]);
+      });
+    });
+
+    describe('when a user enters a search term', () => {
+      it('shows the filtered list of components', async () => {
+        await searchForComponent(TEST_COMPONENTS[0].name);
+
+        expect(findSuggestions()).toHaveLength(1);
+        expect(findSuggestions().at(0).text()).toBe(TEST_COMPONENTS[0].name);
+      });
+
+      it('shows the already selected components in the filtered list', async () => {
+        await selectComponent(TEST_COMPONENTS[0]);
+        await searchForComponent(TEST_COMPONENTS[1].name);
+
+        expect(findSuggestions()).toHaveLength(2);
+        expect(findSuggestions().at(0).text()).toBe(TEST_COMPONENTS[0].name);
+        expect(findSuggestions().at(1).text()).toBe(TEST_COMPONENTS[1].name);
+      });
+    });
+  });
+});
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 96350d402c6e14b601d03909338bc450a4a05d7f..9a5965acf0160f00825b578f562c5c0b2dd661d3 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -17222,6 +17222,9 @@ msgstr ""
 msgid "Dependencies|There was a problem fetching vulnerabilities."
 msgstr ""
 
+msgid "Dependencies|There was an error fetching the components for this group. Please try again later."
+msgstr ""
+
 msgid "Dependencies|There was an error fetching the projects for this group. Please try again later."
 msgstr ""