BladewindUI: Data Grid Component

Data Grid

Data Grid is a higher-level companion to Table: an accessible data grid with column sorting, searching, row selection, sticky headers, and both client-side and server-driven state, all built in rather than assembled by hand. It renders a native <table>, so every interaction, sorting, selecting, paging, searching, happens through real, independently keyboard-operable controls rather than a hand-rolled widget.

ORD-1041 Kofi Addo Paid $61.97
ORD-1042 Akosua Owusu Paid $98.94
ORD-1043 Ama Mensah Paid $135.91
ORD-1044 Yaw Boateng Pending $172.88
ORD-1045 Efua Asante Paid $29.85
ORD-1046 Kwame Nkrumah Jr. Refunded $66.82
ORD-1047 Abena Darko Pending $103.79
ORD-1048 Kwabena Osei Paid $140.76
ORD-1049 Adjoa Frimpong Paid $177.73
ORD-1050 Kojo Asante Pending $34.70
Showing 1–10 of 34
Page 1 of 4
<x-bladewind::data-grid
    name="orders-grid"
    label="Orders"
    searchable="true"
    selectable="true"
    sortable="true"
    paginated="true"
    page-size="10"
    :columns="[
        ['key' => 'reference', 'label' => 'Reference', 'sortable' => true],
        ['key' => 'customer', 'label' => 'Customer', 'sortable' => true],
        ['key' => 'status', 'label' => 'Status', 'align' => 'center', 'format' => $statusPill],
        ['key' => 'total', 'label' => 'Total', 'align' => 'right', 'sortable' => true,
            'format' => fn ($value) => '$'.number_format($value / 100, 2)],
    ]"
    :rows="$orders" />

The example above uses 34 orders, enough for four real pages at the default page size, so paging, sorting, and searching all have something genuine to work against instead of a handful of rows that fit on one screen anyway.

Columns and Rows

Each column accepts key, label, align, width, sortable, class, and two callbacks: format($value, $row) for display, and sort($value, $row) for when the sortable value should differ from the displayed one. rows is an array of associative arrays or objects; a row's identity comes from row-key, which defaults to id.

Shorthand Column Syntax

Writing out ['key' => 'name', 'label' => 'Name'] for every column is tedious when you just want the label auto-generated from the key. Pass a plain array of key strings instead, and the grid title-cases each key and swaps underscores for spaces to build the label.

Shorthand columns skip the format and sort callbacks entirely, so cells render whatever raw value the field holds. That is fine for text and status fields, and the reason the example below leaves total out: an unformatted amount in cents is not something you would want to ship.

<x-bladewind::data-grid name="short-columns" label="Reviewers"
    :columns="['reference', 'customer', 'status']"
    :rows="$orders" />
reference customer status
ORD-1041 Kofi Addo paid
ORD-1042 Akosua Owusu paid
ORD-1043 Ama Mensah paid
ORD-1044 Yaw Boateng pending

You can also pass an associative array of key => label pairs when you only need to rename a column, without the full array shape.

<x-bladewind::data-grid name="aliased-columns" label="Reviewers"
    :columns="['reference' => 'Order #', 'customer' => 'Placed By', 'status' => 'State']"
    :rows="$orders" />
Order # Placed By State
ORD-1041 Kofi Addo paid
ORD-1042 Akosua Owusu paid
ORD-1043 Ama Mensah paid
ORD-1044 Yaw Boateng pending

Formatting a Column

format($value, $row) receives the raw cell value and the full row, and its return value is rendered as raw HTML rather than escaped text. That means a format callback can return a styled badge, an icon, or a link, not just a plain string. The Status column in the orders grid above uses exactly this to render a colour-coded pill:

$statusColors = [
    'paid' => 'bg-emerald-100 text-emerald-700',
    'pending' => 'bg-amber-100 text-amber-700',
    'refunded' => 'bg-slate-200 text-slate-600',
];

$columns = [
    // ...
    [
        'key' => 'status',
        'label' => 'Status',
        'align' => 'center',
        'format' => function ($value) use ($statusColors) {
            $class = $statusColors[$value] ?? $statusColors['pending'];
            return '<span class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium '.$class.'">'
                .ucfirst($value).'</span>';
        },
    ],
];

Custom Sort Values

Sorting compares the raw cell value by default, before format runs. That is correct for the currency column above, since total is already an integer number of cents. It breaks down for a column whose sortable order should not match either the raw value or the formatted text, a date stored as a display string, or a status that should sort by severity rather than alphabetically. Give the column its own sort($value, $row) callback to override just the comparison value.

<x-bladewind::data-grid name="status-priority" label="Orders by priority" sortable="true"
    :columns="[
        ['key' => 'reference', 'label' => 'Reference'],
        ['key' => 'status', 'label' => 'Status', 'align' => 'center',
            'format' => $statusPill,
            'sort' => fn ($value) => ['refunded' => 0, 'pending' => 1, 'paid' => 2][$value] ?? 1],
    ]"
    :rows="$orders" />
ORD-1041 Kofi Addo Paid
ORD-1042 Akosua Owusu Paid
ORD-1043 Ama Mensah Paid
ORD-1044 Yaw Boateng Pending
ORD-1045 Efua Asante Paid
ORD-1046 Kwame Nkrumah Jr. Refunded
ORD-1047 Abena Darko Pending
ORD-1048 Kwabena Osei Paid
Showing 1–8 of 34
Page 1 of 5

Click the Status header above: refunded orders sort first, then pending, then paid, even though the visible text is a badge rather than the plain word the sort actually compares.

Row Identity

Every row needs a stable, unique key so selection, sorting, and pagination can track it across re-renders. By default the grid reads id off each row. Set row-key when your data's identifier is called something else, an order reference, a UUID column, a database primary key with a different name.

<x-bladewind::data-grid name="by-reference" label="Orders" row-key="reference"
    :columns="$orderColumns" :rows="$orders" />

Sorting

Set sortable="true" on the grid to make every column sortable, or set sortable per column, as the Reference and Customer columns do in the first example on this page. Clicking a header cycles none, ascending, descending, none again.

client-sort defaults to true and reorders rows in the browser. Set it to false for a server-driven grid: clicking a header only updates the arrow indicator and emits bladewind:data-grid:sort-change, leaving the actual reordering to the application.

Sorting on Load

Pass sort-key and sort-direction to render the grid already sorted, useful for a grid that should default to showing the newest or highest-value rows first without the user having to click anything.

<x-bladewind::data-grid name="highest-value-first" label="Orders by value"
    sortable="true" sort-key="total" sort-direction="desc"
    :columns="$orderColumns" :rows="$orders" />
ORD-1041 Kofi Addo Paid $61.97
ORD-1042 Akosua Owusu Paid $98.94
ORD-1043 Ama Mensah Paid $135.91
ORD-1044 Yaw Boateng Pending $172.88
ORD-1045 Efua Asante Paid $29.85
ORD-1046 Kwame Nkrumah Jr. Refunded $66.82
Showing 1–6 of 34
Page 1 of 6

Server-Driven Sorting

With client-sort="false", clicking a sortable header does not touch the DOM. It fires a cancelable before-sort-change followed by sort-change, with the column key and the new direction in the event detail. Handle it, refetch the sorted page from your backend, and re-render the grid, the same pattern used by the search and pagination events further down this page.

document.addEventListener('bladewind:data-grid:sort-change', (event) => {
    if (event.detail.name !== 'orders-grid') return;
    const { key, direction } = event.detail;
    setDataGridLoading('orders-grid', true);
    fetch(`/orders?sort=${key}&direction=${direction}`)
        .then((response) => response.text())
        .then((html) => {
            document.getElementById('orders-grid-wrapper').innerHTML = html;
        });
});

Searching

searchable="true" renders a toolbar search field. client-search defaults to true and filters rows by their rendered cell text as you type. Try searching for a customer name in the first example on this page, or for a status like refunded.

Customise the placeholder with search-placeholder:

<x-bladewind::data-grid name="orders-grid" searchable="true" search-placeholder="Search by reference or customer…"
    :columns="$orderColumns" :rows="$orders" />

Set client-search="false" to filter server-side instead. The grid renders no filtering itself, it emits bladewind:data-grid:search with the current query on every keystroke, so debounce it yourself before hitting your backend.

let searchTimer;
document.addEventListener('bladewind:data-grid:search', (event) => {
    if (event.detail.name !== 'orders-grid') return;
    clearTimeout(searchTimer);
    searchTimer = setTimeout(() => {
        setDataGridLoading('orders-grid', true);
        fetch(`/orders?q=${encodeURIComponent(event.detail.query)}`)
            .then((response) => response.text())
            .then((html) => {
                document.getElementById('orders-grid-wrapper').innerHTML = html;
            });
    }, 300);
});

Row Selection

selectable="true" adds a selection column. selection-mode is multiple (checkboxes, with a tri-state select-all in the header, scoped to the current page or search results) or single (radio buttons). A selection bar appears above the grid once anything is selected, with a clear-selection control and an optional bulk-actions slot for custom buttons.

Multiple Selection

<x-bladewind::data-grid name="bulk-orders" label="Orders" selectable="true" selection-mode="multiple"
    paginated="true" page-size="8" :columns="$orderColumns" :rows="$orders" />
Status
ORD-1041 Kofi Addo Paid $61.97
ORD-1042 Akosua Owusu Paid $98.94
ORD-1043 Ama Mensah Paid $135.91
ORD-1044 Yaw Boateng Pending $172.88
ORD-1045 Efua Asante Paid $29.85
ORD-1046 Kwame Nkrumah Jr. Refunded $66.82
ORD-1047 Abena Darko Pending $103.79
ORD-1048 Kwabena Osei Paid $140.76
Showing 1–8 of 34
Page 1 of 5

Single Selection

Use selection-mode="single" for pick-one flows, choosing a reviewer to assign, picking a default address, selecting one plan.

Select all rows Reviewer Department
Ama Mensah Support
Kofi Addo Engineering
Yaw Boateng Finance

Preselected Rows

Pass selected with an array of row keys to render the grid with some rows already checked, useful for an edit form that reopens with a saved set of chosen rows.

<x-bladewind::data-grid name="preselected-orders" label="Orders" selectable="true"
    :selected="['3', '7', '12']"
    :columns="$orderColumns" :rows="$orders" />
Status
ORD-1041 Kofi Addo Paid $61.97
ORD-1042 Akosua Owusu Paid $98.94
ORD-1043 Ama Mensah Paid $135.91
ORD-1044 Yaw Boateng Pending $172.88
ORD-1045 Efua Asante Paid $29.85
ORD-1046 Kwame Nkrumah Jr. Refunded $66.82
Showing 1–6 of 34
Page 1 of 6

Bulk Actions

The bulk-actions slot renders inside the selection bar, next to the clear-selection control, and only appears once at least one row is selected. Pair it with dataGridSelectedKeys() to read the current selection when a bulk action fires.

<x-bladewind::data-grid name="orders-with-actions" label="Orders" selectable="true"
    :columns="$orderColumns" :rows="$orders">
    <x-slot:bulk-actions>
        <x-bladewind::button size="small" onclick="alert('Exporting: ' + dataGridSelectedKeys('orders-with-actions').join(', '))">Export</x-bladewind::button>
        <x-bladewind::button size="small" type="red" onclick="alert('Deleting: ' + dataGridSelectedKeys('orders-with-actions').join(', '))">Delete</x-bladewind::button>
    </x-slot:bulk-actions>
</x-bladewind::data-grid>
Status
ORD-1041 Kofi Addo Paid $61.97
ORD-1042 Akosua Owusu Paid $98.94
ORD-1043 Ama Mensah Paid $135.91
ORD-1044 Yaw Boateng Pending $172.88
ORD-1045 Efua Asante Paid $29.85
ORD-1046 Kwame Nkrumah Jr. Refunded $66.82
Showing 1–6 of 34
Page 1 of 6

Select a couple of rows above, then try the Export and Delete buttons that appear in the selection bar.

Pagination and Server-Driven State

Set paginated="true" with page-size for client-side pagination, as in the orders grid at the top of this page. The grid renders its own previous and next footer and keeps it in sync with sorting and searching, page one always reflects whatever the current sort and search produce.

Client-Side Pagination at Scale

Client pagination still ships every row to the browser and pages through them there, which is fine for a few hundred rows but the wrong tool once a dataset grows past what is reasonable to send on every page load. The grid below pages through the same 34-row order list at a smaller page size, six pages of six rows each, to show the previous and next controls disabling correctly at both ends.

Status
ORD-1041 Kofi Addo Paid $61.97
ORD-1042 Akosua Owusu Paid $98.94
ORD-1043 Ama Mensah Paid $135.91
ORD-1044 Yaw Boateng Pending $172.88
ORD-1045 Efua Asante Paid $29.85
ORD-1046 Kwame Nkrumah Jr. Refunded $66.82
Showing 1–6 of 34
Page 1 of 6

Server-Driven Pagination

Pass a real Laravel paginator through paginator instead of setting paginated directly, the grid detects it and switches into server mode on its own, rendering Pagination's standard page links. rows should be the paginator's current-page items, not the full dataset.

// in your controller or route closure
$staff = Staff::query()->orderBy('company_name')->paginate(8);
return view('staff.index', ['staff' => $staff]);
<x-bladewind::data-grid name="staff-directory" label="Staff directory" row-key="member_id"
    :columns="[
        ['key' => 'company_name', 'label' => 'Company', 'sortable' => true],
        ['key' => 'first_name', 'label' => 'Contact', 'format' => fn ($v, $row) => $row['first_name'].' '.$row['last_name']],
        ['key' => 'mobile', 'label' => 'Mobile'],
        ['key' => 'email', 'label' => 'Email'],
    ]"
    :rows="$staff->items()"
    :paginator="$staff" />
Contact Mobile Email
Cummerata-Zemlak Dejuan Kiehn (415) 971-6920 alf12@example.net
Leuschke-Parisian Howard Carroll 930.873.1567 joannie.dubuque@example.com
Senger Group Willa O'Conner 774.333.2743 rschinner@example.com
Hauck, Lubowitz and Corkery Bethel Jacobs +1 (432) 647-6479 stacy.koch@example.net
Prosacco, Donnelly and D'Amore Yolanda Doyle 484.649.5165 lreilly@example.com
Sipes and Sons Haylee Reinger 1-267-775-9759 cschneider@example.com
Schroeder-Gulgowski Craig Raynor +1-254-486-4218 hayes.verna@example.com
Kiehn LLC Zora Howe (872) 223-5597 emard.roma@example.com
Showing 49 to 56 of 222 records

This one is not a simulation. The 222-record directory behind it lives in a plain PHP file on the server, and the page links above genuinely reload the page with a real, different set of 8 records each time, exactly what a database-backed grid would do.

Loading State

Set loading="true", or call setDataGridLoading(name, true), while an application fetches new rows for a server-driven grid. The table dims and shows a progress indicator, and screen readers see aria-busy="true".

Status
ORD-1041 Kofi Addo Paid $61.97
ORD-1042 Akosua Owusu Paid $98.94
ORD-1043 Ama Mensah Paid $135.91
ORD-1044 Yaw Boateng Pending $172.88
ORD-1045 Efua Asante Paid $29.85

Appearance

striped, bordered, and dense control visual density. sticky keeps the header pinned while the body scrolls, and defaults to true. Set height to cap the grid at a fixed height with an internal scrollbar rather than letting it grow with the row count.

Striped

Status
ORD-1041 Kofi Addo Paid $61.97
ORD-1042 Akosua Owusu Paid $98.94
ORD-1043 Ama Mensah Paid $135.91
ORD-1044 Yaw Boateng Pending $172.88
ORD-1045 Efua Asante Paid $29.85
ORD-1046 Kwame Nkrumah Jr. Refunded $66.82
<x-bladewind::data-grid name="striped-grid" label="Orders" striped="true" :columns="$orderColumns" :rows="$orders" />

Bordered

Status
ORD-1041 Kofi Addo Paid $61.97
ORD-1042 Akosua Owusu Paid $98.94
ORD-1043 Ama Mensah Paid $135.91
ORD-1044 Yaw Boateng Pending $172.88
ORD-1045 Efua Asante Paid $29.85
ORD-1046 Kwame Nkrumah Jr. Refunded $66.82
<x-bladewind::data-grid name="bordered-grid" label="Orders" bordered="true" :columns="$orderColumns" :rows="$orders" />

sticky pins the header row while the body scrolls, and defaults to true. It only has something to do once the grid has a height short enough that the rows actually scroll, so the two go together. Scroll inside the grid below and the header stays put.

Status
ORD-1041 Kofi Addo Paid $61.97
ORD-1042 Akosua Owusu Paid $98.94
ORD-1043 Ama Mensah Paid $135.91
ORD-1044 Yaw Boateng Pending $172.88
ORD-1045 Efua Asante Paid $29.85
ORD-1046 Kwame Nkrumah Jr. Refunded $66.82
ORD-1047 Abena Darko Pending $103.79
ORD-1048 Kwabena Osei Paid $140.76
ORD-1049 Adjoa Frimpong Paid $177.73
ORD-1050 Kojo Asante Pending $34.70
ORD-1051 Akua Sarpong Paid $71.67
ORD-1052 Yaa Amponsah Paid $108.64
ORD-1053 Kwesi Appiah Paid $145.61
ORD-1054 Afia Boadu Pending $182.58
ORD-1055 Kwaku Antwi Paid $39.55
ORD-1056 Esi Danso Refunded $76.52
ORD-1057 Kofi Addo Pending $113.49
ORD-1058 Akosua Owusu Paid $150.46
ORD-1059 Ama Mensah Paid $187.43
ORD-1060 Yaw Boateng Pending $44.40
ORD-1061 Efua Asante Paid $81.37
ORD-1062 Kwame Nkrumah Jr. Paid $118.34
ORD-1063 Abena Darko Paid $155.31
ORD-1064 Kwabena Osei Pending $192.28
ORD-1065 Adjoa Frimpong Paid $49.25
ORD-1066 Kojo Asante Refunded $86.22
ORD-1067 Akua Sarpong Pending $123.19
ORD-1068 Yaa Amponsah Paid $160.16
ORD-1069 Kwesi Appiah Paid $197.13
ORD-1070 Afia Boadu Pending $54.10
ORD-1071 Kwaku Antwi Paid $91.07
ORD-1072 Esi Danso Paid $128.04
ORD-1073 Kofi Addo Paid $165.01
ORD-1074 Akosua Owusu Pending $201.98
<x-bladewind::data-grid name="sticky-grid" label="Orders" height="12rem" :columns="$orderColumns" :rows="$orders" />

Set sticky="false" to let the header scroll away with the rest of the content instead, useful if the grid already sits inside its own scroll container that provides a sticky header at a higher level.

Status
ORD-1041 Kofi Addo Paid $61.97
ORD-1042 Akosua Owusu Paid $98.94
ORD-1043 Ama Mensah Paid $135.91
ORD-1044 Yaw Boateng Pending $172.88
ORD-1045 Efua Asante Paid $29.85
ORD-1046 Kwame Nkrumah Jr. Refunded $66.82
ORD-1047 Abena Darko Pending $103.79
ORD-1048 Kwabena Osei Paid $140.76
ORD-1049 Adjoa Frimpong Paid $177.73
ORD-1050 Kojo Asante Pending $34.70
ORD-1051 Akua Sarpong Paid $71.67
ORD-1052 Yaa Amponsah Paid $108.64
ORD-1053 Kwesi Appiah Paid $145.61
ORD-1054 Afia Boadu Pending $182.58
ORD-1055 Kwaku Antwi Paid $39.55
ORD-1056 Esi Danso Refunded $76.52
ORD-1057 Kofi Addo Pending $113.49
ORD-1058 Akosua Owusu Paid $150.46
ORD-1059 Ama Mensah Paid $187.43
ORD-1060 Yaw Boateng Pending $44.40
ORD-1061 Efua Asante Paid $81.37
ORD-1062 Kwame Nkrumah Jr. Paid $118.34
ORD-1063 Abena Darko Paid $155.31
ORD-1064 Kwabena Osei Pending $192.28
ORD-1065 Adjoa Frimpong Paid $49.25
ORD-1066 Kojo Asante Refunded $86.22
ORD-1067 Akua Sarpong Pending $123.19
ORD-1068 Yaa Amponsah Paid $160.16
ORD-1069 Kwesi Appiah Paid $197.13
ORD-1070 Afia Boadu Pending $54.10
ORD-1071 Kwaku Antwi Paid $91.07
ORD-1072 Esi Danso Paid $128.04
ORD-1073 Kofi Addo Paid $165.01
ORD-1074 Akosua Owusu Pending $201.98
<x-bladewind::data-grid name="non-sticky-grid" label="Orders" height="12rem" sticky="false" :columns="$orderColumns" :rows="$orders" />

Dense, With a Fixed Scrollable Height

dense and a fixed height pair well for a compact grid embedded inside a card or a dashboard widget, where the header should stay visible while the body scrolls internally instead of pushing the rest of the page down.

Status
ORD-1041 Kofi Addo Paid $61.97
ORD-1042 Akosua Owusu Paid $98.94
ORD-1043 Ama Mensah Paid $135.91
ORD-1044 Yaw Boateng Pending $172.88
ORD-1045 Efua Asante Paid $29.85
ORD-1046 Kwame Nkrumah Jr. Refunded $66.82
ORD-1047 Abena Darko Pending $103.79
ORD-1048 Kwabena Osei Paid $140.76
ORD-1049 Adjoa Frimpong Paid $177.73
ORD-1050 Kojo Asante Pending $34.70
ORD-1051 Akua Sarpong Paid $71.67
ORD-1052 Yaa Amponsah Paid $108.64
ORD-1053 Kwesi Appiah Paid $145.61
ORD-1054 Afia Boadu Pending $182.58
ORD-1055 Kwaku Antwi Paid $39.55
ORD-1056 Esi Danso Refunded $76.52
ORD-1057 Kofi Addo Pending $113.49
ORD-1058 Akosua Owusu Paid $150.46
ORD-1059 Ama Mensah Paid $187.43
ORD-1060 Yaw Boateng Pending $44.40
ORD-1061 Efua Asante Paid $81.37
ORD-1062 Kwame Nkrumah Jr. Paid $118.34
ORD-1063 Abena Darko Paid $155.31
ORD-1064 Kwabena Osei Pending $192.28
ORD-1065 Adjoa Frimpong Paid $49.25
ORD-1066 Kojo Asante Refunded $86.22
ORD-1067 Akua Sarpong Pending $123.19
ORD-1068 Yaa Amponsah Paid $160.16
ORD-1069 Kwesi Appiah Paid $197.13
ORD-1070 Afia Boadu Pending $54.10
ORD-1071 Kwaku Antwi Paid $91.07
ORD-1072 Esi Danso Paid $128.04
ORD-1073 Kofi Addo Paid $165.01
ORD-1074 Akosua Owusu Pending $201.98
<x-bladewind::data-grid name="dense-grid" label="Compact orders"
    striped="true" dense="true" height="14rem"
    :columns="$orderColumns" :rows="$orders" />

Toolbar

The toolbar slot renders next to the search field, for controls that apply to the grid as a whole rather than to a selection, an export button, a view switcher, a status filter.

<x-bladewind::data-grid name="orders-with-toolbar" label="Orders" searchable="true"
    :columns="$orderColumns" :rows="$orders">
    <x-slot:toolbar>
        <x-bladewind::button size="small" onclick="alert('Exporting all orders as CSV')">Export CSV</x-bladewind::button>
    </x-slot:toolbar>
</x-bladewind::data-grid>
Status
ORD-1041 Kofi Addo Paid $61.97
ORD-1042 Akosua Owusu Paid $98.94
ORD-1043 Ama Mensah Paid $135.91
ORD-1044 Yaw Boateng Pending $172.88
ORD-1045 Efua Asante Paid $29.85
ORD-1046 Kwame Nkrumah Jr. Refunded $66.82
Showing 1–6 of 34
Page 1 of 6

Custom Layout

Skip columns and rows entirely for a fully custom layout: a header slot for <th> content, and the default slot for hand-written <tr> rows. This is the escape hatch for a table body that does not fit the column model at all, merged cells, a summary row, a layout the grid was never meant to describe.

<x-bladewind::data-grid name="custom-orders" label="Orders summary">
    <x-slot:header>
        <th>Reference</th>
        <th>Customer</th>
        <th class="text-right">Total</th>
    </x-slot:header>

    <tr>
        <td>ORD-1041</td>
        <td>Kofi Addo</td>
        <td class="text-right">$84.00</td>
    </tr>
    <tr class="font-semibold">
        <td colspan="2">Total</td>
        <td class="text-right">$84.00</td>
    </tr>
</x-bladewind::data-grid>
Reference Customer Total
ORD-1041 Kofi Addo $84.00
Total $84.00

Events

Before events are cancelable. Call preventDefault() on the event to stop the related change, useful for confirming a destructive selection change or blocking a sort while a save is in flight. All event names start with bladewind:data-grid:.

Event suffixWhen it runs
before-sort-change, sort-changeBefore and after a column's sort state changes.
before-select-change, select-changeBefore and after row selection changes. Preventing the before event reverts the checkbox or radio.
before-page-change, page-changeBefore and after the current client page changes.
searchOn every keystroke in the search field, with the current query.

A practical use for the before events is confirming a change rather than silently accepting it:

document.addEventListener('bladewind:data-grid:before-select-change', (event) => {
    if (event.detail.name !== 'orders-grid') return;
    if (event.detail.selecting && event.detail.row.status === 'refunded') {
        if (!confirm('This order was refunded. Select it anyway?')) {
            event.preventDefault();
        }
    }
});

Full List of Attributes

AttributeDefaultDescription
nameGeneratedUnique public helper and DOM scope.
labelData gridAccessible table name.
columns[]Column model. Omit with rows for a custom layout.
rowsnullArray of associative arrays or objects to render.
row-keyidField used as each row's unique identity.
selectablefalseAdds a selection column.
selection-modemultiplemultiple (checkboxes) or single (radios).
selected[]Row keys to preselect.
sortablefalseMakes every column sortable. A column's own sortable key wins per column.
sort-keynullColumn key to render as initially sorted.
sort-directionnullasc or desc, paired with sort-key.
client-sorttrueReorders rows in the browser. When false, only the indicator updates and the app must reorder the data.
searchablefalseRenders the toolbar search field.
search-placeholderSearch…Search field placeholder text.
client-searchtrueFilters rows in the browser. When false, only the search event fires.
paginatedfalseEnables client pagination. Implied automatically by passing paginator.
page-size25Rows per page in client pagination mode.
paginatornullA Laravel paginator, for server-driven pagination.
stickytruePins the header while the body scrolls.
loadingfalseDims the table and shows a progress indicator.
empty-textNo records found.Text shown when there are no rows.
stripedfalseAlternating row background.
borderedfalseVertical cell borders.
densefalseReduced cell padding.
heightnullMax height for the scrollable body, e.g. 24rem.
select-all-labelSelect all rowsAccessible label for the header checkbox.
clear-selection-labelClear selectionLabel for the clear-selection control.

Slots

SlotDescription
toolbarContent appended after the search field. See Toolbar.
bulk-actionsCustom buttons in the selection bar. See Bulk actions.
headerCustom <th> content, used instead of columns. See Custom layout.
defaultCustom <tr> rows, used instead of rows. See Custom layout.

JavaScript API

Every helper returns true when it completes, or when the requested state already applies, and false when the target is missing or a cancelable event was prevented.

FunctionWhat it does
sortDataGrid(name, key, direction)Sorts a client-mode grid by the given column, direction is 'asc', 'desc', or null to clear.
setDataGridPage(name, page)Jumps to a page in a client-paginated grid.
selectAllDataGridRows(name, selected)Selects or deselects every visible row, matching the header checkbox.
clearDataGridSelection(name)Clears the current selection entirely.
dataGridSelectedKeys(name)Returns an array of the currently selected row keys.
setDataGridLoading(name, loading)Toggles the dimmed, busy loading state. See Loading state.
resetDataGrid(name)Clears search, sort, selection, and returns to page one, all at once.
sortDataGrid('orders-grid', 'total', 'desc');
setDataGridPage('orders-grid', 2);
selectAllDataGridRows('orders-grid', true);
dataGridSelectedKeys('orders-grid'); // ['3', '7', '12']
clearDataGridSelection('orders-grid');
setDataGridLoading('orders-grid', true);
resetDataGrid('orders-grid');

Putting It All Together

A grid combining most of what is documented above: searchable, multi-select with bulk actions, a toolbar export button, striped rows, and client pagination over the full 34-row order list.

<x-bladewind::data-grid name="complete-orders" label="Orders" row-key="reference"
    searchable="true" search-placeholder="Search orders…"
    selectable="true" selection-mode="multiple"
    sortable="true" striped="true"
    paginated="true" page-size="10"
    :columns="$orderColumns" :rows="$orders">
    <x-slot:toolbar>
        <x-bladewind::button size="small">Export CSV</x-bladewind::button>
    </x-slot:toolbar>
    <x-slot:bulk-actions>
        <x-bladewind::button size="small" type="red">Delete selected</x-bladewind::button>
    </x-slot:bulk-actions>
</x-bladewind::data-grid>
ORD-1041 Kofi Addo Paid $61.97
ORD-1042 Akosua Owusu Paid $98.94
ORD-1043 Ama Mensah Paid $135.91
ORD-1044 Yaw Boateng Pending $172.88
ORD-1045 Efua Asante Paid $29.85
ORD-1046 Kwame Nkrumah Jr. Refunded $66.82
ORD-1047 Abena Darko Pending $103.79
ORD-1048 Kwabena Osei Paid $140.76
ORD-1049 Adjoa Frimpong Paid $177.73
ORD-1050 Kojo Asante Pending $34.70
Showing 1–10 of 34
Page 1 of 4
The source files for this component are available in resources > views > components > bladewind > data-grid