diff --git a/css/pb.css b/css/pb.css
index a4aafc8112a84471a0294b3e1e8006a3bb63c352..fbc50419b069cb473e1fa48b08aa6715be1af556 100644
--- a/css/pb.css
+++ b/css/pb.css
@@ -627,6 +627,13 @@
   outline: none;
 }
 
+.search__search_term::-webkit-search-cancel-button,
+.search__search_term::-webkit-search-decoration,
+.search__search_term::-webkit-search-results-button,
+.search__search_term::-webkit-search-results-decoration {
+    display: none;
+}
+
 .search__search-bar {
   position: relative;
   height: 50px;
@@ -644,6 +651,16 @@
   inset-inline-end: 30px;
 }
 
+.search__search-clear {
+  position: absolute;
+  top: 0;
+  height: 100%;
+  cursor: pointer;
+  border: none;
+  background: none;
+  inset-inline-end: 60px;
+}
+
 .search__search_term::placeholder {
   display: flex;
   align-items: center;
diff --git a/images/cross--dark-color-scheme.svg b/images/cross--dark-color-scheme.svg
new file mode 100644
index 0000000000000000000000000000000000000000..6d0119f6389a9878f80452fc7816ec83c2d12952
--- /dev/null
+++ b/images/cross--dark-color-scheme.svg
@@ -0,0 +1,3 @@
+<svg width="16px" height="16px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="#FFF" >
+  <path d="M1.293 1.293a1 1 0 0 1 1.414 0L8 6.586l5.293-5.293a1 1 0 1 1 1.414 1.414L9.414 8l5.293 5.293a1 1 0 0 1-1.414 1.414L8 9.414l-5.293 5.293a1 1 0 0 1-1.414-1.414L6.586 8 1.293 2.707a1 1 0 0 1 0-1.414z"/>
+</svg>
diff --git a/images/cross.svg b/images/cross.svg
new file mode 100644
index 0000000000000000000000000000000000000000..11daaf2bc3653fb93ebf61a78b67847697a91320
--- /dev/null
+++ b/images/cross.svg
@@ -0,0 +1,3 @@
+<svg width="16px" height="16px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="#75767B" >
+  <path d="M1.293 1.293a1 1 0 0 1 1.414 0L8 6.586l5.293-5.293a1 1 0 1 1 1.414 1.414L9.414 8l5.293 5.293a1 1 0 0 1-1.414 1.414L8 9.414l-5.293 5.293a1 1 0 0 1-1.414-1.414L6.586 8 1.293 2.707a1 1 0 0 1 0-1.414z"/>
+</svg>
diff --git a/sveltejs/public/build/bundle.js b/sveltejs/public/build/bundle.js
index c7a30c9f188e28939a4dd6825e5095a90f3c00ee..7f06822dadab7e05705d0149813941bcb051c961 100644
Binary files a/sveltejs/public/build/bundle.js and b/sveltejs/public/build/bundle.js differ
diff --git a/sveltejs/public/build/bundle.js.map b/sveltejs/public/build/bundle.js.map
index ca335be89d7be0d9aa159df912a03b3f3c441430..34fd1b2813a25d81de76e4634704b7315f539208 100644
Binary files a/sveltejs/public/build/bundle.js.map and b/sveltejs/public/build/bundle.js.map differ
diff --git a/sveltejs/src/Search/Search.svelte b/sveltejs/src/Search/Search.svelte
index 97d8fd9e1c61f222c9a53f9ce86bb5be7d8b6027..75c93cb29611c19acf7b1d272f718c399db95e89 100644
--- a/sveltejs/src/Search/Search.svelte
+++ b/sveltejs/src/Search/Search.svelte
@@ -136,6 +136,12 @@
     onAdvancedFilter();
   }
 
+  function clearText() {
+    $searchString = '';
+    onSearch();
+    document.getElementById('pb-text').focus();
+  }
+
   /**
    * Actions performed when clicking filter resets such as "recommended"
    * @param {string} maintenanceId
@@ -174,7 +180,29 @@
         name="text"
         bind:value={$searchString}
         on:keyup={Drupal.debounce(onSearch, 250, false)}
+        on:keydown={(e) => {
+          if (e.key === 'Escape') {
+            e.preventDefault();
+            clearText();
+          }
+        }}
       />
+      {#if $searchString}
+        <button
+          class="search__search-clear"
+          id="clear-text"
+          type="button"
+          on:click={clearText}
+          aria-label={Drupal.t('Clear search text')}
+        >
+          <img
+            src="{FULL_MODULE_PATH}/images/cross{DARK_COLOR_SCHEME
+              ? '--dark-color-scheme'
+              : ''}.svg"
+            alt=""
+          />
+        </button>
+      {/if}
       <img
         class="search__search-icon"
         id="search-icon"
diff --git a/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php b/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php
index cd9a0a73a2fcbd99d173c5852112068d7c476ff8..34889d352fb112673dc1a19beffff2fd831abd04 100644
--- a/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php
+++ b/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php
@@ -974,4 +974,40 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
     $this->assertFalse($ui_install_input->hasAttribute('disabled'));
   }
 
+  /**
+   * Tests that we can clear search results with one click.
+   */
+  function testClearKeywordSearch() {
+    $page = $this->getSession()->getPage();
+    $this->assertSession();
+    $this->drupalGet('admin/modules/browse');
+    $this->svelteInitHelper('css', '.pb-search-results');
+    
+    // Get the original result count.
+    $results = $page->find('css', '.pb-search-results');
+    $script = "document.querySelector('.pb-search-results').textContent.includes('Results')";
+    $this->getSession()->wait(10000, $script);
+    $this->assertNotEmpty($results);
+    $original_text = $results->getText();
+    $this->assertNotEmpty($original_text);
+
+    // Search for something to change it.
+    $this->inputSearchField('abcdefghijklmnop');
+    $results = $page->find('css', '.pb-search-results');
+    $this->assertNotEmpty($results);
+    $new_text = $results->getText();
+    $this->assertNotSame($original_text, $new_text);
+    
+    // Remove the search text and make sure it auto-updates.
+    // Use our clear search button to do it.
+    // Clearing it should bring us back to the original value.
+    $clear_button = $page->find('css', '.search__search-clear');
+    $this->assertNotEmpty($clear_button);
+    $clear_button->click();
+    $results = $page->find('css', '.pb-search-results');
+    $this->assertNotEmpty($results);
+    $final_text = $results->getText();
+    $this->assertEquals($original_text, $final_text);
+  }
+
 }