Components
Data Table
An interactive table with search, sortable columns, row selection and pagination.
| Select | ||||
|---|---|---|---|---|
| No results. | ||||
of row(s) selected.
@php($rows = [
['name' => 'Olivia Martin', 'email' => '[email protected]', 'role' => 'Owner', 'amount' => '$1,200'],
['name' => 'Jackson Lee', 'email' => '[email protected]', 'role' => 'Member', 'amount' => '$840'],
['name' => 'Isabella Nguyen', 'email' => '[email protected]', 'role' => 'Member', 'amount' => '$2,100'],
['name' => 'William Kim', 'email' => '[email protected]', 'role' => 'Admin', 'amount' => '$640'],
['name' => 'Sofia Davis', 'email' => '[email protected]', 'role' => 'Member', 'amount' => '$1,950'],
['name' => 'Liam Johnson', 'email' => '[email protected]', 'role' => 'Member', 'amount' => '$320'],
['name' => 'Emma Brown', 'email' => '[email protected]', 'role' => 'Admin', 'amount' => '$5,400'],
['name' => 'Noah Wilson', 'email' => '[email protected]', 'role' => 'Member', 'amount' => '$1,100'],
['name' => 'Ava Garcia', 'email' => '[email protected]', 'role' => 'Member', 'amount' => '$760'],
['name' => 'Mason Lopez', 'email' => '[email protected]', 'role' => 'Owner', 'amount' => '$3,300'],
['name' => 'Mia Hernandez', 'email' => '[email protected]', 'role' => 'Member', 'amount' => '$220'],
['name' => 'Lucas Young', 'email' => '[email protected]', 'role' => 'Member', 'amount' => '$1,480'],
])
<x-ui.data-table
class="w-full max-w-2xl"
:columns="[
['key' => 'name', 'label' => 'Name'],
['key' => 'email', 'label' => 'Email'],
['key' => 'role', 'label' => 'Role'],
['key' => 'amount', 'label' => 'Amount', 'class' => 'text-right'],
]"
:rows="$rows"
search-key="name"
search-placeholder="Filter names..."
:page-size="5"
/>
Full
| Select | Actions | ||||
|---|---|---|---|---|---|
|
|
|||||
| No results. | |||||
of row(s) selected.
@php($rows = [
['id' => 1, 'name' => 'Olivia Martin', 'email' => '[email protected]', 'role' => 'Owner', 'amount' => '$1,200'],
['id' => 2, 'name' => 'Jackson Lee', 'email' => '[email protected]', 'role' => 'Member', 'amount' => '$840'],
['id' => 3, 'name' => 'Isabella Nguyen', 'email' => '[email protected]', 'role' => 'Member', 'amount' => '$2,100'],
['id' => 4, 'name' => 'William Kim', 'email' => '[email protected]', 'role' => 'Admin', 'amount' => '$640'],
['id' => 5, 'name' => 'Sofia Davis', 'email' => '[email protected]', 'role' => 'Member', 'amount' => '$1,950'],
['id' => 6, 'name' => 'Liam Johnson', 'email' => '[email protected]', 'role' => 'Member', 'amount' => '$320'],
])
{{-- Selection + search + sortable columns + row actions together — the full interactive table. --}}
<x-ui.data-table
class="w-full max-w-2xl"
row-key="id"
search-placeholder="Filter members..."
:columns="[
['key' => 'name', 'label' => 'Name'],
['key' => 'email', 'label' => 'Email'],
['key' => 'role', 'label' => 'Role'],
['key' => 'amount', 'label' => 'Amount', 'class' => 'text-right'],
]"
:rows="$rows"
:page-size="5"
>
<x-slot:actions>
<x-ui.button size="sm" variant="ghost" x-on:click="$wire.edit(item.r.id)" aria-label="Edit row">
<x-lucide-pencil class="size-4" aria-hidden="true" />
<span class="sr-only sm:not-sr-only">Edit</span>
</x-ui.button>
<x-ui.button size="sm" variant="ghost" class="text-destructive hover:text-destructive" x-on:click="$wire.delete(item.r.id)" aria-label="Delete row">
<x-lucide-trash-2 class="size-4" aria-hidden="true" />
<span class="sr-only sm:not-sr-only">Delete</span>
</x-ui.button>
</x-slot:actions>
</x-ui.data-table>
Global Search
| No results. | ||
result(s).
@php($rows = [
['name' => 'Olivia Martin', 'email' => '[email protected]', 'role' => 'Owner'],
['name' => 'Jackson Lee', 'email' => '[email protected]', 'role' => 'Member'],
['name' => 'Isabella Nguyen', 'email' => '[email protected]', 'role' => 'Member'],
['name' => 'William Kim', 'email' => '[email protected]', 'role' => 'Admin'],
['name' => 'Sofia Davis', 'email' => '[email protected]', 'role' => 'Member'],
])
{{--
Omit search-key and the filter matches across every column — try typing "admin" or a name.
Filtering runs entirely client-side in Alpine.
--}}
<x-ui.data-table
class="w-full max-w-2xl"
:selectable="false"
search-placeholder="Search name, email or role..."
:columns="[
['key' => 'name', 'label' => 'Name'],
['key' => 'email', 'label' => 'Email'],
['key' => 'role', 'label' => 'Role'],
]"
:rows="$rows"
/>
No Selection
| No results. | |||
result(s).
@php($rows = [
['name' => 'Olivia Martin', 'email' => '[email protected]', 'role' => 'Owner', 'amount' => '$1,200'],
['name' => 'Jackson Lee', 'email' => '[email protected]', 'role' => 'Member', 'amount' => '$840'],
['name' => 'Isabella Nguyen', 'email' => '[email protected]', 'role' => 'Member', 'amount' => '$2,100'],
['name' => 'William Kim', 'email' => '[email protected]', 'role' => 'Admin', 'amount' => '$640'],
['name' => 'Sofia Davis', 'email' => '[email protected]', 'role' => 'Member', 'amount' => '$1,950'],
])
{{-- selectable="false" drops the checkbox column; the footer then reports the result count. --}}
<x-ui.data-table
class="w-full max-w-2xl"
:selectable="false"
:columns="[
['key' => 'name', 'label' => 'Name'],
['key' => 'email', 'label' => 'Email'],
['key' => 'role', 'label' => 'Role'],
['key' => 'amount', 'label' => 'Amount', 'class' => 'text-right'],
]"
:rows="$rows"
:page-size="5"
/>
Row Actions
| Actions | |||
|---|---|---|---|
|
|
|||
| No results. | |||
result(s).
@php($rows = [
['id' => 1, 'name' => 'Olivia Martin', 'email' => '[email protected]', 'role' => 'Owner'],
['id' => 2, 'name' => 'Jackson Lee', 'email' => '[email protected]', 'role' => 'Member'],
['id' => 3, 'name' => 'Isabella Nguyen', 'email' => '[email protected]', 'role' => 'Member'],
['id' => 4, 'name' => 'William Kim', 'email' => '[email protected]', 'role' => 'Admin'],
['id' => 5, 'name' => 'Sofia Davis', 'email' => '[email protected]', 'role' => 'Member'],
])
{{--
The `actions` slot renders inside the client-side x-for, so the current row is `item.r`.
Bind to Livewire with $wire (shown below). In this static preview there is no Livewire
runtime, so the clicks are inert — copy this into a Livewire component to wire them up.
--}}
<x-ui.data-table
class="w-full max-w-2xl"
:columns="[
['key' => 'name', 'label' => 'Name'],
['key' => 'email', 'label' => 'Email'],
['key' => 'role', 'label' => 'Role'],
]"
:rows="$rows"
row-key="id"
:selectable="false"
:searchable="false"
:page-size="5"
>
<x-slot:actions>
<x-ui.button size="sm" variant="ghost" x-on:click="$wire.edit(item.r.id)" aria-label="Edit row">
<x-lucide-pencil class="size-4" aria-hidden="true" />
<span class="sr-only sm:not-sr-only">Edit</span>
</x-ui.button>
<x-ui.button size="sm" variant="ghost" class="text-destructive hover:text-destructive" x-on:click="$wire.delete(item.r.id)" aria-label="Delete row">
<x-lucide-trash-2 class="size-4" aria-hidden="true" />
<span class="sr-only sm:not-sr-only">Delete</span>
</x-ui.button>
</x-slot:actions>
</x-ui.data-table>
Sticky Actions
| Actions | |||||
|---|---|---|---|---|---|
|
|
|||||
| No results. | |||||
result(s).
@php($rows = [
['id' => 1, 'name' => 'Olivia Martin', 'email' => '[email protected]', 'role' => 'Owner', 'team' => 'Design', 'location' => 'Lisbon'],
['id' => 2, 'name' => 'Jackson Lee', 'email' => '[email protected]', 'role' => 'Member', 'team' => 'Engineering', 'location' => 'Berlin'],
['id' => 3, 'name' => 'Isabella Nguyen', 'email' => '[email protected]', 'role' => 'Member', 'team' => 'Marketing', 'location' => 'Toronto'],
])
{{-- stickyActions freezes the actions column to the right edge while the table scrolls sideways. --}}
<x-ui.data-table
class="w-full max-w-2xl"
row-key="id"
:selectable="false"
:searchable="false"
sticky-actions
:columns="[
['key' => 'name', 'label' => 'Name'],
['key' => 'email', 'label' => 'Email'],
['key' => 'role', 'label' => 'Role'],
['key' => 'team', 'label' => 'Team'],
['key' => 'location', 'label' => 'Location'],
]"
:rows="$rows"
>
<x-slot:actions>
<x-ui.button size="sm" variant="ghost" x-on:click="$wire.edit(item.r.id)" aria-label="Edit row">
<x-lucide-pencil class="size-4" aria-hidden="true" />
</x-ui.button>
<x-ui.button size="sm" variant="ghost" class="text-destructive hover:text-destructive" x-on:click="$wire.delete(item.r.id)" aria-label="Delete row">
<x-lucide-trash-2 class="size-4" aria-hidden="true" />
</x-ui.button>
</x-slot:actions>
</x-ui.data-table>
API Reference
Props, slots and exposed methods for
<x-ui.data-table>.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
columns* |
array
|
[]
|
The column definitions. The order here sets the column order, and each column reads its value from the matching key in every row. See Each column below. |
rows* |
array
|
[]
|
The data rows as an array of associative arrays. Each row is keyed by the column keys (e.g. ["name" => "...", "email" => "..."]). Filtering, sorting and pagination all run client-side in Alpine. |
searchable |
bool
|
true
|
Show the search input above the table. |
searchKey |
string
|
— | Restrict searching to a single column key. When omitted, the search matches across all column keys. |
searchPlaceholder |
string
|
'Search...'
|
Placeholder text for the search input. |
selectable |
bool
|
true
|
Show row checkboxes plus a select-all checkbox in the header, and report the selected count in the footer. |
pageSize |
int
|
5
|
Number of rows shown per page. |
rowKey |
string
|
'id'
|
The row-data key used as a stable :key for each rendered row, and the identifier you read in the actions slot (item.r[rowKey]). Point it at your primary key so re-sorts and Livewire updates keep row identity. |
actionsLabel |
string
|
'Actions'
|
Accessible header label for the row-actions column. Rendered sr-only so the column reads correctly to screen readers without a visible header. |
stickyActions |
bool
|
false
|
Freeze the actions column to the right edge so it stays visible while the table scrolls horizontally on small screens. |
* Required.
Each column
| Prop | Type | Default | Description |
|---|---|---|---|
key* |
string
|
— | The row-data key this column reads its cell value from. |
label |
string
|
— | The header text. Defaults to the capitalized key. |
sortable |
bool
|
true
|
Whether clicking the header sorts by this column (numeric when both values parse as numbers, otherwise locale string compare). |
class |
string
|
— | Extra CSS classes applied to this column's header and cells, e.g. "text-right". |
Slots
| Slot | Description |
|---|---|
actions |
Optional per-row action buttons. Rendered inside the Alpine x-for, so the current row is available as item.r (the row data) and item.i (its index). Bind to Livewire with $wire, e.g. x-on:click="$wire.edit(item.r.id)". Because rendering is client-side, use Alpine bindings here — not server-side {{ $row->id }} interpolation. For a fully server-rendered table where wire:click="edit($row->id)" works natively, use server-table. |