diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/MediaOverviewTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/MediaOverviewTest.php
index a79a8ccfdbb00c16af4e44457a0c6563916c021a..11e6db86778bb81a22c7256bd1142938c5993236 100644
--- a/core/modules/media_library/tests/src/FunctionalJavascript/MediaOverviewTest.php
+++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaOverviewTest.php
@@ -107,7 +107,7 @@ public function testAdministrationPage() {
 
     // Test that selecting elements as a part of bulk operations works.
     $page->selectFieldOption('Media type', '- Any -');
-    $assert_session->elementExists('css', '#views-exposed-form-media-library-page')->submit();
+    $assert_session->elementExists('css', '.views-exposed-form-media-library-page')->submit();
     $this->waitForText('Dog');
 
     // Select the "Delete media" action.
diff --git a/core/modules/views/js/ajax_view.js b/core/modules/views/js/ajax_view.js
index 5365ade4724c79bfe4a69de852f05e9147d663d8..829ebf6e1d08bce3e3f6470eaa0c9ac599d15abf 100644
--- a/core/modules/views/js/ajax_view.js
+++ b/core/modules/views/js/ajax_view.js
@@ -101,14 +101,14 @@
 
     // Add the ajax to exposed forms.
     this.$exposed_form = $(
-      `form#views-exposed-form-${settings.view_name.replace(
+      `form[id^="views-exposed-form-${settings.view_name.replace(
         /_/g,
         '-',
-      )}-${settings.view_display_id.replace(/_/g, '-')}`,
-    );
-    once('exposed-form', this.$exposed_form).forEach(
-      this.attachExposedFormAjax.bind(this),
+      )}-${settings.view_display_id.replace(/_/g, '-')}"]`,
     );
+    if (once('exposed-form', this.$exposed_form).length) {
+      this.attachExposedFormAjax();
+    }
 
     // Add the ajax to pagers.
     once(
@@ -139,20 +139,24 @@
    */
   Drupal.views.ajaxView.prototype.attachExposedFormAjax = function () {
     const that = this;
-    this.exposedFormAjax = [];
+    const pendingForms = once('exposed-form-attach', this.$exposed_form);
+    if (!pendingForms.length) {
+      return;
+    }
+
+    this.exposedFormAjax = this.exposedFormAjax || [];
     // Exclude the reset buttons so no AJAX behaviors are bound. Many things
     // break during the form reset phase if using AJAX.
-    $(
-      'input[type=submit], button[type=submit], input[type=image]',
-      this.$exposed_form,
-    )
+    $(pendingForms)
+      .find('input[type=submit], button[type=submit], input[type=image]')
       .not('[data-drupal-selector=edit-reset]')
-      .each(function (index) {
+      .each(function () {
+        // Initialize the Drupal.ajax instance.
         const selfSettings = $.extend({}, that.element_settings, {
           base: $(this).attr('id'),
           element: this,
         });
-        that.exposedFormAjax[index] = Drupal.ajax(selfSettings);
+        that.exposedFormAjax.push(Drupal.ajax(selfSettings));
       });
   };
 
diff --git a/core/modules/views/src/Form/ViewsExposedForm.php b/core/modules/views/src/Form/ViewsExposedForm.php
index 417d97971e923f9aa3a9c3ae5b3068e9e9ae99d6..386aaad6978b725133f4f3e683d55c2dc266bd27 100644
--- a/core/modules/views/src/Form/ViewsExposedForm.php
+++ b/core/modules/views/src/Form/ViewsExposedForm.php
@@ -136,7 +136,10 @@ public function buildForm(array $form, FormStateInterface $form_state) {
 
     $form['#action'] = $form_action;
     $form['#theme'] = $view->buildThemeFunctions('views_exposed_form');
-    $form['#id'] = Html::cleanCssIdentifier('views_exposed_form-' . $view->storage->id() . '-' . $display['id']);
+    $clean_form_id = Html::cleanCssIdentifier('views_exposed_form-' . $view->storage->id() . '-' . $display['id']);
+    $form['#attributes']['class'][] = $clean_form_id;
+    // The form can be possibly rendered twice on a page, make the id unique.
+    $form['#id'] = Html::getUniqueId($clean_form_id);
     // Labels are built too late for inline form errors to work, resulting
     // in duplicated messages.
     $form['#disable_inline_form_errors'] = TRUE;
diff --git a/core/modules/views/tests/src/Functional/Plugin/ExposedFormTest.php b/core/modules/views/tests/src/Functional/Plugin/ExposedFormTest.php
index dc5f4fa9ee57f9f16eb3cee9145a0c7fbc6f0b2c..3b07162de315f686c99195216724906d6dadabf7 100644
--- a/core/modules/views/tests/src/Functional/Plugin/ExposedFormTest.php
+++ b/core/modules/views/tests/src/Functional/Plugin/ExposedFormTest.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Tests\views\Functional\Plugin;
 
+use Drupal\Component\Render\FormattableMarkup;
 use Drupal\Component\Utility\Html;
 use Drupal\entity_test\Entity\EntityTest;
 use Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait;
@@ -293,9 +294,9 @@ public function testExposedBlock($display) {
     $this->assertSession()->elementNotExists('xpath', $xpath);
 
     // Test there is only one views exposed form on the page.
-    $xpath = '//form[@id="' . $this->getExpectedExposedFormId($view) . '"]';
-    $this->assertSession()->elementsCount('xpath', $xpath, 1);
-    $element = $this->assertSession()->elementExists('xpath', $xpath);
+    $css_selector = 'form[id^="' . $this->getExpectedExposedFormId($view) . '"]';
+    $this->assertSession()->elementsCount('css', $css_selector, 1);
+    $element = $this->assertSession()->elementExists('css', $css_selector);
 
     // Test that the correct option is selected after form submission.
     $this->assertCacheContext('url');
@@ -329,6 +330,20 @@ public function providerTestExposedBlock() {
     ];
   }
 
+  /**
+   * Test placing the same form twice on the same page.
+   */
+  public function testNoDoubleIdsForSameExposedForm() {
+    $view = Views::getView('test_exposed_block');
+    $view->setDisplay('page_1');
+    $this->drupalPlaceBlock('views_exposed_filter_block:test_exposed_block-page_1');
+    $this->drupalPlaceBlock('views_exposed_filter_block:test_exposed_block-page_1');
+
+    $this->drupalGet('test_exposed_block');
+
+    $this->assertNoDuplicateIds();
+  }
+
   /**
    * Tests the input required exposed form type.
    */
@@ -543,6 +558,31 @@ protected function assertNodesExist(array $bundles): void {
     }
   }
 
+  /**
+   * Asserts that each HTML ID is used for just a single element on the page.
+   */
+  protected function assertNoDuplicateIds() {
+    $args = ['@url' => $this->getUrl()];
+
+    if (!$elements = $this->xpath('//*[@id]')) {
+      $this->fail(new FormattableMarkup('The page @url contains no HTML IDs.', $args));
+      return;
+    }
+
+    $message = new FormattableMarkup('The page @url contains duplicate HTML IDs', $args);
+
+    $seen_ids = [];
+    foreach ($elements as $element) {
+      $id = $element->getAttribute('id');
+      if (isset($seen_ids[$id])) {
+        $this->fail($message);
+        return;
+      }
+      $seen_ids[$id] = TRUE;
+    }
+    $this->assertTrue(TRUE, $message);
+  }
+
   /**
    * Tests the "Remember the last selection" functionality.
    */
diff --git a/core/modules/views/tests/src/FunctionalJavascript/BlockExposedFilterAJAXTest.php b/core/modules/views/tests/src/FunctionalJavascript/BlockExposedFilterAJAXTest.php
index fe2b4c85089763f8c053cda43e5394ecb24bd70f..ca606cd4edb13513fe9eb858c066b1cb7fc22fe7 100644
--- a/core/modules/views/tests/src/FunctionalJavascript/BlockExposedFilterAJAXTest.php
+++ b/core/modules/views/tests/src/FunctionalJavascript/BlockExposedFilterAJAXTest.php
@@ -95,4 +95,65 @@ public function testExposedFilteringAndReset() {
     $this->assertSession()->addressEquals('some-path');
   }
 
+  /**
+   * Test that AJAX works with two exposed blocks on the same page.
+   */
+  public function testExposedFilterWithDoubleExposedBlock() {
+    $node = $this->createNode();
+    $block1 = $this->drupalPlaceBlock('views_block:test_block_exposed_ajax-block_1');
+    $block2 = $this->drupalPlaceBlock('views_block:test_block_exposed_ajax-block_1');
+    $this->drupalGet($node->toUrl());
+
+    $page = $this->getSession()->getPage();
+
+    // Ensure that the Content we're testing for is present.
+    $this->assertCount(2, $page->findAll('xpath', '//*[text()="Page A"]'));
+    $this->assertCount(2, $page->findAll('xpath', '//*[text()="Page B"]'));
+    $this->assertCount(2, $page->findAll('xpath', '//*[text()="Article A"]'));
+
+    $form1 = $page->find('css', '#block-' . $block1->id() . ' form');
+    $form1_id = $form1->getAttribute('id');
+    // Filter by page type in the first form.
+    $this->submitForm(['type' => 'page'], t('Apply'), $form1_id);
+    $this->waitForCount(1, 'xpath', '//*[text()="Article A"]');
+    $this->assertCount(2, $page->findAll('xpath', '//*[text()="Page A"]'));
+    $this->assertCount(2, $page->findAll('xpath', '//*[text()="Page B"]'));
+    $this->assertCount(1, $page->findAll('xpath', '//*[text()="Article A"]'));
+
+    $form2 = $page->find('css', '#block-' . $block2->id() . ' form');
+    $form2_id = $form2->getAttribute('id');
+    // Filter by page type in the second form.
+    $this->submitForm(['type' => 'page'], t('Apply'), $form2_id);
+    $this->waitForCount(1, 'xpath', '//*[text()="Article A"]');
+    $this->assertCount(2, $page->findAll('xpath', '//*[text()="Page A"]'));
+    $this->assertCount(2, $page->findAll('xpath', '//*[text()="Page B"]'));
+    $this->assertCount(1, $page->findAll('xpath', '//*[text()="Article A"]'));
+  }
+
+  /**
+   * Looks for the selector and waits for the the count is matched.
+   *
+   * @param int $count
+   *   The count to match.
+   * @param string $selector
+   *   The selector engine name. See ElementInterface::findAll() for the
+   *   supported selectors.
+   * @param string|array $locator
+   *   The selector locator.
+   * @param int $timeout
+   *   (Optional) Timeout in milliseconds, defaults to 10000.
+   *
+   * @return bool
+   *   TRUE if count was matched, FALSE if not.
+   */
+  protected function waitForCount($count, $selector, $locator, $timeout = 10000) {
+    $page = $this->getSession()->getPage();
+
+    $result = $page->waitFor($timeout / 1000, function () use ($page, $count, $selector, $locator) {
+      return count($page->findAll($selector, $locator)) === $count;
+    });
+
+    return $result;
+  }
+
 }
diff --git a/core/modules/views/tests/src/Kernel/Plugin/ExposedFormRenderTest.php b/core/modules/views/tests/src/Kernel/Plugin/ExposedFormRenderTest.php
index bc75f8f21073ac3456edf9505280651dded06b1a..0bce88acf2796a82161290d55373826319180a6d 100644
--- a/core/modules/views/tests/src/Kernel/Plugin/ExposedFormRenderTest.php
+++ b/core/modules/views/tests/src/Kernel/Plugin/ExposedFormRenderTest.php
@@ -43,7 +43,7 @@ public function testExposedFormRender() {
     $output = $exposed_form->renderExposedForm();
     $this->setRawContent(\Drupal::service('renderer')->renderRoot($output));
 
-    $this->assertFieldByXpath('//form/@id', Html::cleanCssIdentifier('views-exposed-form-' . $view->storage->id() . '-' . $view->current_display), 'Expected form ID found.');
+    $this->assertFieldByXpath('//form/@id', Html::cleanCssIdentifier('views-exposed-form-' . $view->storage->id() . '-' . $view->current_display . '--2'), 'Expected form ID found.');
 
     $view->setDisplay('page_1');
     $expected_action = $view->display_handler->getUrlInfo()->toString();
diff --git a/core/modules/views/tests/src/Kernel/ViewElementTest.php b/core/modules/views/tests/src/Kernel/ViewElementTest.php
index a62ce859754e0f12591513fbc75363eb85179a20..333cfdd80e61d5043c2d5af95950a7c7c2bd3aff 100644
--- a/core/modules/views/tests/src/Kernel/ViewElementTest.php
+++ b/core/modules/views/tests/src/Kernel/ViewElementTest.php
@@ -126,7 +126,7 @@ public function testViewElementEmbed() {
     $this->setRawContent($renderer->renderRoot($render));
 
     // Ensure that the exposed form is rendered.
-    $this->assertCount(1, $this->xpath('//form[@class="views-exposed-form"]'));
+    $this->assertCount(1, $this->xpath('//form[@class="views-exposed-form views-exposed-form-test-view-embed-embed-2"]'));
   }
 
   /**
