The table component is used to generate tables from arrays. Tables can be static or include features like filtering, sorting, pagination, reordering and others.
<table
x-data="table"
:data-items="$store.table.data"
:data-primary-key="$store.table.primaryKey"
class="w-full table-auto border-collapse text-left text-[0.9rem]"
class-loading="opacity-50 pointer-events-none"
>
<thead>
<tr>
<template x-for="col in definition">
<td
x-bind="header"
class="border-b border-gray-300 px-2 py-2 font-semibold text-text-800 dark:border-dark-600 dark:text-text-300"
>
<div class="flex items-center">
<span x-text="col.label"></span>
<template x-if="isSortable() && !isSorted()">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
fill="currentColor"
class="ml-3 h-4 w-4 opacity-30"
>
<path
d="M137.4 41.4c12.5-12.5 32.8-12.5 45.3 0l128 128c9.2 9.2 11.9 22.9 6.9 34.9s-16.6 19.8-29.6 19.8L32 224c-12.9 0-24.6-7.8-29.6-19.8s-2.2-25.7 6.9-34.9l128-128zm0 429.3l-128-128c-9.2-9.2-11.9-22.9-6.9-34.9s16.6-19.8 29.6-19.8l256 0c12.9 0 24.6 7.8 29.6 19.8s2.2 25.7-6.9 34.9l-128 128c-12.5 12.5-32.8 12.5-45.3 0z"
/>
</svg>
</template>
<template x-if="isSortedAsc()">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
fill="currentColor"
class="ml-3 h-4 w-4 opacity-70"
>
<path
d="M182.6 137.4c-12.5-12.5-32.8-12.5-45.3 0l-128 128c-9.2 9.2-11.9 22.9-6.9 34.9s16.6 19.8 29.6 19.8l256 0c12.9 0 24.6-7.8 29.6-19.8s2.2-25.7-6.9-34.9l-128-128z"
/>
</svg>
</template>
<template x-if="isSortedDesc()">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
fill="currentColor"
class="ml-3 h-4 w-4 opacity-70"
>
<path
d="M137.4 374.6c12.5 12.5 32.8 12.5 45.3 0l128-128c9.2-9.2 11.9-22.9 6.9-34.9s-16.6-19.8-29.6-19.8L32 192c-12.9 0-24.6 7.8-29.6 19.8s-2.2 25.7 6.9 34.9l128 128z"
/>
</svg>
</template>
</div>
</td>
</template>
</tr>
</thead>
<tbody>
<template x-for="row in getDataPaginated" :key="row[primaryKey]">
<tr>
<template x-for="col in definition">
<td
x-text="getCellContent"
class="border-t border-gray-300 px-2 py-2 text-text-800 transition-colors duration-200 dark:border-dark-600 dark:text-text-300"
></td>
</template>
</tr>
</template>
</tbody>
</table>
document.addEventListener('alpine:init', () => {
Alpine.store('table', {
data: [
{"id":1,"first_name":"Anthony","last_name":"Linbohm","city":"Makui","department":"Business Development","title":"Quality Engineer"},
{"id":2,"first_name":"Richard","last_name":"Moult","city":"Xihu","department":"Legal","title":"Budget/Accounting Analyst IV"},
{"id":3,"first_name":"Chance","last_name":"Dallas","city":"Moncton","department":"Support","title":"Product Engineer"},
{"id":4,"first_name":"Rozamond","last_name":"Abbatucci","city":"Chico","department":"Legal","title":"Software Consultant"},
{"id":5,"first_name":"Ashely","last_name":"Petrozzi","city":"Lafia","department":"Services","title":"Staff Accountant III"},
{"id":6,"first_name":"Bron","last_name":"Siuda","city":"Mora","department":"Accounting","title":"Marketing Manager"},
{"id":7,"first_name":"Marena","last_name":"Geraldi","city":"Karanganyar","department":"Support","title":"Compensation Analyst"},
{"id":8,"first_name":"Tomas","last_name":"Donneely","city":"Meirinhas","department":"Services","title":"Research Associate"},
],
primaryKey: 'id',
})
})
<script defer src="https://cdn.jsdelivr.net/npm/litewind-alpine@0.x.x/components/table/dist/cdn.min.js"></script>
The data for the component is provided by the table
function in the x-data
directive and the props in the data-*
attributes. Due to shared state such as filter, pagination, and more, it's recommended to set up the data as a separate store and use the $store
magic to bind props to the table.
data-items
[]
Array
Data to display in the table.
data-primary-key
empty string
String
This prop should be the name of the property that is unique for every record.
data-definition
[]
Array
Table definition is an optional array
of objects
that defines columns of the table. See the detailed explanation below.
data-filter
empty string
String
String used to filter items.
data-page
1
Number
Current page number.
data-items-per-page
0
Number
Number of records (rows) on the single page. Setting it to the 0
disables pagination.
data-is-loading
false
Boolean
If true, adds classes from the class-loading
attribute to the table. This can be useful, for example, when loading table data.
update:items-filtered
Array
Event dispatched after each filtering of the items. This can be useful, for example to update pagination component.
The table definition is an optional array of objects that defines table's columns. Each object must include a unique key
property, along with a number of optional properties. The key
determines which property of the record object is rendered in the column. If key is not found, an empty column is added.
If definition is not provided, the component generates one using the first record in the data array. All additional properties are then set to default values. While this is sufficient for simple tables, using features like sorting or filtering requires providing a custom definition array.
key
empty string
String
The key
defines which property of the record object will be rendered in the column.
sortable
false
Boolean
Enables sorting of the column.
filterable
true
Boolean
Enables filtering of the column.
label
undefined
String
Sets header for this column. If not provided header is the same as key converted to Header Case.
<table
x-data="table"
:data-items="$store.tableDefinition.data"
:data-definition="$store.tableDefinition.definition"
:data-primary-key="$store.tableDefinition.primaryKey"
class="w-full table-auto border-collapse text-left text-[0.9rem]"
class-loading="opacity-50 pointer-events-none"
>
<thead>
<tr>
<template x-for="col in definition">
<td
x-bind="header"
class="border-b border-gray-300 px-2 py-2 font-semibold text-text-800 dark:border-dark-600 dark:text-text-300"
>
<div class="flex items-center">
<span x-text="col.label"></span>
<template x-if="isSortable() && !isSorted()">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
fill="currentColor"
class="ml-3 h-4 w-4 opacity-30"
>
<path
d="M137.4 41.4c12.5-12.5 32.8-12.5 45.3 0l128 128c9.2 9.2 11.9 22.9 6.9 34.9s-16.6 19.8-29.6 19.8L32 224c-12.9 0-24.6-7.8-29.6-19.8s-2.2-25.7 6.9-34.9l128-128zm0 429.3l-128-128c-9.2-9.2-11.9-22.9-6.9-34.9s16.6-19.8 29.6-19.8l256 0c12.9 0 24.6 7.8 29.6 19.8s2.2 25.7-6.9 34.9l-128 128c-12.5 12.5-32.8 12.5-45.3 0z"
/>
</svg>
</template>
<template x-if="isSortedAsc()">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
fill="currentColor"
class="ml-3 h-4 w-4 opacity-70"
>
<path
d="M182.6 137.4c-12.5-12.5-32.8-12.5-45.3 0l-128 128c-9.2 9.2-11.9 22.9-6.9 34.9s16.6 19.8 29.6 19.8l256 0c12.9 0 24.6-7.8 29.6-19.8s2.2-25.7-6.9-34.9l-128-128z"
/>
</svg>
</template>
<template x-if="isSortedDesc()">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
fill="currentColor"
class="ml-3 h-4 w-4 opacity-70"
>
<path
d="M137.4 374.6c12.5 12.5 32.8 12.5 45.3 0l128-128c9.2-9.2 11.9-22.9 6.9-34.9s-16.6-19.8-29.6-19.8L32 192c-12.9 0-24.6 7.8-29.6 19.8s-2.2 25.7 6.9 34.9l128 128z"
/>
</svg>
</template>
</div>
</td>
</template>
</tr>
</thead>
<tbody>
<template x-for="row in getDataPaginated" :key="row[primaryKey]">
<tr>
<template x-for="col in definition">
<td
class="border-t border-gray-300 px-2 py-2 text-text-800 transition-colors duration-200 dark:border-dark-600 dark:text-text-300"
>
<span x-text="getCellContent"></span>
<template x-if="col.key === 'status'">
<svg
xmlns="http://www.w3.org/2000/svg"
id="mdi-checkbox-outline"
viewBox="0 0 24 24"
fill="currentColor"
class="mx-auto h-5 w-5 text-green-600"
>
<path d="M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3M19,5V19H5V5H19M10,17L6,13L7.41,11.58L10,14.17L16.59,7.58L18,9" />
</svg>
</template>
<template x-if="col.key === 'edit'">
<svg
xmlns="http://www.w3.org/2000/svg"
id="mdi-account-edit"
viewBox="0 0 24 24"
fill="currentColor"
class="mx-auto h-5 w-5"
>
<path d="M21.7,13.35L20.7,14.35L18.65,12.3L19.65,11.3C19.86,11.09 20.21,11.09 20.42,11.3L21.7,12.58C21.91,12.79 21.91,13.14 21.7,13.35M12,18.94L18.06,12.88L20.11,14.93L14.06,21H12V18.94M12,14C7.58,14 4,15.79 4,18V20H10V18.11L14,14.11C13.34,14.03 12.67,14 12,14M12,4A4,4 0 0,0 8,8A4,4 0 0,0 12,12A4,4 0 0,0 16,8A4,4 0 0,0 12,4Z" />
</svg>
</template>
</td>
</template>
</tr>
</template>
</tbody>
</table>
document.addEventListener('alpine:init', () => {
Alpine.store('tableDefinition', {
data: [
{"id":1,"first_name":"Anthony","last_name":"Linbohm","city":"Makui","department":"Business Development","title":"Quality Engineer"},
{"id":2,"first_name":"Richard","last_name":"Moult","city":"Xihu","department":"Legal","title":"Budget/Accounting Analyst IV"},
{"id":3,"first_name":"Chance","last_name":"Dallas","city":"Moncton","department":"Support","title":"Product Engineer"},
{"id":4,"first_name":"Rozamond","last_name":"Abbatucci","city":"Chico","department":"Legal","title":"Software Consultant"},
{"id":5,"first_name":"Ashely","last_name":"Petrozzi","city":"Lafia","department":"Services","title":"Staff Accountant III"},
{"id":6,"first_name":"Bron","last_name":"Siuda","city":"Mora","department":"Accounting","title":"Marketing Manager"},
{"id":7,"first_name":"Marena","last_name":"Geraldi","city":"Karanganyar","department":"Support","title":"Compensation Analyst"},
{"id":8,"first_name":"Tomas","last_name":"Donneely","city":"Meirinhas","department":"Services","title":"Research Associate"},
{"id":9,"first_name":"Umberto","last_name":"Cohalan","city":"Cuamba","department":"Research and Development","title":"Chemical Engineer"},
{"id":10,"first_name":"Nicola","last_name":"Flippelli","city":"Faqīrwāli","department":"Human Resources","title":"VP Quality Control"},
],
primaryKey: 'id',
definition: [
{
key: 'status',
label: 'Status',
},
{
key: 'first_name',
label: 'First name',
sortable: true,
},
{
key: 'last_name',
label: 'Last name',
sortable: true,
},
{
key: 'title',
sortable: true,
},
{
key: 'department',
sortable: true,
},
{
key: 'edit',
label: 'Edit',
}
]
})
})
To enable pagination, set the data-items-per-page
to any number greater than 0
. The current page can be controlled with the page
prop. In the example below, the table's current page is managed by the pagination component.
Filtering is enabled by default for all columns. The table data is filtered based on the string provided in the data-filter
prop. The update:items-filtered
event is used to update the item count for the pagination component.
|
<div class="flex flex-col gap-y-4">
<div
x-data="input"
x-model="$store.tableFilterPagination.filter"
class="flex flex-1 items-center rounded-sm border px-3 py-2 outline-hidden transition-shadow duration-200 focus-within:ring-3 focus:outline-hidden"
class-default="border-gray-300 bg-white focus-within:border-gray-400 focus-within:ring-primary-200 dark:border-dark-600 dark:bg-dark-800 dark:text-text-300 dark:focus-within:ring-primary-300"
class-valid="border-success-300 bg-white text-success-600 focus-within:ring-success-200 dark:border-success-400 dark:bg-dark-800 dark:text-success-600 dark:focus-within:ring-success-300"
class-invalid="border-danger-300 bg-white text-danger-600 focus-within:ring-danger-200 dark:border-danger-400 dark:bg-dark-800 dark:text-danger-600 dark:focus-within:ring-danger-300"
>
<div data-icon class="mr-3 empty:hidden"></div>
<div data-prepend class="mr-3 empty:hidden"></div>
<div class="mr-3 flex flex-1 flex-wrap">
<input
x-bind="input"
type="text"
class="w-full min-w-0 flex-1 border-0 bg-transparent p-0 outline-hidden focus:min-w-[64px] focus:outline-hidden"
/>
</div>
<div data-append class="mr-3 empty:hidden"></div>
<div class="flex items-center gap-x-2">
<div x-bind="loader">
<svg
viewBox="25 25 50 50"
fill="none"
class="h-5 w-5 animate-spinner-rotate"
>
<circle
cx="50"
cy="50"
r="20"
stroke="currentColor"
stroke-width="4"
stroke-miterlimit="10"
stroke-linecap="round"
class="animate-spinner-dash"
/>
</svg>
</div>
<button x-bind="clearButton" @click="clear()" class="flex items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
class="h-5 w-5 opacity-70"
viewBox="0 0 16 16"
>
<path
d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z"
/>
</svg>
</button>
</div>
</div>
<table
x-data="table"
:data-items="$store.tableFilterPagination.data"
:data-items-per-page="$store.tableFilterPagination.itemsPerPage"
:data-primary-key="$store.tableFilterPagination.primaryKey"
:data-filter="$store.tableFilterPagination.filter"
:data-page="$store.tableFilterPagination.page"
@update:items-filtered="() => $store.tableFilterPagination.filteredItemsCount = $event.detail.length"
class="w-full table-auto border-collapse text-left text-[0.9rem]"
class-loading="opacity-50 pointer-events-none"
>
<thead>
<tr>
<template x-for="col in definition">
<td
x-bind="header"
class="border-b border-gray-300 px-2 py-2 font-semibold text-text-800 dark:border-dark-600 dark:text-text-300"
>
<div class="flex items-center">
<span x-text="col.label"></span>
<template x-if="isSortable() && !isSorted()">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
fill="currentColor"
class="ml-3 h-4 w-4 opacity-30"
>
<path
d="M137.4 41.4c12.5-12.5 32.8-12.5 45.3 0l128 128c9.2 9.2 11.9 22.9 6.9 34.9s-16.6 19.8-29.6 19.8L32 224c-12.9 0-24.6-7.8-29.6-19.8s-2.2-25.7 6.9-34.9l128-128zm0 429.3l-128-128c-9.2-9.2-11.9-22.9-6.9-34.9s16.6-19.8 29.6-19.8l256 0c12.9 0 24.6 7.8 29.6 19.8s2.2 25.7-6.9 34.9l-128 128c-12.5 12.5-32.8 12.5-45.3 0z"
/>
</svg>
</template>
<template x-if="isSortedAsc()">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
fill="currentColor"
class="ml-3 h-4 w-4 opacity-70"
>
<path
d="M182.6 137.4c-12.5-12.5-32.8-12.5-45.3 0l-128 128c-9.2 9.2-11.9 22.9-6.9 34.9s16.6 19.8 29.6 19.8l256 0c12.9 0 24.6-7.8 29.6-19.8s2.2-25.7-6.9-34.9l-128-128z"
/>
</svg>
</template>
<template x-if="isSortedDesc()">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
fill="currentColor"
class="ml-3 h-4 w-4 opacity-70"
>
<path
d="M137.4 374.6c12.5 12.5 32.8 12.5 45.3 0l128-128c9.2-9.2 11.9-22.9 6.9-34.9s-16.6-19.8-29.6-19.8L32 192c-12.9 0-24.6 7.8-29.6 19.8s-2.2 25.7 6.9 34.9l128 128z"
/>
</svg>
</template>
</div>
</td>
</template>
</tr>
</thead>
<tbody>
<template x-for="row in getDataPaginated" :key="row[primaryKey]">
<tr>
<template x-for="col in definition">
<td
x-text="getCellContent"
class="border-t border-gray-300 px-2 py-2 text-text-800 transition-colors duration-200 dark:border-dark-600 dark:text-text-300"
></td>
</template>
</tr>
</template>
</tbody>
</table>
<nav
x-data="pagination"
x-model="$store.tableFilterPagination.page"
:data-items-count="$store.tableFilterPagination.filteredItemsCount"
:data-items-per-page="$store.tableFilterPagination.itemsPerPage"
data-max-pages="7"
class="ml-auto flex w-auto gap-x-2"
>
<a
x-bind="prevButton"
class="flex w-12 cursor-pointer flex-col items-center justify-center rounded-sm border border-gray-300 p-2 text-sm font-medium hover:bg-secondary-50 dark:border-dark-600 dark:text-text-300 dark:hover:bg-dark-700"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
viewBox="0 0 16 16"
>
<path
fill-rule="evenodd"
d="M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0"
/>
</svg>
</a>
<template x-for="page in getPages">
<a
x-bind="pageButton"
x-text="page"
class="z-10 flex h-10 w-12 flex-none cursor-pointer items-center justify-center rounded-sm border text-sm font-semibold transition-shadow"
class-default="border-gray-300 hover:bg-secondary-50 dark:border-dark-600 dark:text-text-300 dark:hover:bg-dark-800"
class-selected="border-primary-200 bg-primary-500 font-semibold text-white ring-2 ring-primary-200 hover:bg-primary-400 dark:border-primary-200 dark:bg-primary-500 dark:text-text-100 dark:ring-primary-200 dark:hover:bg-primary-400"
>
</a>
</template>
<a
x-bind="nextButton"
class="flex w-12 cursor-pointer flex-col items-center justify-center rounded-sm border border-gray-300 p-2 text-sm font-medium hover:bg-secondary-50 dark:border-dark-600 dark:text-text-300 dark:hover:bg-dark-700"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
viewBox="0 0 16 16"
>
<path
fill-rule="evenodd"
d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708"
/>
</svg>
</a>
</nav>
</div>
document.addEventListener('alpine:init', () => {
Alpine.store('tableFilterPagination', {
data: [
{"id":1,"first_name":"Anthony","last_name":"Linbohm","city":"Makui","department":"Business Development","title":"Quality Engineer"},
{"id":2,"first_name":"Richard","last_name":"Moult","city":"Xihu","department":"Legal","title":"Budget/Accounting Analyst IV"},
{"id":3,"first_name":"Chance","last_name":"Dallas","city":"Moncton","department":"Support","title":"Product Engineer"},
{"id":4,"first_name":"Rozamond","last_name":"Abbatucci","city":"Chico","department":"Legal","title":"Software Consultant"},
{"id":5,"first_name":"Ashely","last_name":"Petrozzi","city":"Lafia","department":"Services","title":"Staff Accountant III"},
{"id":6,"first_name":"Bron","last_name":"Siuda","city":"Mora","department":"Accounting","title":"Marketing Manager"},
{"id":7,"first_name":"Marena","last_name":"Geraldi","city":"Karanganyar","department":"Support","title":"Compensation Analyst"},
{"id":8,"first_name":"Tomas","last_name":"Donneely","city":"Meirinhas","department":"Services","title":"Research Associate"},
{"id":9,"first_name":"Umberto","last_name":"Cohalan","city":"Cuamba","department":"Research and Development","title":"Chemical Engineer"},
{"id":10,"first_name":"Nicola","last_name":"Flippelli","city":"Faqīrwāli","department":"Human Resources","title":"VP Quality Control"},
{"id":11,"first_name":"Jemie","last_name":"McLafferty","city":"Lagoa de Albufeira","department":"Human Resources","title":"Junior Executive"},
{"id":12,"first_name":"Glen","last_name":"Edinborough","city":"Chicago","department":"Accounting","title":"Associate Professor"},
{"id":13,"first_name":"Malachi","last_name":"Broadbridge","city":"Az Zaytūnīyah","department":"Human Resources","title":"Paralegal"},
{"id":14,"first_name":"Yale","last_name":"Milnes","city":"Shuangjie","department":"Accounting","title":"Account Representative III"},
{"id":15,"first_name":"Galvin","last_name":"Morrill","city":"Gaocun","department":"Engineering","title":"Account Representative II"},
{"id":16,"first_name":"Cesar","last_name":"Pinnegar","city":"Drahichyn","department":"Marketing","title":"VP Sales"},
{"id":17,"first_name":"Harlan","last_name":"Aldin","city":"Hulan","department":"Legal","title":"Paralegal"},
{"id":18,"first_name":"Thadeus","last_name":"Tressler","city":"Indaial","department":"Services","title":"Product Engineer"},
{"id":19,"first_name":"Marjie","last_name":"Agiolfinger","city":"Tatarbunary","department":"Marketing","title":"Environmental Specialist"},
{"id":20,"first_name":"Amie","last_name":"Dupoy","city":"Thị Trấn Mường Khến","department":"Accounting","title":"Legal Assistant"},
{"id":21,"first_name":"Marylinda","last_name":"Kidson","city":"Ondoy","department":"Business Development","title":"Community Outreach Specialist"},
{"id":22,"first_name":"Karlen","last_name":"Capun","city":"Buda-Kashalyova","department":"Engineering","title":"Assistant Media Planner"},
{"id":23,"first_name":"Horatius","last_name":"Giovanizio","city":"Kham Sakae Saeng","department":"Marketing","title":"Director of Sales"},
{"id":24,"first_name":"Eleni","last_name":"Tale","city":"Montpellier","department":"Engineering","title":"Tax Accountant"},
{"id":25,"first_name":"Chester","last_name":"Theuss","city":"Panshan","department":"Legal","title":"Account Executive"},
{"id":26,"first_name":"Morey","last_name":"Demangel","city":"Stoney Ground","department":"Support","title":"Human Resources Manager"},
{"id":27,"first_name":"Tedda","last_name":"Rawlin","city":"Erfangping","department":"Sales","title":"Editor"},
{"id":28,"first_name":"Rennie","last_name":"Finnan","city":"Tuusula","department":"Human Resources","title":"Paralegal"},
{"id":29,"first_name":"Merry","last_name":"Wisedale","city":"Renxian","department":"Services","title":"Systems Administrator IV"},
{"id":30,"first_name":"Melodie","last_name":"Hayzer","city":"Hưng Nguyên","department":"Support","title":"Media Manager I"},
],
filter: '',
page: 1,
itemsPerPage: 10,
primaryKey: 'id',
filteredItemsCount: 0,
})
})
The data-is-loading
prop can be used, for example, when loading table data. Based on its value, the classes from the class-loading
attribute are added to or removed from the table. By default, the opacity-50
and pointer-events-none
classes are applied.
<div x-data="{ isLoadingTable: false }">
<button
x-data
@click="() => {
isLoadingTable = true
setTimeout(() => {
isLoadingTable = false
}, 3000)
}"
class="mx-auto block rounded-md border-violet-700 bg-violet-500 px-4 py-2 font-medium text-gray-100 hover:bg-violet-600 focus:ring-violet-200 dark:bg-violet-500 dark:hover:bg-violet-600 flex gap-x-2 items-center mb-4"
>
Refresh
<svg viewBox="25 25 50 50" fill="none" class="animate-spinner-rotate h-4 w-4" x-show="isLoadingTable">
<circle
cx="50"
cy="50"
r="20"
stroke="currentColor"
stroke-width="4"
stroke-miterlimit="10"
stroke-linecap="round"
class="animate-spinner-dash"
/>
</svg>
</button>
<table
x-data="table"
:data-items="$store.table.data"
:data-primary-key="$store.table.primaryKey"
:data-is-loading="isLoadingTable"
class="w-full table-auto border-collapse text-left text-[0.9rem]"
class-loading="opacity-50 pointer-events-none"
>
<thead>
<tr>
<template x-for="col in definition">
<td
x-bind="header"
class="border-b border-gray-300 px-2 py-2 font-semibold text-text-800 dark:border-dark-600 dark:text-text-300"
>
<div class="flex items-center">
<span x-text="col.label"></span>
<template x-if="isSortable() && !isSorted()">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
fill="currentColor"
class="ml-3 h-4 w-4 opacity-30"
>
<path
d="M137.4 41.4c12.5-12.5 32.8-12.5 45.3 0l128 128c9.2 9.2 11.9 22.9 6.9 34.9s-16.6 19.8-29.6 19.8L32 224c-12.9 0-24.6-7.8-29.6-19.8s-2.2-25.7 6.9-34.9l128-128zm0 429.3l-128-128c-9.2-9.2-11.9-22.9-6.9-34.9s16.6-19.8 29.6-19.8l256 0c12.9 0 24.6 7.8 29.6 19.8s2.2 25.7-6.9 34.9l-128 128c-12.5 12.5-32.8 12.5-45.3 0z"
/>
</svg>
</template>
<template x-if="isSortedAsc()">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
fill="currentColor"
class="ml-3 h-4 w-4 opacity-70"
>
<path
d="M182.6 137.4c-12.5-12.5-32.8-12.5-45.3 0l-128 128c-9.2 9.2-11.9 22.9-6.9 34.9s16.6 19.8 29.6 19.8l256 0c12.9 0 24.6-7.8 29.6-19.8s2.2-25.7-6.9-34.9l-128-128z"
/>
</svg>
</template>
<template x-if="isSortedDesc()">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
fill="currentColor"
class="ml-3 h-4 w-4 opacity-70"
>
<path
d="M137.4 374.6c12.5 12.5 32.8 12.5 45.3 0l128-128c9.2-9.2 11.9-22.9 6.9-34.9s-16.6-19.8-29.6-19.8L32 192c-12.9 0-24.6 7.8-29.6 19.8s-2.2 25.7 6.9 34.9l128 128z"
/>
</svg>
</template>
</div>
</td>
</template>
</tr>
</thead>
<tbody>
<template x-for="row in getDataPaginated" :key="row[primaryKey]">
<tr>
<template x-for="col in definition">
<td
x-text="getCellContent"
class="border-t border-gray-300 px-2 py-2 text-text-800 transition-colors duration-200 dark:border-dark-600 dark:text-text-300"
></td>
</template>
</tr>
</template>
</tbody>
</table>
</div>
document.addEventListener('alpine:init', () => {
Alpine.store('table', {
data: [
{"id":1,"first_name":"Anthony","last_name":"Linbohm","city":"Makui","department":"Business Development","title":"Quality Engineer"},
{"id":2,"first_name":"Richard","last_name":"Moult","city":"Xihu","department":"Legal","title":"Budget/Accounting Analyst IV"},
{"id":3,"first_name":"Chance","last_name":"Dallas","city":"Moncton","department":"Support","title":"Product Engineer"},
{"id":4,"first_name":"Rozamond","last_name":"Abbatucci","city":"Chico","department":"Legal","title":"Software Consultant"},
{"id":5,"first_name":"Ashely","last_name":"Petrozzi","city":"Lafia","department":"Services","title":"Staff Accountant III"},
{"id":6,"first_name":"Bron","last_name":"Siuda","city":"Mora","department":"Accounting","title":"Marketing Manager"},
{"id":7,"first_name":"Marena","last_name":"Geraldi","city":"Karanganyar","department":"Support","title":"Compensation Analyst"},
{"id":8,"first_name":"Tomas","last_name":"Donneely","city":"Meirinhas","department":"Services","title":"Research Associate"},
],
primaryKey: 'id',
})
})
By default, the filtered portion of the cell content is not highlighted, as this requires the x-html
directive. You can enable it by simply replacing x-text
with x-html
in the td
element.
<td x-html="getHighlightedCellContent"></td>
|
<div class="flex flex-col gap-y-4">
<div
x-data="input"
x-model="$store.tableFilterPagination.filter"
class="flex flex-1 items-center rounded-sm border px-3 py-2 outline-hidden transition-shadow duration-200 focus-within:ring-3 focus:outline-hidden"
class-default="border-gray-300 bg-white focus-within:border-gray-400 focus-within:ring-primary-200 dark:border-dark-600 dark:bg-dark-800 dark:text-text-300 dark:focus-within:ring-primary-300"
class-valid="border-success-300 bg-white text-success-600 focus-within:ring-success-200 dark:border-success-400 dark:bg-dark-800 dark:text-success-600 dark:focus-within:ring-success-300"
class-invalid="border-danger-300 bg-white text-danger-600 focus-within:ring-danger-200 dark:border-danger-400 dark:bg-dark-800 dark:text-danger-600 dark:focus-within:ring-danger-300"
>
<div data-icon class="mr-3 empty:hidden"></div>
<div data-prepend class="mr-3 empty:hidden"></div>
<div class="mr-3 flex flex-1 flex-wrap">
<input
x-bind="input"
type="text"
class="w-full min-w-0 flex-1 border-0 bg-transparent p-0 outline-hidden focus:min-w-[64px] focus:outline-hidden"
/>
</div>
<div data-append class="mr-3 empty:hidden"></div>
<div class="flex items-center gap-x-2">
<div x-bind="loader">
<svg
viewBox="25 25 50 50"
fill="none"
class="h-5 w-5 animate-spinner-rotate"
>
<circle
cx="50"
cy="50"
r="20"
stroke="currentColor"
stroke-width="4"
stroke-miterlimit="10"
stroke-linecap="round"
class="animate-spinner-dash"
/>
</svg>
</div>
<button x-bind="clearButton" @click="clear()" class="flex items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
class="h-5 w-5 opacity-70"
viewBox="0 0 16 16"
>
<path
d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z"
/>
</svg>
</button>
</div>
</div>
<table
x-data="table"
:data-items="$store.tableFilterPagination.data"
:data-items-per-page="$store.tableFilterPagination.itemsPerPage"
:data-primary-key="$store.tableFilterPagination.primaryKey"
:data-filter="$store.tableFilterPagination.filter"
:data-page="$store.tableFilterPagination.page"
@update:items-filtered="() => $store.tableFilterPagination.filteredItemsCount = $event.detail.length"
class="w-full table-auto border-collapse text-left text-[0.9rem]"
class-loading="opacity-50 pointer-events-none"
>
<thead>
<tr>
<template x-for="col in definition">
<td
x-bind="header"
class="border-b border-gray-300 px-2 py-2 font-semibold text-text-800 dark:border-dark-600 dark:text-text-300"
>
<div class="flex items-center">
<span x-text="col.label"></span>
<template x-if="isSortable() && !isSorted()">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
fill="currentColor"
class="ml-3 h-4 w-4 opacity-30"
>
<path
d="M137.4 41.4c12.5-12.5 32.8-12.5 45.3 0l128 128c9.2 9.2 11.9 22.9 6.9 34.9s-16.6 19.8-29.6 19.8L32 224c-12.9 0-24.6-7.8-29.6-19.8s-2.2-25.7 6.9-34.9l128-128zm0 429.3l-128-128c-9.2-9.2-11.9-22.9-6.9-34.9s16.6-19.8 29.6-19.8l256 0c12.9 0 24.6 7.8 29.6 19.8s2.2 25.7-6.9 34.9l-128 128c-12.5 12.5-32.8 12.5-45.3 0z"
/>
</svg>
</template>
<template x-if="isSortedAsc()">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
fill="currentColor"
class="ml-3 h-4 w-4 opacity-70"
>
<path
d="M182.6 137.4c-12.5-12.5-32.8-12.5-45.3 0l-128 128c-9.2 9.2-11.9 22.9-6.9 34.9s16.6 19.8 29.6 19.8l256 0c12.9 0 24.6-7.8 29.6-19.8s2.2-25.7-6.9-34.9l-128-128z"
/>
</svg>
</template>
<template x-if="isSortedDesc()">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
fill="currentColor"
class="ml-3 h-4 w-4 opacity-70"
>
<path
d="M137.4 374.6c12.5 12.5 32.8 12.5 45.3 0l128-128c9.2-9.2 11.9-22.9 6.9-34.9s-16.6-19.8-29.6-19.8L32 192c-12.9 0-24.6 7.8-29.6 19.8s-2.2 25.7 6.9 34.9l128 128z"
/>
</svg>
</template>
</div>
</td>
</template>
</tr>
</thead>
<tbody>
<template x-for="row in getDataPaginated" :key="row[primaryKey]">
<tr>
<template x-for="col in definition">
<td
x-html="getHighlightedCellContent"
class="border-t border-gray-300 px-2 py-2 text-text-800 transition-colors duration-200 dark:border-dark-600 dark:text-text-300"
></td>
</template>
</tr>
</template>
</tbody>
</table>
<nav
x-data="pagination"
x-model="$store.tableFilterPagination.page"
:data-items-count="$store.tableFilterPagination.filteredItemsCount"
:data-items-per-page="$store.tableFilterPagination.itemsPerPage"
data-max-pages="7"
class="ml-auto flex w-auto gap-x-2"
>
<a
x-bind="prevButton"
class="flex w-12 cursor-pointer flex-col items-center justify-center rounded-sm border border-gray-300 p-2 text-sm font-medium hover:bg-secondary-50 dark:border-dark-600 dark:text-text-300 dark:hover:bg-dark-700"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
viewBox="0 0 16 16"
>
<path
fill-rule="evenodd"
d="M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0"
/>
</svg>
</a>
<template x-for="page in getPages">
<a
x-bind="pageButton"
x-text="page"
class="z-10 flex h-10 w-12 flex-none cursor-pointer items-center justify-center rounded-sm border text-sm font-semibold transition-shadow"
class-default="border-gray-300 hover:bg-secondary-50 dark:border-dark-600 dark:text-text-300 dark:hover:bg-dark-800"
class-selected="border-primary-200 bg-primary-500 font-semibold text-white ring-2 ring-primary-200 hover:bg-primary-400 dark:border-primary-200 dark:bg-primary-500 dark:text-text-100 dark:ring-primary-200 dark:hover:bg-primary-400"
>
</a>
</template>
<a
x-bind="nextButton"
class="flex w-12 cursor-pointer flex-col items-center justify-center rounded-sm border border-gray-300 p-2 text-sm font-medium hover:bg-secondary-50 dark:border-dark-600 dark:text-text-300 dark:hover:bg-dark-700"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
viewBox="0 0 16 16"
>
<path
fill-rule="evenodd"
d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708"
/>
</svg>
</a>
</nav>
</div>
document.addEventListener('alpine:init', () => {
Alpine.store('tableFilterPagination', {
data: [
{"id":1,"first_name":"Anthony","last_name":"Linbohm","city":"Makui","department":"Business Development","title":"Quality Engineer"},
{"id":2,"first_name":"Richard","last_name":"Moult","city":"Xihu","department":"Legal","title":"Budget/Accounting Analyst IV"},
{"id":3,"first_name":"Chance","last_name":"Dallas","city":"Moncton","department":"Support","title":"Product Engineer"},
{"id":4,"first_name":"Rozamond","last_name":"Abbatucci","city":"Chico","department":"Legal","title":"Software Consultant"},
{"id":5,"first_name":"Ashely","last_name":"Petrozzi","city":"Lafia","department":"Services","title":"Staff Accountant III"},
{"id":6,"first_name":"Bron","last_name":"Siuda","city":"Mora","department":"Accounting","title":"Marketing Manager"},
{"id":7,"first_name":"Marena","last_name":"Geraldi","city":"Karanganyar","department":"Support","title":"Compensation Analyst"},
{"id":8,"first_name":"Tomas","last_name":"Donneely","city":"Meirinhas","department":"Services","title":"Research Associate"},
{"id":9,"first_name":"Umberto","last_name":"Cohalan","city":"Cuamba","department":"Research and Development","title":"Chemical Engineer"},
{"id":10,"first_name":"Nicola","last_name":"Flippelli","city":"Faqīrwāli","department":"Human Resources","title":"VP Quality Control"},
{"id":11,"first_name":"Jemie","last_name":"McLafferty","city":"Lagoa de Albufeira","department":"Human Resources","title":"Junior Executive"},
{"id":12,"first_name":"Glen","last_name":"Edinborough","city":"Chicago","department":"Accounting","title":"Associate Professor"},
{"id":13,"first_name":"Malachi","last_name":"Broadbridge","city":"Az Zaytūnīyah","department":"Human Resources","title":"Paralegal"},
{"id":14,"first_name":"Yale","last_name":"Milnes","city":"Shuangjie","department":"Accounting","title":"Account Representative III"},
{"id":15,"first_name":"Galvin","last_name":"Morrill","city":"Gaocun","department":"Engineering","title":"Account Representative II"},
{"id":16,"first_name":"Cesar","last_name":"Pinnegar","city":"Drahichyn","department":"Marketing","title":"VP Sales"},
{"id":17,"first_name":"Harlan","last_name":"Aldin","city":"Hulan","department":"Legal","title":"Paralegal"},
{"id":18,"first_name":"Thadeus","last_name":"Tressler","city":"Indaial","department":"Services","title":"Product Engineer"},
{"id":19,"first_name":"Marjie","last_name":"Agiolfinger","city":"Tatarbunary","department":"Marketing","title":"Environmental Specialist"},
{"id":20,"first_name":"Amie","last_name":"Dupoy","city":"Thị Trấn Mường Khến","department":"Accounting","title":"Legal Assistant"},
{"id":21,"first_name":"Marylinda","last_name":"Kidson","city":"Ondoy","department":"Business Development","title":"Community Outreach Specialist"},
{"id":22,"first_name":"Karlen","last_name":"Capun","city":"Buda-Kashalyova","department":"Engineering","title":"Assistant Media Planner"},
{"id":23,"first_name":"Horatius","last_name":"Giovanizio","city":"Kham Sakae Saeng","department":"Marketing","title":"Director of Sales"},
{"id":24,"first_name":"Eleni","last_name":"Tale","city":"Montpellier","department":"Engineering","title":"Tax Accountant"},
{"id":25,"first_name":"Chester","last_name":"Theuss","city":"Panshan","department":"Legal","title":"Account Executive"},
{"id":26,"first_name":"Morey","last_name":"Demangel","city":"Stoney Ground","department":"Support","title":"Human Resources Manager"},
{"id":27,"first_name":"Tedda","last_name":"Rawlin","city":"Erfangping","department":"Sales","title":"Editor"},
{"id":28,"first_name":"Rennie","last_name":"Finnan","city":"Tuusula","department":"Human Resources","title":"Paralegal"},
{"id":29,"first_name":"Merry","last_name":"Wisedale","city":"Renxian","department":"Services","title":"Systems Administrator IV"},
{"id":30,"first_name":"Melodie","last_name":"Hayzer","city":"Hưng Nguyên","department":"Support","title":"Media Manager I"},
],
filter: '',
page: 1,
itemsPerPage: 10,
primaryKey: 'id',
filteredItemsCount: 0,
})
})