diff --git a/core/modules/node/src/Plugin/views/wizard/Node.php b/core/modules/node/src/Plugin/views/wizard/Node.php
index bef4ddc8da5..810fd6b5ded 100644
--- a/core/modules/node/src/Plugin/views/wizard/Node.php
+++ b/core/modules/node/src/Plugin/views/wizard/Node.php
@@ -168,7 +168,7 @@ protected function defaultDisplayFiltersUser(array $form, FormStateInterface $fo
         'table' => 'taxonomy_index',
         'field' => 'tid',
         'value' => $tids,
-        'vid' => $vid,
+        'vids' => [$vid],
         'plugin_id' => 'taxonomy_index_tid',
       ];
       // If the user entered more than one valid term in the autocomplete
diff --git a/core/modules/taxonomy/config/schema/taxonomy.views.schema.yml b/core/modules/taxonomy/config/schema/taxonomy.views.schema.yml
index 9ec97581852..9b4ed296c17 100644
--- a/core/modules/taxonomy/config/schema/taxonomy.views.schema.yml
+++ b/core/modules/taxonomy/config/schema/taxonomy.views.schema.yml
@@ -119,10 +119,17 @@ views.field.taxonomy_index_tid:
 views.filter.taxonomy_index_tid:
   type: views.filter.many_to_one
   label: 'Taxonomy term ID'
+  deprecated: "The 'vid' key in 'views.filter.taxonomy_index_tid' config schema is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Update your view to use the 'vids' key instead. See https://www.drupal.org/node/3162414"
   mapping:
     vid:
       type: string
       label: 'Vocabulary'
+    vids:
+      type: sequence
+      label: 'Vocabularies'
+      sequence:
+        type: string
+        label: 'Vocabulary'
     type:
       type: string
       label: 'Selection type'
diff --git a/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTid.php b/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTid.php
index ebaf9540f01..2c76b1d42ca 100644
--- a/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTid.php
+++ b/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTid.php
@@ -5,11 +5,12 @@
 use Drupal\Core\Entity\Element\EntityAutocomplete;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Session\AccountInterface;
-use Drupal\taxonomy\Entity\Term;
+use Drupal\taxonomy\TermInterface;
 use Drupal\taxonomy\TermStorageInterface;
+use Drupal\taxonomy\VocabularyInterface;
 use Drupal\taxonomy\VocabularyStorageInterface;
-use Drupal\views\Attribute\ViewsFilter;
 use Drupal\views\ViewExecutable;
+use Drupal\views\Attribute\ViewsFilter;
 use Drupal\views\Plugin\views\display\DisplayPluginBase;
 use Drupal\views\Plugin\views\filter\ManyToOne;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -95,7 +96,7 @@ public function init(ViewExecutable $view, DisplayPluginBase $display, ?array &$
     parent::init($view, $display, $options);

     if (!empty($this->definition['vocabulary'])) {
-      $this->options['vid'] = $this->definition['vocabulary'];
+      $this->options['vids'] = [$this->definition['vocabulary']];
     }
   }

@@ -121,7 +122,7 @@ protected function defineOptions() {

     $options['type'] = ['default' => 'textfield'];
     $options['limit'] = ['default' => TRUE];
-    $options['vid'] = ['default' => ''];
+    $options['vids'] = ['default' => []];
     $options['hierarchy'] = ['default' => FALSE];
     $options['error_message'] = ['default' => TRUE];

@@ -132,26 +133,17 @@ protected function defineOptions() {
    * {@inheritdoc}
    */
   public function buildExtraOptionsForm(&$form, FormStateInterface $form_state) {
-    $vocabularies = $this->vocabularyStorage->loadMultiple();
-    $options = [];
-    foreach ($vocabularies as $voc) {
-      $options[$voc->id()] = $voc->label();
-    }
-
     if ($this->options['limit']) {
       // We only do this when the form is displayed.
-      if (empty($this->options['vid'])) {
-        $first_vocabulary = reset($vocabularies);
-        $this->options['vid'] = $first_vocabulary->id();
-      }
-
+      $vocabularies = $this->vocabularyStorage->loadMultiple();
       if (empty($this->definition['vocabulary'])) {
-        $form['vid'] = [
-          '#type' => 'radios',
+        $form['vids'] = [
+          '#type' => 'checkboxes',
           '#title' => $this->t('Vocabulary'),
-          '#options' => $options,
-          '#description' => $this->t('Select which vocabulary to show terms for in the regular options.'),
-          '#default_value' => $this->options['vid'],
+          '#options' => $this->getVocabularyLabels($vocabularies),
+          '#description' => $this->t('Select which vocabularies to show terms for in the regular options.'),
+          '#default_value' => $this->options['vids'],
+          '#required' => TRUE,
         ];
       }
     }
@@ -175,22 +167,34 @@ public function buildExtraOptionsForm(&$form, FormStateInterface $form_state) {
     ];
   }

+  /**
+   * {@inheritdoc}
+   */
+  public function submitExtraOptionsForm($form, FormStateInterface $form_state): void {
+    $vids = $form_state->getValue(['options', 'vids']);
+    $form_state->setValue(['options', 'vids'], array_keys(array_filter($vids)));
+  }
+
   /**
    * {@inheritdoc}
    */
   protected function valueForm(&$form, FormStateInterface $form_state) {
-    $vocabulary = $this->vocabularyStorage->load($this->options['vid']);
-    if (empty($vocabulary) && $this->options['limit']) {
+    $vocabularies = $this->vocabularyStorage->loadMultiple($this->options['vids']);
+    if (empty($vocabularies) && $this->options['limit']) {
       $form['markup'] = [
-        '#markup' => '<div class="js-form-item form-item">' . $this->t('An invalid vocabulary is selected. Change it in the options.') . '</div>',
+        '#markup' => '<div class="js-form-item form-item">' . $this->t('Invalid or no vocabularies are selected. Select valid vocabularies in filter settings.') . '</div>',
       ];
       return;
     }
+    $form['value'] = [
+      '#title' => $this->options['limit'] ? $this->formatPlural(count($vocabularies), 'Select terms from vocabulary @vocabulary_labels', 'Select terms from vocabularies @vocabulary_labels', [
+        '@vocabulary_labels' => "'" . implode("', '", $this->getVocabularyLabels($vocabularies)) . "'",
+      ]) : $this->t('Select terms'),
+    ];

     if ($this->options['type'] == 'textfield') {
-      $terms = $this->value ? Term::loadMultiple(($this->value)) : [];
-      $form['value'] = [
-        '#title' => $this->options['limit'] ? $this->t('Select terms from vocabulary @voc', ['@voc' => $vocabulary->label()]) : $this->t('Select terms'),
+      $terms = $this->value ? $this->termStorage->loadMultiple($this->value) : [];
+      $form['value'] += [
         '#type' => 'textfield',
         '#default_value' => EntityAutocomplete::getEntityLabels($terms),
       ];
@@ -198,30 +203,26 @@
       if ($this->options['limit']) {
         $form['value']['#type'] = 'entity_autocomplete';
         $form['value']['#target_type'] = 'taxonomy_term';
-        $form['value']['#selection_settings']['target_bundles'] = [$vocabulary->id()];
+        $form['value']['#selection_settings']['target_bundles'] = array_keys($vocabularies);
         $form['value']['#tags'] = TRUE;
         $form['value']['#process_default_value'] = FALSE;
       }
     }
     else {
+      $options = [];
       if (!empty($this->options['hierarchy']) && $this->options['limit']) {
-        $tree = $this->termStorage->loadTree($vocabulary->id(), 0, NULL, TRUE);
-        $options = [];
-
-        if ($tree) {
-          foreach ($tree as $term) {
-            if (!$term->isPublished() && !$this->currentUser->hasPermission('administer taxonomy')) {
-              continue;
+        $terms = [];
+        foreach ($vocabularies as $vocabulary) {
+          $terms = array_merge($terms, array_filter(
+            $this->termStorage->loadTree($vocabulary->id(), 0, NULL, TRUE), function(TermInterface $term): bool {
+              return $term->access('view label');
             }
-            $choice = new \stdClass();
-            $choice->option = [$term->id() => str_repeat('-', $term->depth) . \Drupal::service('entity.repository')->getTranslationFromContext($term)->label()];
-            $options[] = $choice;
-          }
+          ));
         }
       }
       else {
         $options = [];
-        $query = \Drupal::entityQuery('taxonomy_term')
+        $query = $this->termStorage->getQuery()
           ->accessCheck(TRUE)
           // @todo Sorting on vocabulary properties -
           //   https://www.drupal.org/node/1821274.
@@ -232,15 +233,17 @@ protected function valueForm(&$form, FormStateInterface $form_state) {
           $query->condition('status', 1);
         }
         if ($this->options['limit']) {
-          $query->condition('vid', $vocabulary->id());
-        }
-        $terms = Term::loadMultiple($query->execute());
-        foreach ($terms as $term) {
-          $options[$term->id()] = \Drupal::service('entity.repository')->getTranslationFromContext($term)->label();
+          $query->condition('vid', $this->options['vids'], 'IN');
         }
+        $terms = $this->termStorage->loadMultiple($query->execute());
+      }
+
+      /** @var \Drupal\taxonomy\TermInterface[] $terms */
+      foreach ($terms as $term) {
+        $this->addOption($options, $term, $vocabularies);
       }

-      $default_value = (array) $this->value;
+      $default_value = $this->value;

       if ($exposed = $form_state->get('exposed')) {
         $identifier = $this->options['expose']['identifier'];
@@ -272,12 +275,11 @@ protected function valueForm(&$form, FormStateInterface $form_state) {
           }
         }
       }
-      $form['value'] = [
+      $form['value'] += [
         '#type' => 'select',
-        '#title' => $this->options['limit'] ? $this->t('Select terms from vocabulary @voc', ['@voc' => $vocabulary->label()]) : $this->t('Select terms'),
         '#multiple' => TRUE,
         '#options' => $options,
-        '#size' => min(9, count($options)),
+        '#size' => min(9, count($options, COUNT_RECURSIVE)),
         '#default_value' => $default_value,
       ];

@@ -423,7 +425,7 @@ public function adminSummary() {

     if ($this->value) {
       $this->value = array_filter($this->value);
-      $terms = Term::loadMultiple($this->value);
+      $terms = $this->termStorage->loadMultiple($this->value);
       foreach ($terms as $term) {
         $this->valueOptions[$term->id()] = \Drupal::service('entity.repository')->getTranslationFromContext($term)->label();
       }
@@ -437,9 +439,10 @@ public function adminSummary() {
   public function calculateDependencies() {
     $dependencies = parent::calculateDependencies();

-    $vocabulary = $this->vocabularyStorage->load($this->options['vid']);
-    $dependencies[$vocabulary->getConfigDependencyKey()][] = $vocabulary->getConfigDependencyName();
-
+    $vocabularies = $this->vocabularyStorage->loadMultiple($this->options['vids']);
+    foreach ($vocabularies as $vocabulary) {
+      $dependencies[$vocabulary->getConfigDependencyKey()][] = $vocabulary->getConfigDependencyName();
+    }
     foreach ($this->termStorage->loadMultiple($this->options['value']) as $term) {
       $dependencies[$term->getConfigDependencyKey()][] = $term->getConfigDependencyName();
     }
@@ -447,4 +450,45 @@ public function calculateDependencies() {
     return $dependencies;
   }

+  /**
+   * Returns a list of vocabulary labels keyed by vocabulary ID.
+   *
+   * @param array $vocabularies
+   *   An associative array of vocabulary entities, keyed by vocabulary ID.
+   *
+   * @return array
+   *   Associative array of vocabulary labels keyed by vocabulary ID.
+   */
+  protected function getVocabularyLabels(array $vocabularies): array {
+    return array_map(function (VocabularyInterface $vocabulary): string {
+      return $vocabulary->label();
+    }, $vocabularies);
+  }
+
+  /**
+   * Adds an option to the filter settings select.
+   *
+   * @param array $options
+   *   The list of select options passed by reference.
+   * @param \Drupal\taxonomy\TermInterface $term
+   *   The term to be added as option.
+   * @param array $vocabularies
+   *   The list of vocabularies.
+   */
+  protected function addOption(array &$options, TermInterface $term, array $vocabularies): void {
+    $option = \Drupal::service('entity.repository')->getTranslationFromContext($term)->label();
+    if (!empty($this->options['hierarchy']) && $this->options['limit']) {
+      $option = str_repeat('-', $term->depth) . $option;
+    }
+
+    /** @var \Drupal\taxonomy\VocabularyInterface[] $vocabularies */
+    if (count($vocabularies) > 1) {
+      $vocabulary_label = $vocabularies[$term->get('vid')->target_id]->label();
+      $options[$vocabulary_label][$term->id()] = $option;
+    }
+    else {
+      $options[$term->id()] = $option;
+    }
+  }
+
 }
diff --git a/core/modules/taxonomy/taxonomy.post_update.php b/core/modules/taxonomy/taxonomy.post_update.php
index 01afe432bb4..8e5c6fb5186 100644
--- a/core/modules/taxonomy/taxonomy.post_update.php
+++ b/core/modules/taxonomy/taxonomy.post_update.php
@@ -5,6 +5,10 @@
  * Post update functions for Taxonomy.
  */

+use Drupal\Core\Config\Entity\ConfigEntityUpdater;
+use Drupal\views\ViewEntityInterface;
+use Drupal\views\ViewsConfigUpdater;
+
 /**
  * Implements hook_removed_post_updates().
  */
@@ -21,3 +25,19 @@ function taxonomy_removed_post_updates(): array {
     'taxonomy_post_update_set_vocabulary_description_to_null' => '11.0.0',
   ];
 }
+
+/**
+ * Allow multiple vocabularies for views using the taxonomy term ID filter.
+ */
+function taxonomy_post_update_multiple_vocabularies_filter(?array &$sandbox = NULL): void {
+  // If Views is not installed, there is nothing to do.
+  if (!\Drupal::moduleHandler()->moduleExists('views')) {
+    return;
+  }
+  /** @var \Drupal\views\ViewsConfigUpdater $view_config_updater */
+  $view_config_updater = \Drupal::classResolver(ViewsConfigUpdater::class);
+  $view_config_updater->setDeprecationsEnabled(FALSE);
+  \Drupal::classResolver(ConfigEntityUpdater::class)->update($sandbox, 'view', function (ViewEntityInterface $view) use ($view_config_updater): bool {
+    return $view_config_updater->needsTidFilterWithMultipleVocabulariesUpdate($view);
+  });
+}
diff --git a/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_filter_taxonomy_index_tid.yml b/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_filter_taxonomy_index_tid.yml
index be15a8f3e75..a86492817a4 100644
--- a/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_filter_taxonomy_index_tid.yml
+++ b/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_filter_taxonomy_index_tid.yml
@@ -165,7 +165,8 @@ display:
           reduce_duplicates: false
           type: select
           limit: true
-          vid: tags
+          vids:
+            - tags
           hierarchy: true
           error_message: true
           plugin_id: taxonomy_index_tid
diff --git a/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_filter_taxonomy_index_tid__non_existing_dependency.yml b/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_filter_taxonomy_index_tid__non_existing_dependency.yml
index ea9f640813e..ad911872175 100644
--- a/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_filter_taxonomy_index_tid__non_existing_dependency.yml
+++ b/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_filter_taxonomy_index_tid__non_existing_dependency.yml
@@ -166,7 +166,8 @@ display:
           reduce_duplicates: false
           type: select
           limit: true
-          vid: tags
+          vids:
+            - tags
           hierarchy: true
           error_message: true
           plugin_id: taxonomy_index_tid
diff --git a/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_filter_taxonomy_index_tid_depth.yml b/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_filter_taxonomy_index_tid_depth.yml
index 9b4c7847e00..6a744feffe5 100644
--- a/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_filter_taxonomy_index_tid_depth.yml
+++ b/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_filter_taxonomy_index_tid_depth.yml
@@ -164,7 +164,8 @@ display:
           reduce_duplicates: false
           type: select
           limit: true
-          vid: views_testing_tags
+          vid:
+            - views_testing_tags
           hierarchy: true
           depth: -2
           error_message: true
diff --git a/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_taxonomy_exposed_grouped_filter.yml b/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_taxonomy_exposed_grouped_filter.yml
index cf92bcce3e5..03b2710a335 100644
--- a/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_taxonomy_exposed_grouped_filter.yml
+++ b/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_taxonomy_exposed_grouped_filter.yml
@@ -212,7 +212,8 @@ display:
           reduce_duplicates: false
           type: select
           limit: true
-          vid: tags
+          vid:
+            - tags
           hierarchy: false
           error_message: true
           plugin_id: taxonomy_index_tid
diff --git a/core/modules/taxonomy/tests/src/Functional/Update/TaxonomyTermIdFilterUpdateTest.php b/core/modules/taxonomy/tests/src/Functional/Update/TaxonomyTermIdFilterUpdateTest.php
new file mode 100644
index 00000000000..eb5033b1549
--- /dev/null
+++ b/core/modules/taxonomy/tests/src/Functional/Update/TaxonomyTermIdFilterUpdateTest.php
@@ -0,0 +1,71 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\taxonomy\Functional\Update;
+
+use Drupal\Core\Serialization\Yaml;
+use Drupal\FunctionalTests\Update\UpdatePathTestBase;
+
+/**
+ * Tests update of term ID filter handlers to allow multiple vocabularies.
+ *
+ * @group taxonomy
+ * @group legacy
+ *
+ * @see taxonomy_post_update_multiple_vocabularies_filter()
+ */
+class TaxonomyTermIdFilterUpdateTest extends UpdatePathTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['taxonomy', 'views'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getConfigSchemaExclusions() {
+    return ['views.view.test_filter_taxonomy_index_tid'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setDatabaseDumpFiles(): void {
+    $this->databaseDumpFiles = [
+      dirname(__DIR__, 5) . '/system/tests/fixtures/update/drupal-10.3.0.bare.standard.php.gz',
+    ];
+  }
+
+  /**
+   * Tests that filter handlers are updated properly.
+   */
+  public function testPostUpdateTaxonomyIndexFilterMultipleVocabularies(): void {
+    $config_factory = \Drupal::configFactory();
+    // Prepare the test view with the old schema.
+    $view_as_array = Yaml::decode(file_get_contents(dirname(__DIR__, 3) . '/modules/taxonomy_test_views/test_views/views.view.test_filter_taxonomy_index_tid.yml'));
+    $view_as_array['uuid'] = \Drupal::service('uuid')->generate();
+    unset($view_as_array['display']['default']['display_options']['filters']['tid']['vids']);
+    $view_as_array['display']['default']['display_options']['filters']['tid']['vid'] = 'tags';
+    $config_factory->getEditable('views.view.test_filter_taxonomy_index_tid')
+      ->setData($view_as_array)
+      ->save();
+
+    $path = 'display.default.display_options.filters.tid';
+    $view_as_config = $config_factory->get('views.view.test_filter_taxonomy_index_tid');
+
+    // Check that, before running updates, only the legacy 'vid' key exists.
+    $this->assertSame('tags', $view_as_config->get("{$path}.vid"));
+    $this->assertArrayNotHasKey('vids', $view_as_config->get($path));
+
+    $this->runUpdates();
+
+    $view_as_config = $config_factory->get('views.view.test_filter_taxonomy_index_tid');
+
+    // Check that, after running updates, only the new 'vids' key exists.
+    $this->assertSame(['tags'], $view_as_config->get("{$path}.vids"));
+    $this->assertArrayNotHasKey('vid', $view_as_config->get($path));
+  }
+
+}
diff --git a/core/modules/taxonomy/tests/src/Functional/Views/TaxonomyIndexTidUiTest.php b/core/modules/taxonomy/tests/src/Functional/Views/TaxonomyIndexTidUiTest.php
index 0b74e5d7aa8..f291520a21c 100644
--- a/core/modules/taxonomy/tests/src/Functional/Views/TaxonomyIndexTidUiTest.php
+++ b/core/modules/taxonomy/tests/src/Functional/Views/TaxonomyIndexTidUiTest.php
@@ -69,53 +69,102 @@ protected function setUp($import_test_views = TRUE, $modules = []): void {
       'administer views',
     ]);
     $this->drupalLogin($this->adminUser);
+    $this->terms = $this->createVocabularyAndTerms('tags');

     Vocabulary::create([
-      'vid' => 'tags',
-      'name' => 'Tags',
+      'vid' => 'other_tags',
+      'name' => 'Other tags',
     ])->save();

-    // Setup a hierarchy which looks like this:
-    // term 0.0
-    // term 1.0
-    // - term 1.1
-    // term 2.0
-    // - term 2.1
-    // - term 2.2
+    Vocabulary::create([
+      'vid' => 'empty_vocabulary',
+      'name' => 'Empty Vocabulary',
+    ])->save();
+
+    ViewTestData::createTestViews(get_class($this), ['taxonomy_test_views']);
+  }
+
+  /**
+   * Creates a vocabulary and terms for it, ensuring unique term names within the vocabulary.
+   *
+   * @param string $vocab_id
+   *   The vocabulary ID.
+   *
+   * @return \Drupal\taxonomy\TermInterface[][]
+   *   The terms.
+   *
+   * @throws \Drupal\Core\Entity\EntityStorageException
+   */
+  protected function createVocabularyAndTerms(string $vocab_id): array {
+    // Load the vocabulary or create it if it doesn't exist.
+    $vocab = Vocabulary::load($vocab_id);
+    if (!$vocab) {
+      $vocab = Vocabulary::create([
+        'vid' => $vocab_id,
+        'name' => 'Test Vocabulary ' . $vocab_id,
+        ]);
+      $vocab->save();
+    }
+
+    // Initialize a local array to store terms.
+    $terms = [];
+
+    // Setup a hierarchy with unique term names.
     for ($i = 0; $i < 3; $i++) {
       for ($j = 0; $j <= $i; $j++) {
-        $this->terms[$i][$j] = $term = Term::create([
-          'vid' => 'tags',
-          'name' => "Term $i.$j",
-          'parent' => isset($this->terms[$i][0]) ? $this->terms[$i][0]->id() : 0,
-        ]);
-        $term->save();
+        $term_name = "Term $i.$j $vocab_id";
+
+        // Check if the term already exists in the vocabulary.
+        $existing_term = \Drupal::entityTypeManager()
+          ->getStorage('taxonomy_term')
+          ->loadByProperties(['name' => $term_name, 'vid' => $vocab_id]);
+
+        // If the term does not exist, create it.
+        if (empty($existing_term)) {
+          $parent_term_id = isset($terms[$i][0]) ? $terms[$i][0]->id() : 0;
+          $terms[$i][$j] = Term::create([
+            'vid' => $vocab_id,
+            'name' => $term_name,
+            'parent' => $parent_term_id,
+          ]);
+          $terms[$i][$j]->save();
+        }
+        else {
+          // If the term exists, load the first match.
+          $terms[$i][$j] = reset($existing_term);
+        }
       }
     }
-    ViewTestData::createTestViews(static::class, ['taxonomy_test_views']);

-    // Extra taxonomy and terms.
-    Vocabulary::create([
-      'vid' => 'other_tags',
-      'name' => 'Other tags',
-    ])->save();
+    // Create a standalone term for Term 3.0
+    $term_name = "Term 3.0";

-    $this->terms[3][0] = $term = Term::create([
-      'vid' => 'tags',
-      'name' => "Term 3.0",
-    ]);
-    $term->save();
+    // Check if the term already exists.
+    $existing_term = \Drupal::entityTypeManager()
+      ->getStorage('taxonomy_term')
+      ->loadByProperties(['name' => $term_name, 'vid' => $vocab_id]);

-    Vocabulary::create([
-      'vid' => 'empty_vocabulary',
-      'name' => 'Empty Vocabulary',
-    ])->save();
+    if (empty($existing_term)) {
+      $terms[3][0] = Term::create([
+        'vid' => $vocab_id,
+        'name' => $term_name,
+      ]);
+      $terms[3][0]->save();
+    }
+    else {
+      $terms[3][0] = reset($existing_term);
+    }
+
+    return $terms;
   }

   /**
    * Tests the filter UI.
+   *
+   * @group legacy
    */
   public function testFilterUI(): void {
+    $this->expectDeprecation("The 'vid' key in 'views.filter.taxonomy_index_tid' config schema is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Update your view to use the 'vids' key instead. See https://www.drupal.org/node/3162414");
     $this->drupalGet('admin/structure/views/nojs/handler/test_filter_taxonomy_index_tid/default/filter/tid');

     $result = $this->assertSession()->selectExists('edit-options-value')->findAll('css', 'option');
@@ -159,10 +208,97 @@ public function testFilterUI(): void {
     $this->assertSame($expected, $view->calculateDependencies()->getDependencies());
   }

+  /**
+   * Tests the filter UI with multiple vocabularies.
+   *
+   * @group legacy
+   */
+  public function testFilterUIWithMultipleVocabularies(): void {
+    $this->expectDeprecation("The 'vid' key in 'views.filter.taxonomy_index_tid' config schema is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Update your view to use the 'vids' key instead. See https://www.drupal.org/node/3162414");
+    $terms2 = $this->createVocabularyAndTerms('tags2');
+    $node_type = $this->drupalCreateContentType(['type' => 'page']);
+    // Create the tag field itself.
+    $field_name = 'taxonomy_tags';
+    $this->createEntityReferenceField('node', $node_type->id(), $field_name, NULL, 'taxonomy_term');
+    // Create three nodes: 1 with a term from the first vocabulary,
+    // and 1 with a term from the second vocabulary, and 1 with terms from both.
+    $node0 = $this->drupalCreateNode([
+      'type' => 'page',
+      'taxonomy_tags' => $this->terms[0][0]->id(),
+    ]);
+    $node0->save();
+    $node1 = $this->drupalCreateNode([
+      'type' => 'page',
+      'taxonomy_tags' => $terms2[0][0]->id(),
+    ]);
+    $node1->save();
+    $node2 = $this->drupalCreateNode([
+      'type' => 'page',
+      'taxonomy_tags' => [$this->terms[0][0]->id(), $terms2[0][0]->id()],
+    ]);
+    $node2->save();
+    // Edit the view to use the second vocabulary.
+    $edit = [
+     'options[vids][tags]' => TRUE,
+     'options[vids][tags2]' => TRUE,
+     'options[type]' => 'textfield',
+    ];
+    $this->drupalGet('admin/structure/views/nojs/handler-extra/test_filter_taxonomy_index_tid/default/filter/tid');
+    $this->submitForm($edit, 'Apply');
+    // Expose the filter.
+    $this->drupalGet('admin/structure/views/nojs/handler/test_filter_taxonomy_index_tid/default/filter/tid');
+    $this->submitForm([], 'Expose filter');
+
+    $edit = [
+      'options[operator]' => 'and',
+      'options[value]' => '',
+      'options[reduce_duplicates]' => TRUE,
+    ];
+    $this->submitForm($edit, 'Apply');
+    $this->submitForm([], 'Save');
+    // Check that the terms from both vocabularies are available in the UI.
+    $this->drupalGet('test-filter-taxonomy-index-tid', ['query' => ['tid' => '']]);
+    $xpath = $this->xpath('//div[@class="views-row"]//a');
+    $this->assertCount(3, $xpath);
+    // The nodes tagged with the term from the first vocabulary should be shown.
+    $this->drupalGet('test-filter-taxonomy-index-tid', ['query' => ['tid' => "{$this->terms[0][0]->getName()}"]]);
+    $xpath = $this->xpath('//div[@class="views-row"]//a');
+    $this->assertCount(2, $xpath);
+    $xpath = $this->xpath('//div[@class="views-row"]//a[@href=:href]', [
+      ':href' => $node0->toUrl()->toString(),
+    ]);
+    $this->assertCount(1, $xpath);
+    $xpath = $this->xpath('//div[@class="views-row"]//a[@href=:href]', [
+      ':href' => $node2->toUrl()->toString(),
+    ]);
+    $this->assertCount(1, $xpath);
+    $this->drupalGet('test-filter-taxonomy-index-tid', ['query' => ['tid' => "{$terms2[0][0]->getName()}"]]);
+    $xpath = $this->xpath('//div[@class="views-row"]//a');
+    $this->assertCount(2, $xpath);
+    $xpath = $this->xpath('//div[@class="views-row"]//a[@href=:href]', [
+      ':href' => $node1->toUrl()->toString(),
+    ]);
+    $this->assertCount(1, $xpath);
+    $xpath = $this->xpath('//div[@class="views-row"]//a[@href=:href]', [
+      ':href' => $node2->toUrl()->toString(),
+    ]);
+    $this->assertCount(1, $xpath);
+    $this->drupalGet('test-filter-taxonomy-index-tid', ['query' => ['tid' => "{$this->terms[0][0]->getName()}, {$terms2[0][0]->getName()}"]]);
+    $xpath = $this->xpath('//div[@class="views-row"]//a');
+    $this->assertCount(1, $xpath);
+    $xpath = $this->xpath('//div[@class="views-row"]//a[@href=:href]', [
+      ':href' => $node2->toUrl()->toString(),
+    ]);
+    $this->assertCount(1, $xpath);
+  }
+
   /**
    * Tests exposed taxonomy filters.
+   *
+   * @group legacy
    */
   public function testExposedFilter(): void {
+    $this->expectDeprecation("The 'vid' key in 'views.filter.taxonomy_index_tid' config schema is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Update your view to use the 'vids' key instead. See https://www.drupal.org/node/3162414");
     $node_type = $this->drupalCreateContentType(['type' => 'page']);

     // Create the tag field itself.
@@ -264,8 +400,12 @@ public function testExposedFilter(): void {
     $this->drupalGet('admin/structure/views/nojs/add-handler/test_taxonomy_term_name/default/filter');
     $this->submitForm($edit, 'Add and configure filter criteria');
     // Select 'Empty Vocabulary' and 'Autocomplete' from the list of options.
+    $edit = [
+      'options[vids][empty_vocabulary]' => TRUE,
+      'options[type]' => 'textfield',
+    ];
     $this->drupalGet('admin/structure/views/nojs/handler-extra/test_taxonomy_term_name/default/filter/tid');
-    $this->submitForm([], 'Apply and continue');
+    $this->submitForm($edit, 'Apply and continue');
     // Expose the filter.
     $edit = ['options[expose_button][checkbox][checkbox]' => TRUE];
     $this->drupalGet('admin/structure/views/nojs/handler/test_taxonomy_term_name/default/filter/tid');
@@ -292,8 +432,11 @@ public function testExposedFilter(): void {

   /**
    * Tests exposed grouped taxonomy filters.
+   *
+   * @group legacy
    */
   public function testExposedGroupedFilter(): void {
+    $this->expectDeprecation("The 'vid' key in 'views.filter.taxonomy_index_tid' config schema is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Update your view to use the 'vids' key instead. See https://www.drupal.org/node/3162414");
     // Create a content type with a taxonomy field.
     $this->drupalCreateContentType(['type' => 'article']);
     $field_name = 'field_views_testing_tags';
@@ -307,7 +450,7 @@ public function testExposedGroupedFilter(): void {
       $nodes[] = $this->drupalCreateNode($node);
     }

-    $this->drupalGet('/admin/structure/views/nojs/handler/test_taxonomy_exposed_grouped_filter/page_1/filter/field_views_testing_tags_target_id');
+    $this->drupalGet('admin/structure/views/nojs/handler/test_taxonomy_exposed_grouped_filter/page_1/filter/field_views_testing_tags_target_id');
     $edit = [
       'options[group_info][group_items][1][value][]' => [$this->terms[0][0]->id(), $this->terms[1][0]->id()],
       'options[group_info][group_items][2][value][]' => [$this->terms[1][0]->id(), $this->terms[2][0]->id()],
@@ -317,7 +460,7 @@ public function testExposedGroupedFilter(): void {
     $this->submitForm([], 'Save');

     // Visit the view's page URL and validate the results.
-    $this->drupalGet('/test-taxonomy-exposed-grouped-filter');
+    $this->drupalGet('test-taxonomy-exposed-grouped-filter');
     $this->submitForm(['field_views_testing_tags_target_id' => 1], 'Apply');
     $this->assertSession()->pageTextContains($nodes[0]->getTitle());
     $this->assertSession()->pageTextContains($nodes[1]->getTitle());
@@ -336,8 +479,11 @@ public function testExposedGroupedFilter(): void {

   /**
    * Tests that an exposed taxonomy filter doesn't show unpublished terms.
+   *
+   * @group legacy
    */
   public function testExposedUnpublishedFilterOptions(): void {
+    $this->expectDeprecation("The 'vid' key in 'views.filter.taxonomy_index_tid' config schema is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Update your view to use the 'vids' key instead. See https://www.drupal.org/node/3162414");
     $this->terms[1][0]->setUnpublished()->save();
     // Expose the filter.
     $this->drupalGet('admin/structure/views/nojs/handler/test_filter_taxonomy_index_tid/default/filter/tid');
@@ -375,8 +521,11 @@ public function testExposedUnpublishedFilterOptions(): void {

   /**
    * Tests using the TaxonomyIndexTid in a filter group.
+   *
+   * @group legacy
    */
   public function testFilterGrouping(): void {
+    $this->expectDeprecation("The 'vid' key in 'views.filter.taxonomy_index_tid' config schema is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Update your view to use the 'vids' key instead. See https://www.drupal.org/node/3162414");
     $node_type = $this->drupalCreateContentType(['type' => 'page']);

     // Create the tag field itself.
diff --git a/core/modules/taxonomy/tests/src/Kernel/Views/TaxonomyIndexTidFilterTest.php b/core/modules/taxonomy/tests/src/Kernel/Views/TaxonomyIndexTidFilterTest.php
index 84e5a4af7ce..a899bdc1eaf 100644
--- a/core/modules/taxonomy/tests/src/Kernel/Views/TaxonomyIndexTidFilterTest.php
+++ b/core/modules/taxonomy/tests/src/Kernel/Views/TaxonomyIndexTidFilterTest.php
@@ -14,7 +14,7 @@
 /**
  * Test the taxonomy term index filter.
  *
- * @see \Drupal\taxonomy\Plugin\views\filter\TaxonomyIndexTid
+ * @coversDefaultClass \Drupal\taxonomy\Plugin\views\filter\TaxonomyIndexTid
  */
 #[Group('taxonomy')]
 #[RunTestsInSeparateProcesses]
@@ -62,8 +62,11 @@ protected function setUp($import_test_views = TRUE): void {

   /**
    * Tests dependencies are not added for terms that do not exist.
+   *
+   * @group legacy
    */
   public function testConfigDependency(): void {
+    $this->expectDeprecation("The 'vid' key in 'views.filter.taxonomy_index_tid' config schema is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Update your view to use the 'vids' key instead. See https://www.drupal.org/node/3162414");
     /** @var \Drupal\views\Entity\View $view */
     $view = View::load('test_filter_taxonomy_index_tid__non_existing_dependency');

@@ -103,4 +106,32 @@ public function testConfigDependency(): void {
     ], $view->calculateDependencies()->getDependencies());
   }

+  /**
+   * Tests 'taxonomy_index_tid' filter handler vocabulary dependencies.
+   *
+   * @covers ::calculateDependencies
+   * @group legacy
+   */
+  public function testMultipleVocabularies(): void {
+    $this->expectDeprecation("The 'vid' key in 'views.filter.taxonomy_index_tid' config schema is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Update your view to use the 'vids' key instead. See https://www.drupal.org/node/3162414");
+    /** @var \Drupal\views\Entity\View $view */
+    $view = View::load('test_filter_taxonomy_index_tid__non_existing_dependency');
+
+    // Add a second vocabulary to the view 'tid' filter handler.
+    Vocabulary::create([
+      'vid' => 'second_vocab',
+      'name' => 'Second vocabulary',
+    ])->save();
+    $displays = $view->get('display');
+    $displays['default']['display_options']['filters']['tid']['vids'][] = 'second_vocab';
+    $view->set('display', $displays);
+    $view->save();
+
+    // Check that the dependencies were updated.
+    $this->assertSame([
+      'taxonomy.vocabulary.second_vocab',
+      'taxonomy.vocabulary.tags',
+    ], $view->getDependencies()['config']);
+  }
+
 }
diff --git a/core/modules/taxonomy/tests/src/Kernel/Views/TaxonomyTermFilterDepthTest.php b/core/modules/taxonomy/tests/src/Kernel/Views/TaxonomyTermFilterDepthTest.php
index 7d21470c1d3..5c683470a35 100644
--- a/core/modules/taxonomy/tests/src/Kernel/Views/TaxonomyTermFilterDepthTest.php
+++ b/core/modules/taxonomy/tests/src/Kernel/Views/TaxonomyTermFilterDepthTest.php
@@ -84,8 +84,11 @@ protected function setUp($import_test_views = TRUE): void {

   /**
    * Tests the terms with depth filter.
+   *
+   * @group legacy
    */
   public function testTermWithDepthFilter(): void {
+    $this->expectDeprecation("The 'vid' key in 'views.filter.taxonomy_index_tid' config schema is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Update your view to use the 'vids' key instead. See https://www.drupal.org/node/3162414");
     // Default view has an empty value for this filter, so all nodes should be
     // returned.
     $expected = [
diff --git a/core/modules/views/src/ViewsConfigUpdater.php b/core/modules/views/src/ViewsConfigUpdater.php
index 2bfa7deb2e2..ee59a0e8397 100644
--- a/core/modules/views/src/ViewsConfigUpdater.php
+++ b/core/modules/views/src/ViewsConfigUpdater.php
@@ -472,4 +472,40 @@ public function processRssViewModeUpdate(ViewEntityInterface $view, ?string $pre
     return $changed;
   }

+  /**
+   * Update taxonomy term ID filter handlers to allow multiple vocabularies.
+   *
+   * @param \Drupal\views\ViewEntityInterface $view
+   *   The View to update.
+   *
+   * @return bool
+   *   Whether the view was updated.
+   */
+  public function needsTidFilterWithMultipleVocabulariesUpdate(ViewEntityInterface $view): bool {
+    return $this->processDisplayHandlers($view, FALSE, function (array &$handler, string $handler_type): bool {
+      return $this->processTidFilterWithMultipleVocabulariesHandler($handler, $handler_type);
+    });
+  }
+
+  /**
+   * Processes taxonomy term ID filter handlers to allow multiple vocabularies.
+   *
+   * @param array $handler
+   *   A display handler.
+   * @param string $handler_type
+   *   The handler type.
+   *
+   * @return bool
+   *   Whether the handler was updated.
+   */
+  protected function processTidFilterWithMultipleVocabulariesHandler(array &$handler, string $handler_type): bool {
+    if ($handler_type === 'filter' && isset($handler['plugin_id']) && $handler['plugin_id'] === 'taxonomy_index_tid' && empty($handler['vids']) && !empty($handler['vid'])) {
+      $handler['plugin_id'] = 'taxonomy_index_tid_vids';
+      $handler['vids'] = [$handler['vid']];
+      unset($handler['vid']);
+      return TRUE;
+    }
+    return FALSE;
+  }
+
 }
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_form_checkboxes.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_form_checkboxes.yml
index f3caf6512a6..0a08cb09da4 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_form_checkboxes.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_form_checkboxes.yml
@@ -103,7 +103,8 @@ display:
           reduce_duplicates: false
           type: select
           limit: true
-          vid: test_exposed_checkboxes
+          vid:
+            - test_exposed_checkboxes
           hierarchy: false
           error_message: true
           plugin_id: taxonomy_index_tid
diff --git a/core/modules/views/tests/src/Functional/Plugin/ExposedFormCheckboxesTest.php b/core/modules/views/tests/src/Functional/Plugin/ExposedFormCheckboxesTest.php
index b30763a88e5..57d1f0d8483 100644
--- a/core/modules/views/tests/src/Functional/Plugin/ExposedFormCheckboxesTest.php
+++ b/core/modules/views/tests/src/Functional/Plugin/ExposedFormCheckboxesTest.php
@@ -85,8 +85,11 @@ protected function setUp($import_test_views = TRUE, $modules = []): void {

   /**
    * Tests overriding the default render option with checkboxes.
+   *
+   * @group legacy
    */
   public function testExposedFormRenderCheckboxes(): void {
+    $this->expectDeprecation("The 'vid' key in 'views.filter.taxonomy_index_tid' config schema is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Update your view to use the 'vids' key instead. See https://www.drupal.org/node/3162414");
     // Use a test theme to convert multi-select elements into checkboxes.
     \Drupal::service('theme_installer')->install(['views_test_checkboxes_theme']);
     $this->config('system.theme')
@@ -118,8 +121,11 @@ public function testExposedFormRenderCheckboxes(): void {

   /**
    * Tests that "is all of" filters work with checkboxes.
+   *
+   * @group legacy
    */
   public function testExposedIsAllOfFilter(): void {
+    $this->expectDeprecation("The 'vid' key in 'views.filter.taxonomy_index_tid' config schema is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Update your view to use the 'vids' key instead. See https://www.drupal.org/node/3162414");
     foreach (['Term 1', 'Term 2', 'Term 3'] as $term_name) {
       // Add a few terms to the new vocabulary.
       $term = Term::create([
diff --git a/core/modules/views/tests/src/Functional/Wizard/TaggedWithTest.php b/core/modules/views/tests/src/Functional/Wizard/TaggedWithTest.php
index 6f3ca7924de..badf63dabd7 100644
--- a/core/modules/views/tests/src/Functional/Wizard/TaggedWithTest.php
+++ b/core/modules/views/tests/src/Functional/Wizard/TaggedWithTest.php
@@ -126,8 +126,11 @@ protected function setUp($import_test_views = TRUE, $modules = []): void {

   /**
    * Tests the "tagged with" functionality.
+   *
+   * @group legacy
    */
   public function testTaggedWith(): void {
+    $this->expectDeprecation("The 'vid' key in 'views.filter.taxonomy_index_tid' config schema is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Update your view to use the 'vids' key instead. See https://www.drupal.org/node/3162414");
     // In this test we will only create nodes that have an instance of the tag
     // field.
     $node_add_path = 'node/add/' . $this->nodeTypeWithTags->id();
diff --git a/core/modules/views/tests/src/Kernel/TestViewsTest.php b/core/modules/views/tests/src/Kernel/TestViewsTest.php
index 8a16acf2422..d9e71288a5e 100644
--- a/core/modules/views/tests/src/Kernel/TestViewsTest.php
+++ b/core/modules/views/tests/src/Kernel/TestViewsTest.php
@@ -180,8 +180,11 @@ protected function setUp(): void {

   /**
    * Tests default configuration data type.
+   *
+   * @group legacy
    */
   public function testDefaultConfig(): void {
+    $this->expectDeprecation("The 'vid' key in 'views.filter.taxonomy_index_tid' config schema is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Update your view to use the 'vids' key instead. See https://www.drupal.org/node/3162414");
     // Create a typed config manager with access to configuration schema in
     // every module, profile and theme.
     $typed_config = new TypedConfigManager(
