mrcatz / datatable
Livewire DataTable base class for Laravel — CRUD, bulk actions, export, keyboard navigation, expandable rows, URL persistence, and more.
Requires
- php: ^8.1
- laravel/framework: ^11.0|^12.0|^13.0
- livewire/livewire: ^3.0|^4.0
Requires (Dev)
- orchestra/testbench: ^11.0
- phpunit/phpunit: ^10.0|^11.0
Suggests
- barryvdh/laravel-dompdf: Required for PDF export (^2.0|^3.0)
- maatwebsite/excel: Required for Excel export (^3.1)
- dev-main
- v1.20.5
- v1.20.4
- v1.20.3
- v1.20.2
- v1.20.1
- v1.20.0
- v1.19.6
- v1.19.5
- v1.19.4
- v1.19.3
- v1.19.2
- v1.19.1
- v1.19.0
- v1.18.5
- v1.18.4
- v1.18.3
- v1.18.2
- v1.18.1
- v1.18.0
- v1.17.2
- v1.17.1
- v1.17.0
- v1.16.9
- v1.16.8
- v1.16.7
- v1.16.6
- v1.16.5
- v1.16.4
- v1.16.3
- v1.16.2
- v1.16.1
- v1.16.0
- v1.15.3
- v1.15.2
- v1.15.1
- v1.15.0
- v1.14.8
- v1.14.7
- v1.14.6
- v1.14.5
- v1.14.4
- v1.14.3
- v1.14.2
- v1.14.1
- v1.14.0
- v1.13.1
- v1.13.0
- v1.12.6
- v1.12.5
- v1.12.4
- v1.12.3
- v1.12.2
- v1.12.1
- v1.12.0
- v1.11.0
- v1.10.2
- v1.10.1
- v1.10.0
- v1.9.4
- v1.9.3
- v1.9.2
- v1.9.1
- v1.9.0
- v1.8.6
- v1.8.5
- v1.8.4
- v1.8.3
- v1.8.2
- v1.8.1
- v1.8.0
- v1.7.0
- v1.6.1
- v1.6.0
- v1.5.5
- v1.5.4
- v1.5.3
- v1.5.2
- v1.5.1
- v1.5.0
- v1.4.3
- v1.4.2
- v1.4.1
- v1.4.0
- v1.3.2
- v1.3.1
- v1.3.0
- v1.2.5
- v1.2.4
- v1.2.3
- v1.2.2
- v1.2.1
- v1.2.0
- v1.1.3
- v1.1.2
- v1.1.1
- v1.1.0
- v1.0.2
- v1.0.1
- v1.0.0
This package is auto-updated.
Last update: 2026-04-03 08:46:18 UTC
README
MrCatz DataTable
DataTable + Form Builder for Laravel Livewire — build complete admin pages in minutes.
View Live Demo | Demo Source Code
# Generate a full CRUD page (table + form) with one command
php artisan mrcatz:make Product --path=Admin
// Or define forms in PHP — no Blade needed public function setForm(): array { return [ MrCatzFormField::text('name', label: 'Name', rules: 'required', icon: 'person'), MrCatzFormField::email('email', label: 'Email', rules: 'required|email'), MrCatzFormField::select('role', label: 'Role', data: $roles, value: 'id', option: 'name'), MrCatzFormField::button('Check', onClick: 'checkAvail', style: 'info')->span(4), ]; }
Why MrCatz?
| Problem | MrCatz Solution |
|---|---|
| Building CRUD pages over and over | mrcatz:make generates 4 files at once |
| Writing Blade forms for every model | Form Builder — define fields in PHP, auto-render with validation |
| Forms only work inside DataTable modal | Standalone forms — use Form Builder on any page (profile, settings, etc.) |
| Search is just basic LIKE | Multi-keyword search with relevance scoring |
| Filter state lost on reload | URL persistence — all state in URL, shareable & bookmarkable |
| Export requires manual coding | Excel & PDF export built-in with preview |
| No bulk delete | Bulk actions with per-row control |
| Editing requires opening a modal | Inline editing — double-click to edit cells, with validation |
| Table unusable on mobile | Responsive card view — auto card layout on small screens |
| No keyboard navigation | Keyboard nav — Arrow, Enter, Delete, Escape |
| Too many columns cluttering the view | Column visibility — hide/show columns |
| Column headers disappear on scroll | Sticky header — always visible |
| Can only sort by one column | Multi-sort — Shift+click for secondary sort |
Features
Form Builder
- Define form fields in PHP — no Blade form file needed
- 25+ field types — text, email, password, number, select, textarea, file, toggle, checkbox, chooser, radio, date, time, datetime, color, range, url, tel, search, rating, hidden
- Button with Livewire hook —
onClick,withLoading()for action buttons inside form - Static elements — section, note, alert, divider, raw HTML
- DaisyUI style & size —
->style('primary'),->size('lg')on any field - Grid layout — 12-column grid with
->span()for multi-column forms - Dynamic/dependent fields —
->visibleWhen(),->visibleWhenAll(),->onChange(),->dependsOn() - Wire model modes —
->live(),->lazy(),->debounce() - Prefix, suffix, hint (with color), file preview, password confirmation
- Validation — auto-extracted rules & custom messages from field definitions
- Icon system — built-in SVG, raw HTML, or custom config
form_icons - Works in DataTable modal AND standalone pages (profile, settings, any Livewire component)
CRUD & Data
- CRUD lifecycle hooks — prepareAdd, prepareEdit, save, delete, bulk delete
- Inline editing — double-click cells to edit, Enter to save, with server-side validation
- Row click hook — custom action when row is clicked
- Fluent DataTable API —
->withColumn(),->withCustomColumn(),->enableExpand()
Search & Filter
- Multi-keyword search with relevance scoring and highlight
- Filters — simple, callback, dependent (parent-child), dynamic show/hide
- Filter presets — save/load filter combinations (localStorage)
- Dependent filters auto-initialize from URL/presets
Sorting & Columns
- Column sorting — click header, visual indicator
- Multi-sort — Shift+click for multiple sort columns with numbered badges
- Column visibility toggle — hide/show columns, persistent in URL
- Column resize — drag handle on headers
- Column reorder — drag & drop headers, persistent in URL
- Default column visibility —
visible: falseto hide columns by default
Export
- Excel (.xlsx) & PDF (.pdf) with filter scope and preview count
- Built-in PDF template and Excel export class
- Export hooks —
beforeExport()/afterExport()for data manipulation
UX & Display
- Responsive mobile view — auto card layout on small screens, tap-to-edit
- Sticky header — keeps thead visible on scroll
- Loading skeleton — placeholder rows during data fetch (responsive)
- Expandable rows — inline detail without modal
- Keyboard navigation — Arrow Up/Down, Enter, Delete/Backspace, Escape
- Zebra table styling
- Toast notifications — success, error, warning, info
- Loading overlay — fullscreen loading state
- URL persistence — search, sort, multi-sort, filter, pagination, column order, hidden columns
Accessibility
aria-sorton sortable headers,aria-modal+aria-labelledbyon modals- Focus trap on all modals,
aria-labelon checkboxes aria-liveon toast container,role="grid"on table
Developer Experience
- Artisan generator —
mrcatz:makeandmrcatz:remove - Modular traits — HasFilters, HasExport, HasBulkActions
- Event constants —
MrCatzEvent::REFRESH_DATAetc. - Multi-language — English & Indonesian via Laravel lang files
- Configurable icon set — Default (inline SVG), Heroicons, Material Icons, Font Awesome, or custom
- Search debounce validation — auto-corrects invalid format
- Backward compatible — no strict types on public properties/methods
- Test suite — 103 tests, 243 assertions (incl. Livewire render tests)
- CI/CD — GitHub Actions (PHP 8.1–8.4)
Installation
composer require mrcatz/datatable
Setup
1. Toast Notifications — add to your main layout before </body>:
@include('mrcatz::components.ui.notification')
2. Tailwind Content Scan — add the package path:
/* app.css (Tailwind v4) */ @source '../../vendor/mrcatz/**/*.blade.php';
// tailwind.config.js (Tailwind v3) content: ['./vendor/mrcatz/**/*.blade.php']
3. Icons — works out of the box with built-in SVG icons. No setup needed.
Optionally switch to Heroicons, Material Icons, or Font Awesome — see Icon Set section.
4. Optional Dependencies:
composer require maatwebsite/excel # Excel export composer require barryvdh/laravel-dompdf # PDF export
Publish (Optional)
php artisan vendor:publish --tag=mrcatz-views # Customize blade views php artisan vendor:publish --tag=mrcatz-lang # Customize translations php artisan vendor:publish --tag=mrcatz-config # Customize config
Quick Start
Fast Way: Artisan Generator
php artisan mrcatz:make Product --path=Admin
Generates 4 ready-to-use files:
app/Livewire/Admin/Product/ProductPage.php <- CRUD logic
app/Livewire/Admin/Product/ProductTable.php <- DataTable config
resources/views/livewire/admin/product/product-page.blade.php
resources/views/livewire/admin/product/product_form.blade.php
Add a route, edit columns and form — done.
php artisan mrcatz:make Product # Without path php artisan mrcatz:make Product --path=Admin --table=my_products # Custom table php artisan mrcatz:make Product --path=Admin --force # Overwrite existing php artisan mrcatz:remove Product --path=Admin # Remove generated files
Manual Way
1. Page Component (CRUD Logic)
<?php namespace App\Livewire\Admin\User; use App\Models\User; use MrCatz\DataTable\MrCatzComponent; use Illuminate\Support\Facades\Hash; class UserPage extends MrCatzComponent { public $name, $email, $password; public function mount() { $this->setTitle('User'); $this->breadcrumbs = [ ['title' => 'Dashboard', 'url' => route('admin.dashboard')], ['title' => 'User', 'url' => null], ]; } public function render() { return view('livewire.admin.user.user-page') ->layout('components.layouts.admin_layout'); } public function prepareAddData() { $this->form_title = 'Add User'; $this->reset(['name', 'email', 'password']); } public function prepareEditData($data) { $this->form_title = 'Edit User'; $this->id = $data['id']; $this->name = $data['name']; $this->email = $data['email']; } public function prepareDeleteData($data) { $this->id = $data['id']; $this->deleted_text = $data['name']; } public function saveData() { $this->validate(['name' => 'required|max:255', 'email' => 'required|email']); if ($this->isEdit) { User::find($this->id)->update(['name' => $this->name, 'email' => $this->email]); $this->dispatch_to_view(true, 'update'); } else { $user = User::create([ 'name' => $this->name, 'email' => $this->email, 'password' => Hash::make($this->password), ]); $this->dispatch_to_view($user, 'insert'); } } public function dropData() { $delete = User::find($this->id)->delete(); $this->dispatch_to_view($delete, 'delete'); } public function dropBulkData($selectedRows) { $count = User::whereIn('id', $selectedRows)->delete(); $this->dispatch('refresh-data', [ 'status' => true, 'text' => $count . ' users deleted!' ]); } }
2. Table Component (DataTable Config)
<?php namespace App\Livewire\Admin\User; use MrCatz\DataTable\MrCatzDataTableFilter; use MrCatz\DataTable\MrCatzDataTables; use MrCatz\DataTable\MrCatzDataTablesComponent; use Illuminate\Support\Facades\DB; class UserTable extends MrCatzDataTablesComponent { public $showSearch = true; public $showAddButton = true; public $exportTitle = 'User Data'; public function baseQuery() { return DB::table('users'); } public function configTable() { return ['table_name' => 'users', 'table_id' => 'id']; } public function setTable() { return $this->CreateMrCatzTable() ->enableExpand(function ($data, $i) { return MrCatzDataTables::getExpandView($data, [ 'Email' => 'email', 'Created' => 'created_at', ]); }) ->withColumnIndex('No') ->withColumn('Name', 'name') ->withColumn('Email', 'email') ->withCustomColumn('Actions', function ($data, $i) { return MrCatzDataTables::getActionView($data, $i); }); } public function getRowPerPageOption() { return [10, 15, 20, 30]; } }
3. Blade Views
{{-- resources/views/livewire/admin/user/user-page.blade.php --}} @push('title') <title>{{ $title ?? 'User' }} || {{ config('app.name') }}</title> @endpush <div class="p-6"> @include('mrcatz::components.ui.breadcrumbs') <livewire:admin.user.user-table /> @include('livewire.admin.user.user_form') </div> @include('mrcatz::components.ui.datatable-scripts')
{{-- resources/views/livewire/admin/user/user_form.blade.php --}} <div> @extends('mrcatz::components.ui.datatable-form') @section('forms') <div class="space-y-4"> <label class="form-control"> <div class="label"><span class="label-text">Name</span></div> <input type="text" class="input input-bordered" wire:model="name" /> @error('name') <span class="text-error text-xs">{{ $message }}</span> @enderror </label> <label class="form-control"> <div class="label"><span class="label-text">Email</span></div> <input type="email" class="input input-bordered" wire:model="email" /> @error('email') <span class="text-error text-xs">{{ $message }}</span> @enderror </label> </div> @endsection </div>
Feature Guide
Columns
public function setTable() { return $this->CreateMrCatzTable() ->withColumnIndex('No') ->withColumn('Name', 'name') ->withColumn('Email', 'email', visible: false) // hidden by default ->withColumn('Price', 'price', editable: true) // inline editable ->withColumn('Code', 'code', uppercase: true, gravity: 'center') ->withColumn('Phone', 'phone', showOn: 'mobile') // mobile card only ->withCustomColumn('Status', function ($data, $i) { return '<span class="badge badge-sm">' . $data->status . '</span>'; }, 'status', false) ->withCustomColumn('Actions', function ($data, $i) { return MrCatzDataTables::getActionView($data, $i); }, showOn: 'desktop'); // desktop table only }
withColumn options: $uppercase, $th, $sort, $gravity ('left'/'center'/'right'), $editable, $visible, $rules, $showOn
withCustomColumn options: $key (for search), $sort, $visible, $showOn
Image Column (withColumnImage)
Display images in table cells with clickable lightbox (scroll zoom, click to reset/close):
// Storage (default) — asset('storage/' . value) ->withColumnImage('Avatar', 'avatar', width: 40, height: 40, previewClass: 'rounded-full ring ring-primary', fallback: 'name', ) // Public directory — asset(value) ->withColumnImage('Image', 'image', 36, 36, 'rounded-lg', 'name', urlPrefix: 'public') // External CDN — prefix + '/' + value ->withColumnImage('Photo', 'photo', 40, 40, 'rounded-full', urlPrefix: 'https://cdn.example.com') // DB value is already full URL — use as-is ->withColumnImage('Photo', 'photo_url', 40, 40, 'rounded-full', urlPrefix: null)
| Parameter | Default | Description |
|---|---|---|
$width |
40 |
Preview width in pixels |
$height |
40 |
Preview height in pixels |
$previewClass |
'rounded-full' |
Tailwind classes for shape/border/shadow |
$fallback |
null |
DB column name — shows first letter when no image |
$urlPrefix |
'storage' |
URL resolution mode (see below) |
$sort |
false |
Sortable column |
$visible |
true |
Column visibility |
$showOn |
'both' |
Responsive visibility |
urlPrefix modes:
| Value | DB Value | Resolved URL |
|---|---|---|
'storage' (default) |
users/avatar.jpg |
asset('storage/users/avatar.jpg') |
'public' |
uploads/img.jpg |
asset('uploads/img.jpg') |
'https://cdn.ex.com' |
photos/1.jpg |
https://cdn.ex.com/photos/1.jpg |
null |
https://full-url.com/img.jpg |
https://full-url.com/img.jpg |
If the DB value already starts with http://, https://, or /, it's used as-is regardless of prefix.
The same urlPrefix option is available in expand view image type:
'Photo' => ['type' => 'image', 'key' => 'avatar', 'urlPrefix' => 'public', ...]
Clicking the image opens a lightbox with scroll zoom, click to reset/close.
Responsive Column Visibility (showOn)
Control which columns appear on mobile (card view) vs desktop (table view):
->withColumn('Name', 'name') // both (default) ->withColumn('Email', 'email', showOn: 'desktop') // desktop table only ->withColumn('Phone', 'phone', showOn: 'mobile') // mobile card only ->withCustomColumn('Actions', fn($d, $i) => ..., showOn: 'desktop')
| Value | Mobile Card | Desktop Table |
|---|---|---|
'both' |
Shown | Shown |
'mobile' |
Shown | Hidden |
'desktop' |
Hidden | Shown |
This is independent of $visible (column visibility toggle) — showOn controls responsive layout, visible controls user-togglable visibility.
Search Highlight on Custom Columns
withColumn() highlights search keywords automatically. For withCustomColumn(), call $this->setSearchWord():
->withCustomColumn('Category', function ($data, $i) { return $this->setSearchWord($data->category_name); }, 'categories.name', true)
Table-Prefixed Keys (JOIN queries)
->withColumn('Product', 'products.name') ->withCustomColumn('Category', fn($data, $i) => $this->setSearchWord($data->category_name), 'categories.name', true)
Filters
public function setFilter() { // Simple array filter $roleFilter = MrCatzDataTableFilter::create( 'filter_role', 'Role', [['value' => 'admin', 'label' => 'Admin'], ['value' => 'user', 'label' => 'User']], 'value', 'label', 'role' )->get(); // Custom callback filter $dateFilter = MrCatzDataTableFilter::createWithCallback( 'filter_date', 'Date', [['value' => 'today', 'label' => 'Today'], ['value' => 'week', 'label' => 'This Week']], 'value', 'label', function ($query, $value) { return match ($value) { 'today' => $query->whereDate('created_at', today()), 'week' => $query->whereBetween('created_at', [now()->startOfWeek(), now()->endOfWeek()]), default => $query, }; } )->get(); return [$roleFilter, $dateFilter]; }
Dependent Filters
public function setFilter() { $categoryFilter = MrCatzDataTableFilter::create( 'filter_category', 'Category', $categories, 'value', 'label', 'category_id' )->get(); // Hidden by default (last param = false) $subFilter = MrCatzDataTableFilter::create( 'filter_sub', 'Subcategory', [], 'value', 'label', 'subcategory_id', false )->get(); return [$categoryFilter, $subFilter]; } public function onFilterChanged($id, $value) { if ($id === 'filter_category') { $this->resetFilter('filter_sub'); if (!empty($value)) { $subs = DB::table('subcategories')->where('category_id', $value)->get()->toArray(); $this->setFilterData('filter_sub', json_decode(json_encode($subs), true)); $this->setFilterShow('filter_sub', true); } else { $this->setFilterShow('filter_sub', false); } } }
Inline Editing
// Table: mark columns as editable, with optional validation rules ->withColumn('Name', 'name', editable: true, rules: 'required|max:255') ->withColumn('Price', 'price', editable: true, rules: 'required|numeric|min:0') // Page: handle the update public function onInlineUpdate($rowData, $columnKey, $newValue) { DB::table('products')->where('id', $rowData['id'])->update([$columnKey => $newValue]); $this->dispatch_to_view(true, 'update'); }
Double-click to edit (tap on mobile), Enter to save, Escape to cancel.
Validation uses standard Laravel rules. If validation fails, the input shows a red border with the error message — no data is saved until the value is valid.
Per-Row Editable Control (enableEditable)
Use enableEditable() to control which rows and columns are editable via a callback:
->withColumn('Name', 'name', editable: true) ->withColumn('Email', 'email', editable: true) ->withColumn('Price', 'price', editable: true, rules: 'required|numeric|min:0') ->enableEditable(function ($data, $i, $column_key) { // Prevent editing name column for super-admin rows if ($column_key == 'name' && $data->role === 'super-admin') { return false; } return true; })
The callback receives three parameters:
| Parameter | Description |
|---|---|
$data |
Row data object |
$i |
Row index |
$column_key |
The column key being checked (e.g. 'name', 'email', 'price') |
Return true to allow editing, false to disable editing for that specific row + column combination.
Without a callback, all editable columns are editable on all rows:
->enableEditable() // all editable columns enabled on all rows
Without calling enableEditable() at all, the behavior is unchanged — column-level editable: true applies to all rows (backward compatible).
Column Visibility
Enabled by default. Set default visibility per column:
->withColumn('Name', 'name') // visible ->withColumn('Email', 'email', visible: false) // hidden by default
Users toggle columns via the "Columns" button. State persisted in URL (col_hidden). Disable with $enableColumnVisibility = false.
Multi-Sort
Click header = single sort. Shift+click = add secondary sort. Numbered badges show sort priority. State persisted in URL (sort_multi).
Export Hooks
// Table component public function beforeExport($headers, $rows, $format, $scope) { foreach ($rows as &$row) { $row[2] = 'Rp ' . number_format($row[2], 0, ',', '.'); } return ['headers' => $headers, 'rows' => $rows]; } public function afterExport($format, $scope) { logger("Exported {$format} with scope: {$scope}"); }
Row Click Hook
Enable in Table component, handle in Page component:
// Table component public $enableRowClick = true; // Page component public function onRowClick($data) { return redirect()->route('product.show', $data['id']); }
Empty State Customization
Override emptyStateView() in your Table component to use a custom blade view:
public function emptyStateView() { return 'partials.custom-empty-state'; // your custom blade view }
The view receives $search and $activeFilterCount variables. Return null (default) to use the built-in empty state.
Sticky Header
public $stickyHeader = true; // thead stays visible on scroll
Bulk Actions
// Table public $bulkPrimaryKey = 'id'; public $showBulkButton = true; ->enableBulk(function ($data, $i) { return Auth::id() !== $data->id; // can't select own account }) // Page public function dropBulkData($selectedRows) { $count = User::whereIn('id', $selectedRows)->delete(); $this->dispatch('refresh-data', ['status' => true, 'text' => $count . ' deleted!']); }
Expandable Rows
public $expandableRows = true; // or 'both', 'mobile', 'desktop' ->enableExpand(function ($data, $i) { if (!$data->has_details) return null; // null = disable for this row return MrCatzDataTables::getExpandView($data, [ // Text fields 'Email' => 'email', 'Created' => 'created_at', // Image with lightbox (click to zoom) 'Photo' => [ 'type' => 'image', 'key' => 'avatar', 'width' => 64, 'height' => 64, 'previewClass' => 'rounded-lg shadow-sm', 'fallback' => 'name', ], // Download link (attachment style) 'Document' => [ 'type' => 'button', 'label' => 'Download PDF', 'url' => fn($d) => asset('storage/' . $d->file_path), 'icon' => 'download', 'download' => true, 'newTab' => true, ], // Action button (navigate) 'Profile' => [ 'type' => 'link', 'label' => 'View Profile', 'url' => fn($d) => route('profile.show', $d->id), 'icon' => 'person', 'style' => 'info', 'newTab' => true, ], ]); })
Expand Field Types
| Type | Format | Description |
|---|---|---|
| text | 'Label' => 'db_key' |
Simple text display (default) |
| image | 'Label' => ['type' => 'image', 'key' => '...', ...] |
Image with clickable lightbox zoom |
| button | 'Label' => ['type' => 'button', 'label' => '...', ...] |
Attachment link (download) |
| link | 'Label' => ['type' => 'link', 'label' => '...', ...] |
Action button (navigate) |
Image options: key, width (default: 64), height (default: 64), previewClass (default: 'rounded-lg'), fallback (DB column for initial letter)
Button/Link options: label, url (string or Closure), icon, style (DaisyUI), download (bool), newTab (bool), target
Return null from the callback to disable expand for a specific row — the chevron (desktop) and Details button (mobile) will be hidden for that row.
Control where expand is available:
| Value | Mobile | Desktop |
|---|---|---|
false |
Disabled | Disabled |
true / 'both' |
Bottom-sheet modal | Inline expand |
'mobile' |
Bottom-sheet modal | Disabled |
'desktop' |
Disabled | Inline expand |
On mobile, expand content opens in a bottom-sheet modal instead of inline — better UX for small screens.
Relevance Search
public function configTable() { return ['table_name' => 'users', 'table_id' => 'id']; }
URL Persistence
All state is automatically saved to the URL:
/users?search=ryan&sort=name&dir=asc&per_page=20&filter[role]=admin&col_hidden[0]=3&sort_multi[0][key]=name&sort_multi[0][dir]=asc
Notifications
$this->dispatch_to_view($success, 'insert'); // auto: "User successfully added!" $this->show_notif('success', 'Custom message'); $this->show_notif('error', 'Something went wrong');
Localization
Publish translations:
php artisan vendor:publish --tag=mrcatz-lang
Set locale in config/mrcatz.php:
'locale' => 'id', // 'en' (default) or 'id'
Add new languages by creating lang/vendor/mrcatz/{locale}/mrcatz.php.
Icon Set
MrCatz DataTable supports 5 icon sets. Set in config/mrcatz.php:
'icon_set' => 'default', // 'default', 'heroicons', 'material', 'fontawesome', 'custom'
| Icon Set | Size | Setup | Best For |
|---|---|---|---|
| Default | ~0KB (inline SVG) | No setup needed | Works out of the box |
| Heroicons | ~0KB (inline SVG) | composer require blade-ui-kit/blade-heroicons |
Higher quality SVG |
| Material Icons | ~150KB | <link> Google Fonts in layout |
Familiar, many icons |
| Font Awesome | ~300KB | <link> CDN in layout |
Popular, many icons |
| Custom | Varies | Define map in config | Full control |
Default — built-in inline SVG, zero dependencies, zero CDN. Works immediately after install.
Heroicons — higher quality SVG via Blade Heroicons package:
composer require blade-ui-kit/blade-heroicons
'icon_set' => 'heroicons',
If
blade-heroiconsis not installed, automatically falls back to default (inline SVG).
Material Icons — add link to your layout:
<link href="https://fonts.googleapis.com/icon?family=Material+Icons|Material+Symbols+Outlined" rel="stylesheet">
'icon_set' => 'material',
Font Awesome 6 — add link to your layout:
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
'icon_set' => 'fontawesome',
Custom — define your own icon map. All 30 keys are pre-filled as comments in config (just uncomment and edit):
'icon_set' => 'custom',
Icons not defined in custom_icons fallback to Default (inline SVG).
Form Builder
Define form fields in PHP — no Blade form needed. Works in DataTable modal and standalone pages.
Full Form Builder Documentation
Quick example:
public function setForm(): array { return [ MrCatzFormField::text('name', label: 'Name', rules: 'required', icon: 'person'), MrCatzFormField::email('email', label: 'Email', rules: 'required|email'), MrCatzFormField::select('role', label: 'Role', data: $roles, value: 'id', option: 'name'), MrCatzFormField::image('avatar', label: 'Photo') ->preview($this->avatarUrl) ->previewClass('w-32 h-32 rounded-full ring ring-primary') ->onUpload('uploadPhoto')->onDelete('deletePhoto', 'Delete?'), MrCatzFormField::button('Check', onClick: 'check', style: 'info')->withLoading()->span(4), MrCatzFormField::password('password', label: 'Password')->withConfirmation(), ]; }
Highlights:
- 26+ field types — text, email, select, textarea, file, image, toggle, radio, date, time, color, range, rating, button, and more
- Image upload — preview with
previewClass()for full Tailwind control (circle, square, mask, any size/border/shadow) - Grid layout —
->span()+->rowSpan()for multi-column and side-by-side layouts - Dynamic fields —
->visibleWhen(),->onChange(),->dependsOn()for dependent/conditional fields - DaisyUI styling —
->style('primary'),->size('lg')on any field - Validation — auto-extracted rules & custom messages
- Standalone — works on any Livewire component via
HasFormBuildertrait, not just DataTable
Property Reference
Page Properties
| Property | Default | Description |
|---|---|---|
$title |
'' |
Page title, used in notifications |
$form_title |
'' |
Modal form title |
$deleted_text |
'' |
Delete confirmation text |
$breadcrumbs |
[] |
Breadcrumb navigation data |
Table Properties
| Property | Default | Description |
|---|---|---|
$showSearch |
true |
Show search input |
$showAddButton |
true |
Show add button |
$showExportButton |
true |
Show export button |
$exportTitle |
'Data Export' |
Export file/sheet title |
$usePagination |
true |
Enable pagination |
$cardContainer |
true |
Table inside card |
$borderContainer |
false |
Table with border |
$withLoading |
false |
Fullscreen loading overlay |
$tableZebraStyle |
true |
Zebra stripe rows |
$typeSearch |
false |
Realtime search on typing |
$typeSearchWithDelay |
false |
Realtime search with debounce |
$typeSearchDelay |
'500ms' |
Debounce delay (e.g. '500ms', '1s') |
$enableColumnSorting |
true |
Enable column sorting |
$enableColumnVisibility |
true |
Show column visibility toggle |
$enableColumnResize |
true |
Enable column resize |
$enableColumnReorder |
true |
Enable column drag & drop reorder |
$enableKeyboardNav |
true |
Keyboard navigation |
$showKeyboardNavNote |
false |
Show keyboard shortcut hints |
$expandableRows |
false |
Expandable rows: false, true/'both', 'mobile', 'desktop' |
$stickyHeader |
false |
Sticky header on scroll |
$enableRowClick |
false |
Enable row click dispatch to Page |
$bulkPrimaryKey |
null |
Primary key for bulk select, null = disabled |
$showBulkButton |
false |
Show bulk select toggle button |
Method Reference
Page — Override Methods
| Method | Description |
|---|---|
prepareAddData() |
Prepare add form |
prepareEditData($data) |
Prepare edit form |
prepareDeleteData($data) |
Prepare delete confirmation |
saveData() |
Handle form submit |
dropData() |
Handle delete |
dropBulkData($selectedRows) |
Handle bulk delete |
onInlineUpdate($rowData, $columnKey, $newValue) |
Handle inline cell edit |
onRowClick($data) |
Handle row click |
dispatch_to_view($condition, $type) |
Send success/failure notification |
show_notif($type, $text) |
Show custom notification |
setForm() |
Define form fields (Form Builder) |
getFormValidationRules() |
Extract validation rules from form fields |
getFormValidationMessages() |
Extract custom validation messages |
hasFormBuilder() |
Check if Form Builder is active |
formFieldChanged($id, $value) |
Handle field change events |
Table — Override Methods
| Method | Description |
|---|---|
baseQuery() |
Return query builder |
setTable() |
Define columns via fluent API |
configTable() |
Config for relevance search |
setFilter() |
Define filters |
getRowPerPageOption() |
Rows per page options |
setView() |
Custom blade view |
setPageName() |
Custom page name |
onDataLoaded($builder, $data) |
Hook after data loaded |
onFilterChanged($id, $value) |
Hook for dependent filters |
beforeExport($headers, $rows, $format, $scope) |
Manipulate export data |
afterExport($format, $scope) |
Post-export logic |
emptyStateView() |
Custom blade view for empty state (null = default) |
setFilterShow($id, $show) |
Show/hide filter dynamically |
setFilterData($id, $data) |
Update filter options dynamically |
resetFilter($id) |
Reset filter to "All" |
Engine — Fluent API
| Method | Description |
|---|---|
withColumnIndex($head) |
Auto-numbered row column |
withColumn($head, $key, ...) |
Data column ($uppercase, $th, $sort, $gravity, $editable, $visible, $rules, $showOn) |
withCustomColumn($head, $callback, ...) |
Custom column ($key, $sort, $visible, $showOn) |
enableBulk($callback) |
Bulk select with per-row callback |
enableEditable($callback) |
Per-row/column editable control ($data, $i, $column_key) → bool |
enableExpand($callback) |
Expandable row content |
setDefaultOrder($key, $dir) |
Default sort |
addOrderBy($key, $dir) |
Additional sort |
getActionView($data, $i, $editable, $deletable) |
Render edit/delete buttons |
getExpandView($data, $fields) |
Render expand grid |
withColumnImage($head, $key, ...) |
Image column with lightbox ($width, $height, $previewClass, $fallback, $sort, $visible, $showOn) |
Filter Factory
| Method | Description |
|---|---|
MrCatzDataTableFilter::create($id, $label, $data, $value, $option, $key, $show, $condition) |
Standard filter |
MrCatzDataTableFilter::createWithCallback($id, $label, $data, $value, $option, $callback, $show) |
Callback filter |
->get() |
Finalize (required) |
Troubleshooting
Search not working on JOIN queries
Use table-prefixed column keys in withColumn() and add configTable():
->withColumn('Name', 'products.name') // not just 'name'
Filter not showing
Make sure ->get() is called on every filter:
MrCatzDataTableFilter::create(...)->get(); // don't forget ->get()
Export error: Class not found
composer require maatwebsite/excel # Excel composer require barryvdh/laravel-dompdf # PDF
Data not refreshing after save/delete
Make sure to call dispatch_to_view() at the end of saveData() / dropData().
Override method error "Declaration must be compatible"
Do not add return types on overridden methods:
public function saveData() { ... } // correct public function saveData(): void { ... } // wrong
Requirements
- PHP >= 8.1
- Laravel >= 11.0
- Livewire >= 3.0
- Tailwind CSS + DaisyUI
- Icon set: Default (inline SVG), Heroicons, Material Icons, Font Awesome, or custom
License
MIT