Published on June 4, 2023
Go homeCreating a custom search control with HTMX and Hyperscript
I have decided to see what type of advanced widgets I can build without explicitly using Javascript. The first control I decided to build was a search widget for this site. The control is currently live and can be opened by pressing Ctrl+k
. The search widget is built using the html5 dialog
element, which is available in most browsers, HTMX and it's companion library Hyperscript.
The control consists of the following HTML.
<dialog
id="search"
class="fixed top-0 left-1/4 m-0 py-5 rounded-t-none rounded-lg shadow-md w-[50%]">
<div class="flex flex-col gap-3">
<div>
<input
name="q"
type="text"
class="outline-none p-2 border-[1px] rounded-lg rounded-b-none h-10 w-full"
autocomplete="off"
autofocus="autofocus"
placeholder="Begin typing to search this website..."
hx-post="{% url 'bk-search' %}"
hx-trigger="keyup delay:100ms"
hx-target="#search-results"
hx-swap="innerHTML"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-include="[name='q']">
<div id="search-results" class="prose-sm"></div>
</div>
<form>
<button class="rounded bg-emerald-500 p-2 text-white font-bold w-full" formmethod="dialog">Close</button>
</form>
</div>
</dialog>
The #search-results
is populated by HTMX with the following partial template.
{% if articles %}
<div class="bg-white border-[1px] border-t-0 border-slate-300 rounded-b-lg z-50 max-h-[50vh] overflow-scroll">
{% for article in articles %}
<a href="{{ article.get_absolute_url }}" class="inline-block w-full p-2 bg-white hover:bg-slate-300 border-b-[1px] border-b-gray-300">
<p class="m-0 font-bold text-lg">{{ article.title }}</p>
<p class="m-0">{{ article.description }}</p>
</a>
{% endfor %}
</div>
{% endif %}
The final piece of the puzzle is the event listener which calls showModal
on the #search
dialog. I came up with the following hyperscript which I have attached to the body
tag.
<body
_="on keydown[key is 'k' and ctrlKey is true]
halt the event then
set search to #search
js(search) if (!search.open) { search.showModal() }
">
</body>