Skip to main content
WireKit
Copy for LLM

Data Table

The single most-demanded surface in any app — orders, tickets, contacts, users, records — is one thing: a sortable, searchable, bulk-selectable table. The data-table wrapper handles the 80% case entirely client-side: hand it a rows array and a columns definition and it sorts, searches, selects, toggles column visibility, and switches density with no backend round-trip. For 10k+ rows you drive the same UI from Livewire via the server contract.

Basic Usage

Define columns (each {key, label}) and pass rows. Headers are sortable by default — click to sort, click again to reverse.

A sortable data table
Customers

No results

Status Badges

A cellType: 'badge' column renders a tinted intent pill — common status words (paid, pending, failed, …) map to success / warning / danger automatically.

Orders with status badges
Orders

No results

Selection & Bulk Actions

Add selectable for per-row checkboxes plus a tri-state select-all. When rows are selected, a bulk-action bar appears — fill it with the bulkActions slot.

Selectable rows with a bulk-action bar
selected
Subscribers

No results

Search, Density & Column Manager

searchable adds a client-side filter box, the density toggle switches row padding, and column-manager lets the user show or hide columns.

Toolbar with search, density, and column manager
Users

No results

Row Actions

The rowActions slot adds a trailing actions column. Compose a dropdown or buttons; the slot renders once per row.

A row-actions column
Invoices
Actions

No results

Server Mode (Large Datasets)

For datasets too large to ship to the browser, add the server flag and drive sort / search / selection from Livewire. The component stops sorting and filtering locally and instead emits sort-change, search-change, and selection-change events for you to handle:

{{-- 1. Server mode: the component renders the current page; you re-query --}}
<x-wirekit::data-table
    server
    selectable
    name="selectedIds"
    :columns="$columns"
    :rows="$this->rows()->items()"
    x-on:sort-change="$wire.set('sort', $event.detail)"
    x-on:search-change="$wire.set('search', $event.detail.value)"
/>

{{-- 2. Compose pagination below it --}}
{{ $this->rows()->links() }}
// 3. A reusable trait carries the server-side state contract
trait WithDataTable
{
    public array $sort = ['key' => null, 'dir' => 'asc'];
    public string $search = '';
    public array $selectedIds = [];

    public function rows(): \Illuminate\Contracts\Pagination\LengthAwarePaginator
    {
        return Order::query()
            ->when($this->search, fn ($q) => $q->where('ref', 'like', "%{$this->search}%"))
            ->when($this->sort['key'], fn ($q) => $q->orderBy($this->sort['key'], $this->sort['dir']))
            ->paginate(25);
    }
}

The selection-change event (and the hidden input the name prop creates) carry the selected id list, so wire:model="selectedIds" stays in sync for bulk operations.

Column Definition

Key Required Description
key yes The row field to read
label yes The header text
sortable no false disables sorting for the column
align no left (default) · center · right
cellType no text (default) · number (right tabular) · badge

Props

Prop Type Default Description
rows array [] Row objects
columns array [] Column definitions (see above)
rowKey string 'id' Unique id field (selection + morph keying)
selectable bool false Per-row + select-all checkboxes
searchable bool false Toolbar search box
density string 'comfortable' comfortable · compact
columnManager bool false Show/hide-columns dropdown
hidden array [] Initially-hidden column keys
server bool false Server-driven — stop local sort/filter, emit events only
searchPlaceholder string 'Search…' Search box placeholder
emptyText string 'No results' Empty-state message
caption string|null null Accessible table caption / name
name string|null null Hidden-input name mirroring selected ids
scope string|null null Scoped personalization name

Accessibility

  • The table sits in a labeled role="region" with tabindex="0" and a focus ring, so the horizontally-scrolling grid is keyboard-reachable (WCAG 2.1.1).
  • Sortable headers are buttons carrying aria-sort (ascending / descending / none); the sort direction is shown with a caret glyph, not color.
  • The select-all checkbox is tri-state (indeterminate when a subset is selected); the selection count is announced via an aria-live region.
  • A caption becomes the table's accessible name.

Keyboard Interaction

Key Action
Tab Move through the toolbar, headers, checkboxes, and row actions
Enter / Space Sort a column, toggle a checkbox, or trigger an action

Design Tokens

Element Token
Header text --color-wk-text-muted
Row hover --color-wk-bg-subtle
Selected row --color-wk-bg-muted
Bulk-action bar --color-wk-bg-muted
Checkbox accent --color-wk-accent
Badge (success/warning/danger) --color-wk-success / --color-wk-warning / --color-wk-danger
Focus ring --color-wk-ring

Customization

Override the defaults via config/wirekit.php:

'components' => [
    'data-table' => [
        'density' => 'compact',
        'selectable' => true,
        'searchable' => true,
    ],
],

Further Reading