htmx Tutorial · Common Patterns · Lesson 16 of 30

Active Search

Search as the user types, without sending a request for every keystroke, and keep the results accessible.

Create a free account to save this page, take notes and track your progress. Sign in

The pattern

A search box that updates the results while the user types. The important details:

  • hx-trigger="input changed delay:300ms": wait for a pause, and skip requests when the text didn't change.
  • search in the trigger list also fires when the user clears the box with the ✕ button of type="search".
  • hx-sync="this:replace": if a slow request is still running, cancel it, so old results never overwrite newer ones.
  • An htmx-indicator shows the search is running.
  • aria-live="polite" on the results lets screen readers announce the update.
Search a table as you typeTry it yourself
<input type="search" name="q" placeholder="Search languages..." aria-controls="rows"
       hx-get="/languages" hx-trigger="input changed delay:300ms, search" hx-target="#rows"
       hx-sync="this:replace" hx-indicator="#searching">
<span id="searching" class="htmx-indicator">Searching...</span>
<table><thead><tr><th>Language</th><th>Year</th></tr></thead>
  <tbody id="rows" aria-live="polite"></tbody></table>

<!-- The pretend server: on a real site this part is your backend (Python, PHP, Node, Go...) -->
<script>
  const langs = [["Python", 1991], ["JavaScript", 1995], ["Java", 1995], ["PHP", 1995], ["Go", 2009],
                 ["Rust", 2015], ["TypeScript", 2012], ["Kotlin", 2011], ["Swift", 2014], ["C#", 2000]];
  server.get("/languages", (req) => {
    const q = (req.query.q || "").toLowerCase();
    const rows = langs.filter(([name]) => name.toLowerCase().includes(q));
    return rows.length ? rows.map(([n, y]) => `<tr><td>${server.escape(n)}</td><td>${y}</td></tr>`).join("")
                       : `<tr><td colspan="2">No match for "${server.escape(q)}"</td></tr>`;
  }, { delay: 400 });
</script>

On the server this is an ordinary search query that returns table rows. Use a database LIKE (or full-text) query with bound parameters, never by pasting the user's text into SQL.

Test yourself

Why add hx-sync="this:replace" to a search input?

  1. So a slow old request is cancelled and can't overwrite newer results
  2. To replace the input after each search
  3. To make the search case-insensitive
  4. To cache the results
Show answer

Answer: So a slow old request is cancelled and can't overwrite newer results

Responses can arrive out of order; replace aborts the request that's still running.

Was this page helpful?