
Laravel Tailwind DataTable with Vanilla JS (No jQuery)
If you’re building a Laravel application and using Tailwind CSS v4, you probably want your tables to look clean, modern, and fast. But adding features like search, sort, pagination, and CSV export can quickly become a headache — especially if you're trying to avoid jQuery or bloated plugins.
That’s where vanillajs-datatable
comes in.
This zero-dependency JavaScript library helps you turn plain HTML tables into fully interactive Tailwind-styled DataTables — perfect for Laravel projects using Blade, Livewire, or Inertia.
Why vanillajs-datatable?
- ✅ Works out of the box with Tailwind CSS v4
- ✅ No jQuery or frontend framework needed
- ✅ Integrates with Laravel Blade templates
- ✅ Adds search, sorting, pagination, export features and many more
- ✅ Fully customizable with Tailwind classes
Step-by-Step Installation (Copy-Paste)
Install
npm i vanillajs-datatable
Import once in resources/js/app.js
import DataTable from 'vanillajs-datatable';
window.DataTable = DataTable;
Tailwind purge safety (skip if using CDN)
/* resources/css/app.css */
@source '../../node_modules/vanillajs-datatable/dist/**/*.{js,mjs}';
Add a Table in Your Blade View
<div class="overflow-x-auto">
<table id="userTable" class="min-w-full divide-y divide-gray-200">
<!-- rows injected by JS -->
</table>
</div>
Initialize the DataTable
const columns = [
{
name: "id",
label: "ID",
render: (value) => `#${value}`,
},
{
name: "name",
label: "Name",
group: "personal",
highlightable: true,
render: (value) => `<strong>${value}</strong>`,
},
{
name: "email",
label: "Email",
},
{
name: "created_at",
label: "Created At",
render: (val) => new Date(val).toLocaleDateString(),
},
{
name: "actions",
label: "Actions",
render: (value, row) => {
return `
<button class="btn btn-sm py-2 px-3 rounded-lg bg-blue-600 hover:bg-blue-700 text-white mr-2"
onclick="showDetails(${row.id})">
Show
</button>
<button class="btn btn-sm py-2 px-3 rounded-lg bg-yellow-500 hover:bg-yellow-600 text-white"
onclick="editRecord(${row.id})">
Edit
</button>
`;
},
},
];
const table = new DataTable({
tableId: "datatable",
url: "/users/datatable",
dataSrc: "users",
columns: columns,
columnGroups: columnGroups,
exportable: {
enabled: true,
buttons: {
print: true,
excel: true,
csv: true,
pdf: true,
},
title: {
print: "Leads Printable Report",
pdf: "Leads PDF Export",
},
chunkSize: {
print: 1000,
pdf: 500,
excel: 500,
csv: 500,
},
fileName: {
print: "leads_report",
pdf: "leads_pdf",
excel: "leads_excel",
csv: "leads_csv",
},
pdfOptions: {
orientation: "portrait", // or 'portrait'
unit: "mm", // "pt" (points), "mm", "m", "px".
format: "a4",
theme: "grid", // 'striped', 'grid'
watermark: {
text: "test",
opacity: 0.2,
angle: 45,
},
},
footer: true,
},
columnFiltering: true,
filterableColumns: ["name", "email"],
perPageSelector: true,
perPageOptions: [10, 25, 50, 100],
baseTheme: "daisyui", // "daisyui", "bootstrap", "tailwind"
pagination: true,
// paginationType: 'simple', // 'simple' (Previous/Next) or 'detailed' (numbered pagination)
searchable: true,
// searchDelay: 800, // Search delay in milliseconds
sortable: true,
// sortableColumns: ['id', 'name'],
loading: {
show: false,
elementId: "custom-loading-spinner",
delay: 1000,
},
selection: {
enabled: false,
mode: "single", // 'single'|'multiple'
rowClass: "row-selected",
backgroundClass: "bg-blue-100",
},
// saveState: true,
// saveStateDuration: 60 * 60 * 1000, // 1 hour
// theme: {
// table: "w-full border border-green-100 rounded-lg shadow-sm",
// header: "bg-green-600 text-white",
// headerCell: "px-4 py-3 font-medium text-white",
// row: "hover:bg-green-50",
// cell: "px-4 py-3 text-green-900",
// paginationButtonActive: "bg-green-600 text-white"
// },
});
Controller
Create an API endpoint with proper validation and filtering:
use Illuminate\Http\Request;
use Illuminate\Support\Str;
public function index(Request $request)
{
$request->validate([
'search' => 'nullable|string|max:500',
'sortBy' => 'nullable|string|in:id,name,email,created_at',
'order' => 'nullable|string|in:asc,desc',
'perPage' => 'nullable|integer|min:5|max:100',
]);
$query = User::query();
// Search functionality
if ($searchTerm = $request->input('search')) {
$query->where(function ($q) use ($searchTerm) {
$q->where('name', 'like', "%{$searchTerm}%")
->orWhere('email', 'like', "%{$searchTerm}%");
});
}
// Sorting
$sortBy = $request->input('sortBy', 'id');
$order = $request->input('order', 'desc');
$query->orderBy($sortBy, $order);
$perPage = $request->input('perPage', 10);
$paginated = $query->paginate($perPage);
return response()->json([
'users' => $paginated->items(),
'total' => $paginated->total(),
'current_page' => $paginated->currentPage(),
'last_page' => $paginated->lastPage(),
]);
}
📘 Visit full documentation: Docs