Live search
Live search is a search input wired to htmx so the results region
updates as the user types. The form still works without JavaScript —
htmx attributes hang off a real <form action="/items" method="get">.
Expanded HTML
Section titled “Expanded HTML”<form class="hc-search" action="/items" method="get" role="search"> <input class="hc-input" type="search" name="q" placeholder="Search" data-hx-get="/items" data-hx-trigger="input changed delay:300ms, search" data-hx-target="#results" data-hx-swap="innerHTML" data-hx-sync="closest form:replace">
<button class="hc-button" type="submit">Search</button></form>
<div id="results" aria-live="polite"></div>How it works
Section titled “How it works”| Attribute | Effect |
|---|---|
data-hx-get="/items" | GET the search endpoint. |
data-hx-trigger="input changed delay:300ms, search" | Fire 300ms after the user stops typing, or immediately on the native search event. |
data-hx-target="#results" | Replace the results container. |
data-hx-swap="innerHTML" | Swap the inner HTML of the target. |
data-hx-sync="closest form:replace" | If a new request starts while one is in flight, cancel the old one. |
The form itself stays valid:
<form action="/items" method="get">works without JavaScript — the submit button posts the sameqparameter to the same URL.type="search"provides the native clear button and thesearchevent that htmx listens for.role="search"exposes the form as a search landmark to assistive tech.
Macro form
Section titled “Macro form”If you import @hypermedia-components/core/macros, you can write the
same form as a single custom element:
<hc-live-search action="/items" target="#results" name="q" placeholder="Search items" label="Search items" delay="200ms" submit-label="Search"></hc-live-search>
<div id="results" aria-live="polite"></div><hc-live-search> replaces its children with the expanded form on
connectedCallback. The macro does not create the results
container — it only emits the search form so you can place results
wherever the page wants.
Attributes:
| Attribute | Default | Notes |
|---|---|---|
action | (required) | Search endpoint; becomes both <form action> and data-hx-get. |
target | (required) | data-hx-target selector for the results container. |
name | q | Query parameter name on the input. |
placeholder | Search | Input placeholder. |
label | (omitted) | Visible <label> text. If omitted, the input gets an aria-label instead. |
aria-label | Search | Fallback aria-label when no visible label. |
delay | 300ms | htmx debounce delay (substituted into data-hx-trigger). |
submit-label | Search | Submit-button text. |
swap | innerHTML | data-hx-swap mode. |
no-submit | (boolean) | Omit the submit button. |
The macro is optional — for any customization beyond these attributes, copy the expanded HTML below and edit it directly.
Server response contract
Section titled “Server response contract”The server should return only the inner HTML of #results — not
the surrounding <div>.
<!-- /items?q=foo --><ul class="hc-list"> <li><a href="/items/1">Item one</a></li> <li><a href="/items/2">Item two</a></li></ul>For the empty state, return explicit empty-state markup so the user knows the search ran:
<!-- /items?q=zzz --><p class="hc-field__message">No results for "zzz".</p>Return the same HTML when a non-htmx request comes in (full page load
after submitting the form without JavaScript). Servers can detect htmx
requests via the HX-Request: true header and decide whether to wrap
the response in a full page layout or send the fragment only.
Loading indicator
Section titled “Loading indicator”To show a spinner while the search runs, add an htmx indicator that points at the results region:
<div id="results" aria-live="polite"> <!-- During a request htmx adds the `.htmx-request` class to the triggering element; the `.htmx-indicator` spinner below reacts to it. Drive a busy state from that class if you want one. --></div>
<span class="hc-spinner htmx-indicator" id="search-spinner" aria-hidden="true"></span>Add data-hx-indicator="#search-spinner" to the input.
Accessibility
Section titled “Accessibility”role="search"on the<form>exposes the search landmark.aria-live="polite"on#resultsannounces the new result count (or empty state) without interrupting the user’s typing.- The native
type="search"input is the right primitive — it brings the clear button, IME-friendly behavior, and thesearchevent for Enter / form submission. - Avoid moving focus into the results list automatically. Keyboard users should stay in the input; if you provide keyboard navigation into results, document the keys clearly.
Progressive enhancement
Section titled “Progressive enhancement”- Without JavaScript, the form submits to
action="/items"and the server renders a full results page. - With htmx, the same submit endpoint returns the results fragment.
- With JavaScript but htmx not yet loaded (e.g. deferred loading), the form still submits normally on Enter.
The trick is that the htmx attributes never replace the form’s
fundamentals. action and method stay intact; htmx augments.
Related
Section titled “Related”- Input — the control.
- Field — pair an input with a label when you need one.
- Request action — general htmx-with-spinner pattern.