January 5, 2023
Tutorial: How to Make a Simple Search Component in Alpine JS
Justin Golden
Length: Medium
Level: ✓ Beginner ✓ Intermediate X Advanced
Add Alpine
AlpineJS.dev has the script source to get you started as //unpkg.com/alpinejs
, but we’ll want to use the most recent version and pin it, so paste that URL in your browser and then copy the new URL (At time of writing, it’s https://unpkg.com/alpinejs@3.10.5/dist/cdn.min.js
)
So add the following to your <head>
:
<script src="https://unpkg.com/alpinejs@3.10.5/dist/cdn.min.js" defer></script>
Add Your Data
Next, add your data in. Open a <script>
tag and put your data in a const (of course, for an app with a backend, fetch your data and then load async):
const userData = [
{
id: 1,
name: 'Joe',
age: 20,
image: 'https://randomuser.me/api/portraits/men/1.jpg'
},
{
id: 2,
name: 'Sally',
age: 25,
image: 'https://randomuser.me/api/portraits/women/1.jpg'
},
{
id: 3,
name: 'Tom',
age: 30,
image: 'https://randomuser.me/api/portraits/men/2.jpg'
},
{
id: 4,
name: 'John',
age: 35,
image: 'https://randomuser.me/api/portraits/men/3.jpg'
},
{
id: 5,
name: 'Samantha',
age: 40,
image: 'https://randomuser.me/api/portraits/women/2.jpg'
}
];
Add in your HTML
(I’m using some Tailwind classes for this example)
<div class="container mx-auto">
<input
placeholder="Search for a user..."
type="search"
class="block w-full rounded bg-gray-200 p-4 mb-4"
/>
<div class="grid sm:grid-cols-2 md:grid-cols-4 gap-4">
<div class="flex items-center shadow p-4">
<img class="rounded-full w-12 h-12" src="https://randomuser.me/api/portraits/men/2.jpg" />
<div class="ml-4">
<p class="text-lg text-gray-900">Joe</p>
<p class="text-sm font-bold text-gray-600">20</p>
</div>
</div>
<div class="flex items-center shadow p-4">
<img class="rounded-full w-12 h-12" src="https://randomuser.me/api/portraits/men/2.jpg" />
<div class="ml-4">
<p class="text-lg text-gray-900">Joe</p>
<p class="text-sm font-bold text-gray-600">20</p>
</div>
</div>
</div>
</div>
Set up Your Loop
<div x-data="userData">
<!-- ... -->
<div class="flex items-center shadow p-4">
<img class="rounded-full w-12 h-12" :src="user.image" />
<div class="ml-4">
<p class="text-lg text-gray-900" x-text="user.name"></p>
<p class="text-sm font-bold text-gray-600" x-text="user.age"></p>
</div>
</div>
</div>
In this loop, we use a template
with x-for
to iterate each user (key of id
) and then we fill in the image src
and paragraph text
with Alpine.
Set Up Your Search
Add x-model="search"
to bind the data to the search
variable:
<input
x-model="search"
placeholder="Search for a user..."
type="search"
class="block w-full rounded bg-gray-200 p-4 mb-4"
/>
Then, inside your script, add a function to getUsers()
and replace your x-data
with that:
<div class="container mx-auto" x-data="getUsers()">
function getUsers() {
return {
search: '',
userData: userData
};
}
Now, let’s add the search algorithm:
function getUsers() {
return {
search: '',
allData: userData,
get filteredUsers() {
if (this.search === '') {
return this.allData;
}
return this.allData.filter((user) => {
return user.name
.replace(/ /g, '')
.toLowerCase()
.includes(this.search.replace(/ /g, '').toLowerCase());
});
}
};
}
We’ll add a getter for filtered users. The first step is to return all data if our search term is empty.
Next, we can filter our data according to this.search
. The function above takes the user’s name
field without spaces and lowercase and compares it to the search term without spaces and lowercase. We use includes
to see if it’s anywhere inside the string, but you can use startsWith
to check the beginning of strings, or change any other parts of the search algorithm such as comparing multiple different fields.
Note the usage of this.
to use search
and allData
inside our object.
We’ll want to replace userData
with filteredUsers
in our x-for
loop as well.
Bonus: Slash to Focus Search
To make hitting slash focus our input, we simply give it an x-ref
so we can refer to it: x-ref="searchInput"
, then we add an x-on
to run on the keydown event and focus our input: x-on:keydown.window.prevent.slash="$refs.searchInput.focus()"
<input
x-ref="searchInput"
x-model="search"
x-on:keydown.window.prevent.slash="$refs.searchInput.focus()"
placeholder="Search for a user..."
type="search"
class="block w-full rounded bg-gray-200 p-4 mb-4"
/>
Full File
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<script src="https://unpkg.com/alpinejs@3.10.5/dist/cdn.min.js" defer></script>
</head>
<body class="bg-gray-100 text-gray-900 px-4 py-8">
<div class="container mx-auto" x-data="getUsers()">
<input
x-ref="searchInput"
x-model="search"
x-on:keydown.window.prevent.slash="$refs.searchInput.focus()"
placeholder="Search for a user..."
type="search"
class="block w-full rounded bg-gray-200 p-4 mb-4"
/>
<div class="grid sm:grid-cols-2 md:grid-cols-4 gap-4">
<template x-for="user in filteredUsers" :key="user.id">
<div class="flex items-center shadow p-4">
<img class="rounded-full w-12 h-12" :src="user.image" />
<div class="ml-4">
<p class="text-lg text-gray-900" x-text="user.name"></p>
<p class="text-sm font-bold text-gray-600" x-text="user.age"></p>
</div>
</div>
</template>
</div>
</div>
<script>
function getUsers() {
return {
search: '',
allData: userData,
get filteredUsers() {
if (this.search === '') {
return this.allData;
}
return this.allData.filter((user) => {
return user.name
.replace(/ /g, '')
.toLowerCase()
.includes(this.search.replace(/ /g, '').toLowerCase());
});
}
};
}
const userData = [
{
id: 1,
name: 'Joe',
age: 20,
image: 'https://randomuser.me/api/portraits/men/1.jpg'
},
{
id: 2,
name: 'Sally',
age: 25,
image: 'https://randomuser.me/api/portraits/women/1.jpg'
},
{
id: 3,
name: 'Tom',
age: 30,
image: 'https://randomuser.me/api/portraits/men/2.jpg'
},
{
id: 4,
name: 'John',
age: 35,
image: 'https://randomuser.me/api/portraits/men/3.jpg'
},
{
id: 5,
name: 'Samantha',
age: 40,
image: 'https://randomuser.me/api/portraits/women/2.jpg'
}
];
</script>
</body>
</html>
This example was modified from AlpineToolbox.com.
More Blog Articles