Table Sorting
A component that allows users to sort a table by clicking on the table headers.
<!-- Table Sorting -->
<!-- An Alpine.js and Tailwind CSS component by https://pinemix.com -->
<div
x-data="{
data: [
{ id: 1, name: 'Alex Morgan', email: 'a.morgan@example.com', mrr: '59,00' },
{ id: 2, name: 'Jamie Chen', email: 'j.chen@example.com', mrr: '19,00' },
{ id: 3, name: 'Riley Smith', email: 'r.smith@example.com', mrr: '29,00' },
{ id: 4, name: 'Taylor Wilson', email: 't.wilson@example.com', mrr: '59,00' },
{ id: 5, name: 'Jordan Lee', email: 'j.lee@example.com', mrr: '39,00' },
{ id: 6, name: 'Casey Brown', email: 'c.brown@example.com', mrr: '79,00' },
{ id: 7, name: 'Quinn Davis', email: 'q.davis@example.com', mrr: '39,00' },
{ id: 8, name: 'Avery Miller', email: 'a.miller@example.com', mrr: '49,00' },
{ id: 9, name: 'Morgan Parker', email: 'm.parker@example.com', mrr: '69,00' },
{ id: 10, name: 'Blake Johnson', email: 'b.johnson@example.com', mrr: '89,00' },
],
sortColumn: null,
sortDirection: 'asc',
sortedData: [],
init() {
this.sortedData = this.data;
},
sortBy(column) {
if (this.sortColumn === column) {
this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
} else {
this.sortDirection = 'asc';
this.sortColumn = column;
}
this.sortedData = [...this.data].sort((a, b) => {
if (a[column] < b[column]) {
return this.sortDirection === 'asc' ? -1 : 1;
}
if (a[column] > b[column]) {
return this.sortDirection === 'asc' ? 1 : -1;
}
return 0;
});
}
}"
>
<!-- Responsive Table Container -->
<div class="min-w-full overflow-x-auto rounded-xl">
<!-- Table -->
<table class="min-w-full align-middle text-sm whitespace-nowrap">
<!-- Table Header -->
<thead>
<tr class="border-b-2 border-zinc-100 dark:border-zinc-700/50">
<th
class="group w-24 px-3 py-2 text-center font-semibold text-zinc-900 dark:text-zinc-50"
>
<div class="inline-flex items-center gap-2">
<span>ID</span>
<button
@click="sortBy('id')"
type="button"
class="inline-flex items-center justify-center gap-2 rounded-lg border border-zinc-200 bg-white px-1.5 py-1 text-sm leading-5 font-semibold text-zinc-800 transition hover:border-zinc-300 hover:text-zinc-900 hover:shadow-xs focus:ring-3 focus:ring-zinc-300/25 active:border-zinc-200 active:shadow-none dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-300 dark:hover:border-zinc-600 dark:hover:text-zinc-200 dark:focus:ring-zinc-600/40 dark:active:border-zinc-700"
x-bind:class="{
'opacity-25 group-hover:opacity-100': sortColumn !== 'id'
}"
>
<template x-if="sortColumn !== 'id'">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="hi-micro hi-arrows-up-down inline-block size-4"
>
<path
fill-rule="evenodd"
d="M13.78 10.47a.75.75 0 0 1 0 1.06l-2.25 2.25a.75.75 0 0 1-1.06 0l-2.25-2.25a.75.75 0 1 1 1.06-1.06l.97.97V5.75a.75.75 0 0 1 1.5 0v5.69l.97-.97a.75.75 0 0 1 1.06 0ZM2.22 5.53a.75.75 0 0 1 0-1.06l2.25-2.25a.75.75 0 0 1 1.06 0l2.25 2.25a.75.75 0 0 1-1.06 1.06l-.97-.97v5.69a.75.75 0 0 1-1.5 0V4.56l-.97.97a.75.75 0 0 1-1.06 0Z"
clip-rule="evenodd"
/>
</svg>
</template>
<template
x-if="sortColumn === 'id' && sortDirection === 'desc'"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="hi-micro hi-arrow-long-down inline-block size-4"
>
<path
fill-rule="evenodd"
d="M8 2a.75.75 0 0 1 .75.75v8.69l1.22-1.22a.75.75 0 1 1 1.06 1.06l-2.5 2.5a.75.75 0 0 1-1.06 0l-2.5-2.5a.75.75 0 1 1 1.06-1.06l1.22 1.22V2.75A.75.75 0 0 1 8 2Z"
clip-rule="evenodd"
/>
</svg>
</template>
<template x-if="sortColumn === 'id' && sortDirection === 'asc'">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="hi-micro hi-arrow-long-up inline-block size-4"
>
<path
fill-rule="evenodd"
d="M8 14a.75.75 0 0 0 .75-.75V4.56l1.22 1.22a.75.75 0 1 0 1.06-1.06l-2.5-2.5a.75.75 0 0 0-1.06 0l-2.5 2.5a.75.75 0 0 0 1.06 1.06l1.22-1.22v8.69c0 .414.336.75.75.75Z"
clip-rule="evenodd"
/>
</svg>
</template>
</button>
</div>
</th>
<th
class="group px-3 py-2 text-start font-semibold text-zinc-900 dark:text-zinc-50"
>
<div class="inline-flex items-center gap-2">
<span>Name</span>
<button
@click="sortBy('name')"
type="button"
class="inline-flex items-center justify-center gap-2 rounded-lg border border-zinc-200 bg-white px-1.5 py-1 text-sm leading-5 font-semibold text-zinc-800 transition hover:border-zinc-300 hover:text-zinc-900 hover:shadow-xs focus:ring-3 focus:ring-zinc-300/25 active:border-zinc-200 active:shadow-none dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-300 dark:hover:border-zinc-600 dark:hover:text-zinc-200 dark:focus:ring-zinc-600/40 dark:active:border-zinc-700"
x-bind:class="{
'opacity-25 group-hover:opacity-100': sortColumn !== 'name'
}"
>
<template x-if="sortColumn !== 'name'">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="hi-micro hi-arrows-up-down inline-block size-4"
>
<path
fill-rule="evenodd"
d="M13.78 10.47a.75.75 0 0 1 0 1.06l-2.25 2.25a.75.75 0 0 1-1.06 0l-2.25-2.25a.75.75 0 1 1 1.06-1.06l.97.97V5.75a.75.75 0 0 1 1.5 0v5.69l.97-.97a.75.75 0 0 1 1.06 0ZM2.22 5.53a.75.75 0 0 1 0-1.06l2.25-2.25a.75.75 0 0 1 1.06 0l2.25 2.25a.75.75 0 0 1-1.06 1.06l-.97-.97v5.69a.75.75 0 0 1-1.5 0V4.56l-.97.97a.75.75 0 0 1-1.06 0Z"
clip-rule="evenodd"
/>
</svg>
</template>
<template
x-if="sortColumn === 'name' && sortDirection === 'desc'"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="hi-micro hi-arrow-long-down inline-block size-4"
>
<path
fill-rule="evenodd"
d="M8 2a.75.75 0 0 1 .75.75v8.69l1.22-1.22a.75.75 0 1 1 1.06 1.06l-2.5 2.5a.75.75 0 0 1-1.06 0l-2.5-2.5a.75.75 0 1 1 1.06-1.06l1.22 1.22V2.75A.75.75 0 0 1 8 2Z"
clip-rule="evenodd"
/>
</svg>
</template>
<template
x-if="sortColumn === 'name' && sortDirection === 'asc'"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="hi-micro hi-arrow-long-up inline-block size-4"
>
<path
fill-rule="evenodd"
d="M8 14a.75.75 0 0 0 .75-.75V4.56l1.22 1.22a.75.75 0 1 0 1.06-1.06l-2.5-2.5a.75.75 0 0 0-1.06 0l-2.5 2.5a.75.75 0 0 0 1.06 1.06l1.22-1.22v8.69c0 .414.336.75.75.75Z"
clip-rule="evenodd"
/>
</svg>
</template>
</button>
</div>
</th>
<th
class="group px-3 py-2 text-start font-semibold text-zinc-900 dark:text-zinc-50"
>
<div class="inline-flex items-center gap-2">
<span>Email</span>
<button
@click="sortBy('email')"
type="button"
class="inline-flex items-center justify-center gap-2 rounded-lg border border-zinc-200 bg-white px-1.5 py-1 text-sm leading-5 font-semibold text-zinc-800 transition hover:border-zinc-300 hover:text-zinc-900 hover:shadow-xs focus:ring-3 focus:ring-zinc-300/25 active:border-zinc-200 active:shadow-none dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-300 dark:hover:border-zinc-600 dark:hover:text-zinc-200 dark:focus:ring-zinc-600/40 dark:active:border-zinc-700"
x-bind:class="{
'opacity-25 group-hover:opacity-100': sortColumn !== 'email'
}"
>
<template x-if="sortColumn !== 'email'">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="hi-micro hi-arrows-up-down inline-block size-4"
>
<path
fill-rule="evenodd"
d="M13.78 10.47a.75.75 0 0 1 0 1.06l-2.25 2.25a.75.75 0 0 1-1.06 0l-2.25-2.25a.75.75 0 1 1 1.06-1.06l.97.97V5.75a.75.75 0 0 1 1.5 0v5.69l.97-.97a.75.75 0 0 1 1.06 0ZM2.22 5.53a.75.75 0 0 1 0-1.06l2.25-2.25a.75.75 0 0 1 1.06 0l2.25 2.25a.75.75 0 0 1-1.06 1.06l-.97-.97v5.69a.75.75 0 0 1-1.5 0V4.56l-.97.97a.75.75 0 0 1-1.06 0Z"
clip-rule="evenodd"
/>
</svg>
</template>
<template
x-if="sortColumn === 'email' && sortDirection === 'desc'"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="hi-micro hi-arrow-long-down inline-block size-4"
>
<path
fill-rule="evenodd"
d="M8 2a.75.75 0 0 1 .75.75v8.69l1.22-1.22a.75.75 0 1 1 1.06 1.06l-2.5 2.5a.75.75 0 0 1-1.06 0l-2.5-2.5a.75.75 0 1 1 1.06-1.06l1.22 1.22V2.75A.75.75 0 0 1 8 2Z"
clip-rule="evenodd"
/>
</svg>
</template>
<template
x-if="sortColumn === 'email' && sortDirection === 'asc'"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="hi-micro hi-arrow-long-up inline-block size-4"
>
<path
fill-rule="evenodd"
d="M8 14a.75.75 0 0 0 .75-.75V4.56l1.22 1.22a.75.75 0 1 0 1.06-1.06l-2.5-2.5a.75.75 0 0 0-1.06 0l-2.5 2.5a.75.75 0 0 0 1.06 1.06l1.22-1.22v8.69c0 .414.336.75.75.75Z"
clip-rule="evenodd"
/>
</svg>
</template>
</button>
</div>
</th>
<th
class="group px-3 py-2 text-end font-semibold text-zinc-900 dark:text-zinc-50"
>
<div class="inline-flex items-center gap-2">
<span>MRR</span>
<button
@click="sortBy('mrr')"
type="button"
class="inline-flex items-center justify-center gap-2 rounded-lg border border-zinc-200 bg-white px-1.5 py-1 text-sm leading-5 font-semibold text-zinc-800 transition hover:border-zinc-300 hover:text-zinc-900 hover:shadow-xs focus:ring-3 focus:ring-zinc-300/25 active:border-zinc-200 active:shadow-none dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-300 dark:hover:border-zinc-600 dark:hover:text-zinc-200 dark:focus:ring-zinc-600/40 dark:active:border-zinc-700"
x-bind:class="{
'opacity-25 group-hover:opacity-100': sortColumn !== 'mrr'
}"
>
<template x-if="sortColumn !== 'mrr'">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="hi-micro hi-arrows-up-down inline-block size-4"
>
<path
fill-rule="evenodd"
d="M13.78 10.47a.75.75 0 0 1 0 1.06l-2.25 2.25a.75.75 0 0 1-1.06 0l-2.25-2.25a.75.75 0 1 1 1.06-1.06l.97.97V5.75a.75.75 0 0 1 1.5 0v5.69l.97-.97a.75.75 0 0 1 1.06 0ZM2.22 5.53a.75.75 0 0 1 0-1.06l2.25-2.25a.75.75 0 0 1 1.06 0l2.25 2.25a.75.75 0 0 1-1.06 1.06l-.97-.97v5.69a.75.75 0 0 1-1.5 0V4.56l-.97.97a.75.75 0 0 1-1.06 0Z"
clip-rule="evenodd"
/>
</svg>
</template>
<template
x-if="sortColumn === 'mrr' && sortDirection === 'desc'"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="hi-micro hi-arrow-long-down inline-block size-4"
>
<path
fill-rule="evenodd"
d="M8 2a.75.75 0 0 1 .75.75v8.69l1.22-1.22a.75.75 0 1 1 1.06 1.06l-2.5 2.5a.75.75 0 0 1-1.06 0l-2.5-2.5a.75.75 0 1 1 1.06-1.06l1.22 1.22V2.75A.75.75 0 0 1 8 2Z"
clip-rule="evenodd"
/>
</svg>
</template>
<template
x-if="sortColumn === 'mrr' && sortDirection === 'asc'"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="hi-micro hi-arrow-long-up inline-block size-4"
>
<path
fill-rule="evenodd"
d="M8 14a.75.75 0 0 0 .75-.75V4.56l1.22 1.22a.75.75 0 1 0 1.06-1.06l-2.5-2.5a.75.75 0 0 0-1.06 0l-2.5 2.5a.75.75 0 0 0 1.06 1.06l1.22-1.22v8.69c0 .414.336.75.75.75Z"
clip-rule="evenodd"
/>
</svg>
</template>
</button>
</div>
</th>
<th
class="group px-3 py-2 text-center font-semibold text-zinc-900 dark:text-zinc-50"
>
<span>Actions</span>
</th>
</tr>
</thead>
<!-- END Table Header -->
<!-- Table Body -->
<tbody>
<template x-for="item in sortedData" :key="item.id">
<tr
class="border-t border-zinc-100 even:bg-secondary-50 dark:border-zinc-700/50 dark:even:bg-secondary-900/50"
>
<td x-text="item.id" class="px-3 py-2 text-center"></td>
<td x-text="item.name" class="px-3 py-2 font-medium"></td>
<td
x-text="item.email"
class="px-3 py-2 text-zinc-600 dark:text-zinc-400"
></td>
<td
class="px-3 py-2 text-end font-medium text-teal-600 dark:text-teal-400"
>
$<span x-text="item.mrr"></span>
</td>
<td class="px-3 py-2 text-center">
<button
type="button"
class="inline-flex items-center justify-center gap-2 rounded-lg border border-zinc-200 bg-white px-2 py-1 text-sm leading-5 font-semibold text-zinc-800 hover:border-zinc-300 hover:text-zinc-900 hover:shadow-xs focus:ring-3 focus:ring-zinc-300/25 active:border-zinc-200 active:shadow-none dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-300 dark:hover:border-zinc-600 dark:hover:text-zinc-200 dark:focus:ring-zinc-600/40 dark:active:border-zinc-700"
>
Edit
</button>
</td>
</tr>
</template>
</tbody>
<!-- END Table Body -->
</table>
<!-- END Table -->
</div>
<!-- END Responsive Table Container -->
</div>
<!-- END Table Sorting -->
Props
The available data properties for this component.
Property | Default | Description |
---|---|---|
data | [] | An object array to populate the table. Values for 'id', 'name', 'email' and 'mrr' attributes have to be provided for this example (with unique ids) |
Designed with
Tailkit
Featuring 1,900+ Tailwind CSS code snippets for HTML, React, Vue.js and Alpine.js projects
Unlock 15+ free templates
Join our pixelcave newsletter to get them now & we'll also keep you updated about any new Pinemix components!