Tutorials / / 3 min read
How to create a search input with Tailwind CSS: JavaScript and Alpine.js
Build a live search input with Tailwind CSS, two ways: vanilla JavaScript or Alpine.js. Filter a list of items as the user types, no extra libraries.
Today we are building a search input that filters a list of items as you type. We’ll build it twice, first with vanilla JavaScript, then with Alpine.js, so you can pick whichever fits your stack.
What is a search input?
A search input lets users type a query and instantly retrieve matching results. Ours filters a small list as the user types, no search button needed, which is a quick way to help people find specific content without wading through everything.
Use cases
- Product search: help shoppers find items in a large inventory.
- File search: locate documents or images inside a directory tree.
- Docs and knowledge bases: jump straight to the right article.
- Data tables: narrow down large datasets to the rows that matter.
The markup
id="search-component": the container for the search input and the list of items.id="search-input": the search input.id="items-list": the list of items that gets filtered based on the search query.
Classes are omitted for brevity
<div id="search-component">
<!-- Search Input -->
<input id="search-input" />
<!-- Filtered Items -->
<ul id="items-list">
<!-- List items will be injected here by JavaScript -->
</ul>
</div>The script
document.addEventListener("DOMContentLoaded", function () {: waits for the DOM, then grabs the component, the input, and the list.const items = [...]: the array of items we filter, a handful of city names in this case.function renderItems(filter = "") {: clears the list, filters the items with a case-insensitiveincludes, and appends a freshlifor each match.searchInput.addEventListener("input", function () {: re-renders the list on every keystroke with the current query.renderItems();: runs once at the start so the full list shows before any typing.
document.addEventListener("DOMContentLoaded", function () {
const searchComponent = document.getElementById("search-component");
const searchInput = document.getElementById("search-input");
const itemsList = document.getElementById("items-list");
const items = [
"Milano",
"Alicante",
"Switzerland",
"Bilbao",
"Åland Islands",
"Stockholm",
"Torrevieja",
"Minneapolis",
];
function renderItems(filter = "") {
itemsList.innerHTML = "";
const filteredItems = items.filter((item) =>
item.toLowerCase().includes(filter.toLowerCase())
);
filteredItems.forEach((item) => {
const li = document.createElement("li");
li.textContent = item;
itemsList.appendChild(li);
});
}
searchInput.addEventListener("input", function () {
renderItems(searchInput.value);
});
// Initial render
renderItems();
});The Alpine.js version
Same component, no separate script: the state lives in x-data.
x-data="{ searchQuery: '', items: [...] }": stores the search query and the list of items, mapped into objects with aname.<input type="text" x-model="searchQuery" placeholder="Search...">: binds the input to the query.<template x-for="item in items" :key="item.name">: renders each item in the list.x-show="item.name.toLowerCase().includes(searchQuery.toLowerCase())": hides the items that don’t match the query, andx-text="item.name"fills in the label.
<div
x-data="{
searchQuery: '',
items: [
'Milano', 'Alicante', 'Switzerland', 'Bilbao', 'Åland Islands', 'Stockholm', 'Torrevieja', 'Minneapolis'
].map(item => ({ name: item }))
}"
>
<!-- Search Input -->
<input type="text" x-model="searchQuery" placeholder="Search..." />
<!-- Filtered Items -->
<ul>
<template x-for="item in items" :key="item.name">
<li
x-show="item.name.toLowerCase().includes(searchQuery.toLowerCase())"
x-text="item.name"
></li>
</template>
</ul>
</div>You can try the Alpine version separately: live demo and source code.
Which one should you use?
The Alpine version filters with x-show right in the template, a natural fit if Alpine is already around. The vanilla version re-renders the list itself and needs nothing but the browser.
Conclusion
This is a super simple search that filters a list without any extra libraries, not production-ready, but a great way to learn. Remember to add accessibility features like a label for the input before shipping it.
Hope you enjoyed this tutorial and have a great day!
/Michael Andreuzza