diff --git a/core/modules/block/block.libraries.yml b/core/modules/block/block.libraries.yml
index 967a6827a1..9b5877f517 100644
--- a/core/modules/block/block.libraries.yml
+++ b/core/modules/block/block.libraries.yml
@@ -16,4 +16,6 @@ drupal.block.admin:
   dependencies:
     - core/jquery
     - core/drupal
+    - core/drupal.announce
+    - core/drupal.debounce
     - core/drupal.dialog.ajax
diff --git a/core/modules/block/js/block.admin.es6.js b/core/modules/block/js/block.admin.es6.js
index 9f99b7faba..4f8477d554 100644
--- a/core/modules/block/js/block.admin.es6.js
+++ b/core/modules/block/js/block.admin.es6.js
@@ -3,7 +3,7 @@
  * Block admin behaviors.
  */
 
-(function ($, Drupal) {
+(function ($, Drupal, debounce) {
 
   'use strict';
 
@@ -56,6 +56,13 @@
         // Filter if the length of the query is at least 2 characters.
         if (query.length >= 2) {
           $filter_rows.each(toggleBlockEntry);
+          Drupal.announce(
+            Drupal.formatPlural(
+              $table.find('tr:visible').length - 1,
+              '1 block is available in the modified list.',
+              '@count blocks are available in the modified list.'
+            )
+          );
         }
         else {
           $filter_rows.each(function (index) {
@@ -66,7 +73,7 @@
 
       if ($table.length) {
         $filter_rows = $table.find('div.block-filter-text-source');
-        $input.on('keyup', filterBlockList);
+        $input.on('keyup', debounce(filterBlockList, 200));
       }
     }
   };
@@ -94,4 +101,4 @@
     }
   };
 
-}(jQuery, Drupal));
+}(jQuery, Drupal, Drupal.debounce));
diff --git a/core/modules/block/js/block.admin.js b/core/modules/block/js/block.admin.js
index 8b13b9cca3..348782b1d7 100644
--- a/core/modules/block/js/block.admin.js
+++ b/core/modules/block/js/block.admin.js
@@ -5,7 +5,7 @@
 * @preserve
 **/
 
-(function ($, Drupal) {
+(function ($, Drupal, debounce) {
 
   'use strict';
 
@@ -27,6 +27,7 @@
 
         if (query.length >= 2) {
           $filter_rows.each(toggleBlockEntry);
+          Drupal.announce(Drupal.formatPlural($table.find('tr:visible').length - 1, '1 block is available in the modified list.', '@count blocks are available in the modified list.'));
         } else {
           $filter_rows.each(function (index) {
             $(this).parent().parent().show();
@@ -36,7 +37,7 @@
 
       if ($table.length) {
         $filter_rows = $table.find('div.block-filter-text-source');
-        $input.on('keyup', filterBlockList);
+        $input.on('keyup', debounce(filterBlockList, 200));
       }
     }
   };
@@ -54,4 +55,4 @@
       }
     }
   };
-})(jQuery, Drupal);
\ No newline at end of file
+})(jQuery, Drupal, Drupal.debounce);
\ No newline at end of file
diff --git a/core/modules/block/tests/src/FunctionalJavascript/BlockFilterTest.php b/core/modules/block/tests/src/FunctionalJavascript/BlockFilterTest.php
new file mode 100644
index 0000000000..0b34f84102
--- /dev/null
+++ b/core/modules/block/tests/src/FunctionalJavascript/BlockFilterTest.php
@@ -0,0 +1,93 @@
+<?php
+
+namespace Drupal\Tests\block\FunctionalJavascript;
+
+use Behat\Mink\Element\NodeElement;
+use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+
+/**
+ * Tests the JavaScript functionality of the block add filter.
+ *
+ * @group block
+ */
+class BlockFilterTest extends JavascriptTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['user', 'block'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $admin_user = $this->drupalCreateUser([
+      'administer blocks',
+    ]);
+
+    $this->drupalLogin($admin_user);
+  }
+
+  /**
+   * Tests block filter.
+   */
+  public function testBlockFilter() {
+    $this->drupalGet('admin/structure/block');
+    $assertSession = $this->assertSession();
+    $session = $this->getSession();
+    $page = $session->getPage();
+
+    // Find the block filter field on the add-block dialog.
+    $page->find('css', '#edit-blocks-region-header-title')->click();
+    $filter = $assertSession->waitForElement('css', '.block-filter-text');
+
+    // Get all block rows, for assertions later.
+    $block_rows = $page->findAll('css', '.block-add-table tbody tr');
+
+    // Test block filter reduces the number of visible rows.
+    $filter->setValue('ad');
+    $session->wait(10000, 'jQuery("#drupal-live-announce").html().indexOf("blocks are available") > -1');
+    $visible_rows = $this->filterVisibleElements($block_rows);
+    if (count($block_rows) > 0) {
+      $this->assertNotEquals(count($block_rows), count($visible_rows));
+    }
+
+    // Test Drupal.announce() message when multiple matches are expected.
+    $expected_message = count($visible_rows) . ' blocks are available in the modified list.';
+    $assertSession->elementTextContains('css', '#drupal-live-announce', $expected_message);
+
+    // Test Drupal.announce() message when only one match is expected.
+    $filter->setValue('Powered by');
+    $session->wait(10000, 'jQuery("#drupal-live-announce").html().indexOf("block is available") > -1');
+    $visible_rows = $this->filterVisibleElements($block_rows);
+    $this->assertEquals(1, count($visible_rows));
+    $expected_message = '1 block is available in the modified list.';
+    $assertSession->elementTextContains('css', '#drupal-live-announce', $expected_message);
+
+    // Test Drupal.announce() message when no matches are expected.
+    $filter->setValue('Pan-Galactic Gargle Blaster');
+    $session->wait(10000, 'jQuery("#drupal-live-announce").html().indexOf("0 blocks are available") > -1');
+    $visible_rows = $this->filterVisibleElements($block_rows);
+    $this->assertEquals(0, count($visible_rows));
+    $expected_message = '0 blocks are available in the modified list.';
+    $assertSession->elementTextContains('css', '#drupal-live-announce', $expected_message);
+  }
+
+  /**
+   * Removes any non-visible elements from the passed array.
+   *
+   * @param NodeElement[] $elements
+   *   An array of node elements.
+   *
+   * @return NodeElement[]
+   */
+  protected function filterVisibleElements(array $elements) {
+    $elements = array_filter($elements, function(NodeElement $element) {
+      return $element->isVisible();
+    });
+    return $elements;
+  }
+
+}
