Products DataTable
Complete product management datatable with server-side search (HTMX), server-side pagination, bulk actions (delete, change status), table/card view toggle, column visibility, sorting, inline edit (side sheet), and delete confirmation (modal). A single, fully interactive example.
Full Products DataTable
Preview
Show / Hide Columns
0
selected
| SKU | Name | Category | Price | Stock | Status | Actions | |
|---|---|---|---|---|---|---|---|
| PRD-001 | Wireless HeadphonesBluetooth 5.0 |
Electronics | $79.99 | 142 | Active | ||
| PRD-002 | Cotton T-ShirtOrganic cotton |
Clothing | $24.50 | 389 | Active | ||
| PRD-003 | Organic Green Tea100 bags |
Food & Beverage | $12.99 | 56 | Draft | ||
| PRD-004 | Smart Watch ProGPS + LTE |
Electronics | $299.00 | 18 | Active | ||
| PRD-005 | Ceramic Plant PotMedium, white |
Home & Garden | $34.00 | 0 | Archived |
Show
per page
Showing 1-5 of 24 products
<div class="datatable glass" id="products-table">
<!-- Toolbar -->
<div class="datatable-toolbar">
<div class="datatable-toolbar-start">
<label class="input input-sm datatable-search">
<svg class="w-4 h-4 opacity-50" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z"/></svg>
<input type="search" placeholder="Search products..." id="dt-search">
</label>
<select class="select select-sm" id="dt-filter-status">
<option value="">All Status</option>
<option value="active">Active</option>
<option value="draft">Draft</option>
<option value="archived">Archived</option>
</select>
<select class="select select-sm" id="dt-filter-category">
<option value="">All Categories</option>
<option value="electronics">Electronics</option>
<option value="clothing">Clothing</option>
<option value="food">Food & Beverage</option>
<option value="home">Home & Garden</option>
</select>
</div>
<div class="datatable-toolbar-end">
<!-- Add Product -->
<button class="btn btn-sm btn-circle color-primary" id="dt-add-btn" title="Add Product">
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M12 4.5v15m7.5-7.5h-15"/></svg>
</button>
<!-- Column Visibility -->
<details class="dropdown" id="dt-col-dropdown">
<summary class="datatable-column-btn" title="Columns">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M10.5 6h9.75M10.5 6a1.5 1.5 0 1 1-3 0m3 0a1.5 1.5 0 1 0-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 0 1-3 0m3 0a1.5 1.5 0 0 0-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 0 1-3 0m3 0a1.5 1.5 0 0 0-3 0m-9.75 0h9.75"/></svg>
</summary>
<div class="dropdown-menu dropdown-menu-right" style="min-width: 200px;">
<div class="dropdown-header">Show / Hide Columns</div>
<label class="dropdown-item cursor-pointer"><input type="checkbox" class="checkbox checkbox-sm" data-col="sku" checked> SKU</label>
<label class="dropdown-item cursor-pointer"><input type="checkbox" class="checkbox checkbox-sm" data-col="name" checked disabled> Name</label>
<label class="dropdown-item cursor-pointer"><input type="checkbox" class="checkbox checkbox-sm" data-col="category" checked> Category</label>
<label class="dropdown-item cursor-pointer"><input type="checkbox" class="checkbox checkbox-sm" data-col="price" checked> Price</label>
<label class="dropdown-item cursor-pointer"><input type="checkbox" class="checkbox checkbox-sm" data-col="stock" checked> Stock</label>
<label class="dropdown-item cursor-pointer"><input type="checkbox" class="checkbox checkbox-sm" data-col="status" checked> Status</label>
</div>
</details>
<!-- Export -->
<details class="dropdown" id="dt-export-dropdown">
<summary class="datatable-export-btn" title="Export">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3"/></svg>
</summary>
<div class="dropdown-menu dropdown-menu-right">
<button class="dropdown-item">
<svg class="w-4 h-4 opacity-50" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"/></svg>
Export as CSV
</button>
<button class="dropdown-item">
<svg class="w-4 h-4 opacity-50" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"/></svg>
Export as Excel
</button>
<div class="dropdown-divider"></div>
<button class="dropdown-item">
<svg class="w-4 h-4 opacity-50" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M6.72 13.829c-.24.03-.48.062-.72.096m.72-.096a42.415 42.415 0 0 1 10.56 0m-10.56 0L6.34 18m10.94-4.171c.24.03.48.062.72.096m-.72-.096L17.66 18m0 0 .229 2.523a1.125 1.125 0 0 1-1.12 1.227H7.231c-.662 0-1.18-.568-1.12-1.227L6.34 18m11.318 0h1.091A2.25 2.25 0 0 0 21 15.75V9.456c0-1.081-.768-2.015-1.837-2.175a48.055 48.055 0 0 0-1.913-.247M6.34 18H5.25A2.25 2.25 0 0 1 3 15.75V9.456c0-1.081.768-2.015 1.837-2.175a48.041 48.041 0 0 1 1.913-.247m0 0a48.112 48.112 0 0 1 10.5 0m-10.5 0V5.625c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v3.284m-7.5 0h7.5"/></svg>
Print
</button>
</div>
</details>
<!-- Import -->
<button class="datatable-export-btn" id="dt-import-btn" title="Import">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5m-13.5-9L12 3m0 0 4.5 4.5M12 3v13.5"/></svg>
</button>
<!-- View Toggle -->
<div class="datatable-view-toggle" id="dt-view-toggle">
<button class="datatable-view-btn datatable-view-btn-active" data-view="table" title="Table view">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M3.375 19.5h17.25m-17.25 0a1.125 1.125 0 0 1-1.125-1.125M3.375 19.5h7.5c.621 0 1.125-.504 1.125-1.125m-9.75 0V5.625m0 12.75v-1.5c0-.621.504-1.125 1.125-1.125m18.375 2.625V5.625m0 12.75c0 .621-.504 1.125-1.125 1.125m1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125m0 3.75h-7.5A1.125 1.125 0 0 1 12 18.375m9.75-12.75c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125m19.5 0v1.5c0 .621-.504 1.125-1.125 1.125M2.25 5.625v1.5c0 .621.504 1.125 1.125 1.125m0 0h17.25m-17.25 0h7.5c.621 0 1.125.504 1.125 1.125M3.375 8.25c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125m17.25-3.75h-7.5c-.621 0-1.125.504-1.125 1.125m8.625-1.125c.621 0 1.125.504 1.125 1.125v1.5c0 .621-.504 1.125-1.125 1.125m-17.25 0h7.5m-7.5 0c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125M12 10.875v-1.5m0 1.5c0 .621-.504 1.125-1.125 1.125M12 10.875c0 .621.504 1.125 1.125 1.125m-2.25 0c.621 0 1.125.504 1.125 1.125M13.125 12h7.5m-7.5 0c-.621 0-1.125.504-1.125 1.125M20.625 12c.621 0 1.125.504 1.125 1.125v1.5c0 .621-.504 1.125-1.125 1.125m-17.25 0h7.5M12 14.625v-1.5m0 1.5c0 .621-.504 1.125-1.125 1.125M12 14.625c0 .621.504 1.125 1.125 1.125m-2.25 0c.621 0 1.125.504 1.125 1.125m0 0v1.5c0 .621-.504 1.125-1.125 1.125"/></svg>
</button>
<button class="datatable-view-btn" data-view="cards" title="Card view">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M3.75 6A2.25 2.25 0 0 1 6 3.75h2.25A2.25 2.25 0 0 1 10.5 6v2.25a2.25 2.25 0 0 1-2.25 2.25H6a2.25 2.25 0 0 1-2.25-2.25V6Zm0 9.75A2.25 2.25 0 0 1 6 13.5h2.25a2.25 2.25 0 0 1 2.25 2.25V18a2.25 2.25 0 0 1-2.25 2.25H6A2.25 2.25 0 0 1 3.75 18v-2.25ZM13.5 6a2.25 2.25 0 0 1 2.25-2.25H18A2.25 2.25 0 0 1 20.25 6v2.25A2.25 2.25 0 0 1 18 10.5h-2.25a2.25 2.25 0 0 1-2.25-2.25V6Zm0 9.75a2.25 2.25 0 0 1 2.25-2.25H18a2.25 2.25 0 0 1 2.25 2.25V18A2.25 2.25 0 0 1 18 20.25h-2.25A2.25 2.25 0 0 1 13.5 18v-2.25Z"/></svg>
</button>
</div>
</div>
</div>
<!-- Bulk Actions Bar (hidden by default) -->
<div class="datatable-bulk hidden" id="dt-bulk-bar">
<div class="datatable-bulk-info">
<span class="datatable-bulk-count" id="dt-bulk-count">0</span>
<span>selected</span>
</div>
<div class="datatable-bulk-actions">
<button class="datatable-bulk-btn" id="dt-bulk-status" title="Change status">
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182"/></svg>
Set Active
</button>
<button class="datatable-bulk-btn datatable-bulk-btn-danger" id="dt-bulk-delete" title="Delete selected">
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"/></svg>
Delete
</button>
<button class="datatable-bulk-clear" id="dt-bulk-clear" title="Clear selection">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M6 18 18 6M6 6l12 12"/></svg>
</button>
</div>
</div>
<!-- Table Body -->
<div class="datatable-body" id="dt-body">
<table class="datatable-table" id="dt-table">
<thead class="datatable-thead">
<tr>
<th class="datatable-th datatable-th-checkbox">
<label class="checkbox checkbox-sm"><input type="checkbox" id="dt-select-all"><span class="checkbox-mark"></span></label>
</th>
<th class="datatable-th datatable-th-sortable" data-col="sku" data-sort="sku">SKU</th>
<th class="datatable-th datatable-th-sortable datatable-th-sorted" data-col="name" data-sort="name">
Name
<span class="datatable-sort-icon"><svg class="w-3.5 h-3.5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M4.5 15.75l7.5-7.5 7.5 7.5"/></svg></span>
</th>
<th class="datatable-th" data-col="category">Category</th>
<th class="datatable-th datatable-th-sortable datatable-th-right" data-col="price" data-sort="price">Price</th>
<th class="datatable-th datatable-th-sortable datatable-th-center" data-col="stock" data-sort="stock">Stock</th>
<th class="datatable-th datatable-th-center" data-col="status">Status</th>
<th class="datatable-th datatable-th-actions">Actions</th>
</tr>
</thead>
<tbody class="datatable-tbody" id="dt-tbody">
<tr class="datatable-tr" data-id="1" data-name="Wireless Headphones" data-sku="PRD-001" data-price="79.99" data-stock="142" data-category="electronics" data-status="active" data-description="Bluetooth 5.0 wireless headphones with noise cancellation">
<td class="datatable-td datatable-td-checkbox"><label class="checkbox checkbox-sm"><input type="checkbox" class="dt-row-check"><span class="checkbox-mark"></span></label></td>
<td class="datatable-td" data-label="SKU">PRD-001</td>
<td class="datatable-td" data-label="Name"><div class="flex items-center gap-3"><div class="w-8 h-8 bg-primary/10 rounded-lg flex items-center justify-center"><svg class="w-4 h-4 text-primary" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M15.75 10.5V6a3.75 3.75 0 1 0-7.5 0v4.5m11.356-1.993 1.263 12c.07.665-.45 1.243-1.119 1.243H4.25a1.125 1.125 0 0 1-1.12-1.243l1.264-12A1.125 1.125 0 0 1 5.513 7.5h12.974c.576 0 1.059.435 1.119 1.007ZM8.625 10.5a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm7.5 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z"/></svg></div><div><span class="font-medium">Wireless Headphones</span><span class="text-xs text-base-content/50 block">Bluetooth 5.0</span></div></div></td>
<td class="datatable-td" data-label="Category"><span class="badge badge-ghost badge-sm">Electronics</span></td>
<td class="datatable-td datatable-td-right" data-label="Price"><span class="font-medium">$79.99</span></td>
<td class="datatable-td datatable-td-center" data-label="Stock">142</td>
<td class="datatable-td datatable-td-center" data-label="Status"><span class="badge color-success badge-sm">Active</span></td>
<td class="datatable-td datatable-td-actions"><div class="datatable-row-actions"><button class="datatable-row-action dt-edit-btn" title="Edit"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125"/></svg></button><button class="datatable-row-action datatable-row-action-danger dt-delete-btn" title="Delete"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"/></svg></button></div></td>
</tr>
<tr class="datatable-tr" data-id="2" data-name="Cotton T-Shirt" data-sku="PRD-002" data-price="24.50" data-stock="389" data-category="clothing" data-status="active" data-description="Organic cotton t-shirt, available in multiple sizes">
<td class="datatable-td datatable-td-checkbox"><label class="checkbox checkbox-sm"><input type="checkbox" class="dt-row-check"><span class="checkbox-mark"></span></label></td>
<td class="datatable-td" data-label="SKU">PRD-002</td>
<td class="datatable-td" data-label="Name"><div class="flex items-center gap-3"><div class="w-8 h-8 bg-warning/10 rounded-lg flex items-center justify-center"><svg class="w-4 h-4 text-warning" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M15.75 10.5V6a3.75 3.75 0 1 0-7.5 0v4.5m11.356-1.993 1.263 12c.07.665-.45 1.243-1.119 1.243H4.25a1.125 1.125 0 0 1-1.12-1.243l1.264-12A1.125 1.125 0 0 1 5.513 7.5h12.974c.576 0 1.059.435 1.119 1.007ZM8.625 10.5a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm7.5 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z"/></svg></div><div><span class="font-medium">Cotton T-Shirt</span><span class="text-xs text-base-content/50 block">Organic cotton</span></div></div></td>
<td class="datatable-td" data-label="Category"><span class="badge badge-ghost badge-sm">Clothing</span></td>
<td class="datatable-td datatable-td-right" data-label="Price"><span class="font-medium">$24.50</span></td>
<td class="datatable-td datatable-td-center" data-label="Stock">389</td>
<td class="datatable-td datatable-td-center" data-label="Status"><span class="badge color-success badge-sm">Active</span></td>
<td class="datatable-td datatable-td-actions"><div class="datatable-row-actions"><button class="datatable-row-action dt-edit-btn" title="Edit"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125"/></svg></button><button class="datatable-row-action datatable-row-action-danger dt-delete-btn" title="Delete"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"/></svg></button></div></td>
</tr>
<tr class="datatable-tr" data-id="3" data-name="Organic Green Tea" data-sku="PRD-003" data-price="12.99" data-stock="56" data-category="food" data-status="draft" data-description="Premium organic green tea, 100 bags per box">
<td class="datatable-td datatable-td-checkbox"><label class="checkbox checkbox-sm"><input type="checkbox" class="dt-row-check"><span class="checkbox-mark"></span></label></td>
<td class="datatable-td" data-label="SKU">PRD-003</td>
<td class="datatable-td" data-label="Name"><div class="flex items-center gap-3"><div class="w-8 h-8 bg-success/10 rounded-lg flex items-center justify-center"><svg class="w-4 h-4 text-success" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M15.75 10.5V6a3.75 3.75 0 1 0-7.5 0v4.5m11.356-1.993 1.263 12c.07.665-.45 1.243-1.119 1.243H4.25a1.125 1.125 0 0 1-1.12-1.243l1.264-12A1.125 1.125 0 0 1 5.513 7.5h12.974c.576 0 1.059.435 1.119 1.007ZM8.625 10.5a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm7.5 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z"/></svg></div><div><span class="font-medium">Organic Green Tea</span><span class="text-xs text-base-content/50 block">100 bags</span></div></div></td>
<td class="datatable-td" data-label="Category"><span class="badge badge-ghost badge-sm">Food & Beverage</span></td>
<td class="datatable-td datatable-td-right" data-label="Price"><span class="font-medium">$12.99</span></td>
<td class="datatable-td datatable-td-center" data-label="Stock">56</td>
<td class="datatable-td datatable-td-center" data-label="Status"><span class="badge color-warning badge-sm">Draft</span></td>
<td class="datatable-td datatable-td-actions"><div class="datatable-row-actions"><button class="datatable-row-action dt-edit-btn" title="Edit"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125"/></svg></button><button class="datatable-row-action datatable-row-action-danger dt-delete-btn" title="Delete"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"/></svg></button></div></td>
</tr>
<tr class="datatable-tr" data-id="4" data-name="Smart Watch Pro" data-sku="PRD-004" data-price="299.00" data-stock="18" data-category="electronics" data-status="active" data-description="Smart watch with GPS + LTE connectivity">
<td class="datatable-td datatable-td-checkbox"><label class="checkbox checkbox-sm"><input type="checkbox" class="dt-row-check"><span class="checkbox-mark"></span></label></td>
<td class="datatable-td" data-label="SKU">PRD-004</td>
<td class="datatable-td" data-label="Name"><div class="flex items-center gap-3"><div class="w-8 h-8 bg-error/10 rounded-lg flex items-center justify-center"><svg class="w-4 h-4 text-error" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M15.75 10.5V6a3.75 3.75 0 1 0-7.5 0v4.5m11.356-1.993 1.263 12c.07.665-.45 1.243-1.119 1.243H4.25a1.125 1.125 0 0 1-1.12-1.243l1.264-12A1.125 1.125 0 0 1 5.513 7.5h12.974c.576 0 1.059.435 1.119 1.007ZM8.625 10.5a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm7.5 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z"/></svg></div><div><span class="font-medium">Smart Watch Pro</span><span class="text-xs text-base-content/50 block">GPS + LTE</span></div></div></td>
<td class="datatable-td" data-label="Category"><span class="badge badge-ghost badge-sm">Electronics</span></td>
<td class="datatable-td datatable-td-right" data-label="Price"><span class="font-medium">$299.00</span></td>
<td class="datatable-td datatable-td-center" data-label="Stock">18</td>
<td class="datatable-td datatable-td-center" data-label="Status"><span class="badge color-success badge-sm">Active</span></td>
<td class="datatable-td datatable-td-actions"><div class="datatable-row-actions"><button class="datatable-row-action dt-edit-btn" title="Edit"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125"/></svg></button><button class="datatable-row-action datatable-row-action-danger dt-delete-btn" title="Delete"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"/></svg></button></div></td>
</tr>
<tr class="datatable-tr" data-id="5" data-name="Ceramic Plant Pot" data-sku="PRD-005" data-price="34.00" data-stock="0" data-category="home" data-status="archived" data-description="Medium white ceramic plant pot for indoor use">
<td class="datatable-td datatable-td-checkbox"><label class="checkbox checkbox-sm"><input type="checkbox" class="dt-row-check"><span class="checkbox-mark"></span></label></td>
<td class="datatable-td" data-label="SKU">PRD-005</td>
<td class="datatable-td" data-label="Name"><div class="flex items-center gap-3"><div class="w-8 h-8 bg-secondary/10 rounded-lg flex items-center justify-center"><svg class="w-4 h-4 text-secondary" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M15.75 10.5V6a3.75 3.75 0 1 0-7.5 0v4.5m11.356-1.993 1.263 12c.07.665-.45 1.243-1.119 1.243H4.25a1.125 1.125 0 0 1-1.12-1.243l1.264-12A1.125 1.125 0 0 1 5.513 7.5h12.974c.576 0 1.059.435 1.119 1.007ZM8.625 10.5a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm7.5 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z"/></svg></div><div><span class="font-medium">Ceramic Plant Pot</span><span class="text-xs text-base-content/50 block">Medium, white</span></div></div></td>
<td class="datatable-td" data-label="Category"><span class="badge badge-ghost badge-sm">Home & Garden</span></td>
<td class="datatable-td datatable-td-right" data-label="Price"><span class="font-medium">$34.00</span></td>
<td class="datatable-td datatable-td-center" data-label="Stock">0</td>
<td class="datatable-td datatable-td-center" data-label="Status"><span class="badge badge-ghost badge-sm">Archived</span></td>
<td class="datatable-td datatable-td-actions"><div class="datatable-row-actions"><button class="datatable-row-action dt-edit-btn" title="Edit"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125"/></svg></button><button class="datatable-row-action datatable-row-action-danger dt-delete-btn" title="Delete"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"/></svg></button></div></td>
</tr>
</tbody>
</table>
</div>
<!-- Footer / Pagination -->
<div class="datatable-footer">
<div class="datatable-per-page">
Show
<select id="dt-per-page">
<option>10</option>
<option selected>25</option>
<option>50</option>
<option>100</option>
</select>
per page
</div>
<span class="datatable-info">Showing 1-5 of 24 products</span>
<nav class="pagination pagination-sm">
<button class="pagination-btn pagination-prev" disabled>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5"/></svg>
</button>
<button class="pagination-btn pagination-active">1</button>
<button class="pagination-btn">2</button>
<button class="pagination-btn">3</button>
<button class="pagination-btn pagination-next">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5"/></svg>
</button>
</nav>
</div>
</div> Requires: ux.min.css
<!-- The tailwind equivalent uses the same structure but replaces
semantic classes with utility classes. Due to the complexity
of a full datatable, the semantic version IS the recommended
approach. Below is the core structure with utilities: -->
<div class="flex flex-col w-full rounded-2xl overflow-hidden glass">
<!-- Toolbar -->
<div class="flex items-center justify-between gap-3 px-5 py-2 bg-base-200 border-b border-base-300 flex-wrap">
<div class="flex items-center gap-2">
<label class="input input-sm min-w-48">
<svg class="w-4 h-4 opacity-50" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z"/></svg>
<input type="search" placeholder="Search products...">
</label>
<select class="select select-sm">
<option>All Status</option>
<option>Active</option>
<option>Draft</option>
</select>
</div>
<div class="flex items-center gap-2">
<!-- Add (icon-only), Columns, Export, Import, View toggle -->
<button class="btn btn-sm btn-circle color-primary" title="Add Product">+</button>
</div>
</div>
<!-- Table -->
<div class="flex-1 overflow-auto">
<table class="w-full" style="border-collapse: collapse;">
<thead class="sticky top-0 z-10">
<tr>
<th class="w-12 pl-3 pr-2 py-3 text-left text-xs font-semibold uppercase tracking-wide opacity-60 bg-base-200 border-b-2 border-base-300">
<input type="checkbox">
</th>
<th class="px-5 py-3 text-left text-xs font-semibold uppercase tracking-wide opacity-60 bg-base-200 border-b-2 border-base-300 cursor-pointer select-none hover:text-primary hover:opacity-100">SKU</th>
<th class="px-5 py-3 text-left text-xs font-semibold uppercase tracking-wide bg-base-200 border-b-2 border-base-300 cursor-pointer select-none text-primary">Name <span class="inline-flex ml-1">↑</span></th>
<th class="px-5 py-3 text-left text-xs font-semibold uppercase tracking-wide opacity-60 bg-base-200 border-b-2 border-base-300">Category</th>
<th class="px-5 py-3 text-right text-xs font-semibold uppercase tracking-wide opacity-60 bg-base-200 border-b-2 border-base-300 cursor-pointer select-none hover:text-primary hover:opacity-100">Price</th>
<th class="px-5 py-3 text-center text-xs font-semibold uppercase tracking-wide opacity-60 bg-base-200 border-b-2 border-base-300">Stock</th>
<th class="px-5 py-3 text-center text-xs font-semibold uppercase tracking-wide opacity-60 bg-base-200 border-b-2 border-base-300">Status</th>
<th class="w-20 px-5 py-3 text-center text-xs font-semibold uppercase tracking-wide opacity-60 bg-base-200 border-b-2 border-base-300">Actions</th>
</tr>
</thead>
<tbody class="bg-base-100">
<tr class="transition-colors hover:bg-primary/5">
<td class="w-12 pl-3 pr-2 py-3 text-sm align-middle border-b border-base-300"><input type="checkbox"></td>
<td class="px-5 py-3 text-sm align-middle border-b border-base-300">PRD-001</td>
<td class="px-5 py-3 text-sm align-middle border-b border-base-300">
<div class="flex items-center gap-3">
<div class="w-8 h-8 bg-primary/10 rounded-lg flex items-center justify-center">
<svg class="w-4 h-4 text-primary" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M15.75 10.5V6a3.75 3.75 0 1 0-7.5 0v4.5m11.356-1.993 1.263 12c.07.665-.45 1.243-1.119 1.243H4.25a1.125 1.125 0 0 1-1.12-1.243l1.264-12A1.125 1.125 0 0 1 5.513 7.5h12.974c.576 0 1.059.435 1.119 1.007ZM8.625 10.5a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm7.5 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z"/></svg>
</div>
<div>
<span class="font-medium">Wireless Headphones</span>
<span class="text-xs text-base-content/50 block">Bluetooth 5.0</span>
</div>
</div>
</td>
<td class="px-5 py-3 text-sm align-middle border-b border-base-300"><span class="badge badge-ghost badge-sm">Electronics</span></td>
<td class="px-5 py-3 text-sm align-middle border-b border-base-300 text-right font-medium">$79.99</td>
<td class="px-5 py-3 text-sm align-middle border-b border-base-300 text-center">142</td>
<td class="px-5 py-3 text-sm align-middle border-b border-base-300 text-center"><span class="badge color-success badge-sm">Active</span></td>
<td class="w-20 px-5 py-3 text-sm align-middle border-b border-base-300 text-center">
<div class="flex items-center justify-center gap-1">
<button class="flex items-center justify-center w-8 h-8 bg-transparent border-none rounded-lg cursor-pointer opacity-60 hover:opacity-100 hover:bg-base-200" title="Edit">✎</button>
<button class="flex items-center justify-center w-8 h-8 bg-transparent border-none rounded-lg cursor-pointer opacity-60 hover:opacity-100 hover:bg-error/10 hover:text-error" title="Delete">🗑</button>
</div>
</td>
</tr>
<!-- More rows... -->
</tbody>
</table>
</div>
<!-- Footer -->
<div class="flex items-center justify-between px-5 py-3 border-t border-base-300 flex-wrap gap-3">
<div class="flex items-center gap-2 text-sm opacity-60">
Show <select class="px-2 py-1 text-sm rounded-lg border border-base-300 bg-base-100"><option>25</option></select> per page
</div>
<span class="text-sm opacity-60">Showing 1-5 of 24</span>
<nav class="flex items-center gap-1">
<button class="inline-flex items-center justify-center w-9 h-9 rounded-md text-sm font-medium bg-transparent border-none opacity-40" disabled>‹</button>
<button class="inline-flex items-center justify-center w-9 h-9 rounded-md text-sm font-semibold bg-primary text-primary-content">1</button>
<button class="inline-flex items-center justify-center w-9 h-9 rounded-md text-sm font-medium bg-transparent border-none hover:bg-base-200 cursor-pointer">2</button>
<button class="inline-flex items-center justify-center w-9 h-9 rounded-md text-sm font-medium bg-transparent border-none hover:bg-base-200 cursor-pointer">3</button>
<button class="inline-flex items-center justify-center w-9 h-9 rounded-md text-sm font-medium bg-transparent border-none hover:bg-base-200 cursor-pointer">›</button>
</nav>
</div>
</div>
<!-- Edit Side Sheet (UX Components) -->
<div class="sheet-backdrop is-open">
<div class="side-sheet side-sheet-right">
<div class="side-sheet-header">
<h3 class="sheet-title">Edit Product</h3>
<button class="sheet-close">×</button>
</div>
<div class="side-sheet-content">
<form class="flex flex-col gap-4 p-6">
<div><label class="text-sm font-medium mb-1 block">Name</label><input type="text" class="input input-sm w-full"></div>
<div><label class="text-sm font-medium mb-1 block">SKU</label><input type="text" class="input input-sm w-full"></div>
<div class="grid grid-cols-2 gap-3">
<div><label class="text-sm font-medium mb-1 block">Price</label><input type="number" class="input input-sm w-full"></div>
<div><label class="text-sm font-medium mb-1 block">Stock</label><input type="number" class="input input-sm w-full"></div>
</div>
<div><label class="text-sm font-medium mb-1 block">Category</label><select class="select select-sm w-full"><option>Electronics</option></select></div>
<div><label class="text-sm font-medium mb-1 block">Status</label><select class="select select-sm w-full"><option>Active</option></select></div>
<div><label class="text-sm font-medium mb-1 block">Description</label><textarea class="textarea textarea-sm w-full" rows="3"></textarea></div>
</form>
</div>
<div class="side-sheet-footer">
<div class="flex justify-end gap-2">
<button class="btn btn-ghost btn-sm">Cancel</button>
<button class="btn btn-sm color-primary">Save Changes</button>
</div>
</div>
</div>
</div>
<!-- Delete Confirmation Modal (UX Components) -->
<div class="modal-backdrop" data-state="open">
<div class="modal modal-sm">
<div class="modal-header">
<h3 class="modal-title">Delete Product</h3>
<button class="modal-close">×</button>
</div>
<div class="modal-body">
<p class="text-sm text-base-content/70">Are you sure you want to delete <strong>Wireless Headphones</strong>? This action cannot be undone.</p>
</div>
<div class="modal-footer">
<button class="btn btn-ghost btn-sm">Cancel</button>
<button class="btn btn-sm color-error">Delete</button>
</div>
</div>
</div> Requires: tw.min.css
// =============================================
// Complete HTMX + Django Template Pattern
// =============================================
// This shows the REAL production pattern used
// in Django + HTMX apps with UX components.
// =============================================
// 1. MAIN TEMPLATE (content.html)
// Alpine.js state + datatable + overlays
// =============================================
// {% load i18n %}
//
// <div x-data="{
// view: '{{ current_view|default:'table' }}',
// panelOpen: false,
// selectedIds: [],
// selectAll: false,
// deleteConfirm: false,
// deleteTarget: null,
//
// toggleSelect(id) {
// const idx = this.selectedIds.indexOf(id);
// if (idx > -1) this.selectedIds.splice(idx, 1);
// else this.selectedIds.push(id);
// },
// toggleAll(ids) {
// if (this.selectAll) this.selectedIds = [];
// else this.selectedIds = [...ids];
// this.selectAll = !this.selectAll;
// },
// clearSelection() {
// this.selectedIds = [];
// this.selectAll = false;
// },
// openPanel(url) {
// htmx.ajax('GET', url, {
// target: '#panel-content',
// swap: 'innerHTML'
// });
// this.panelOpen = true;
// },
// closePanel() {
// this.panelOpen = false;
// },
// confirmDelete() {
// if (this.deleteTarget) {
// htmx.ajax('POST', this.deleteTarget.url, {
// target: '#datatable-body',
// swap: 'innerHTML',
// headers: {
// 'X-CSRFToken': document.querySelector(
// '[name=csrfmiddlewaretoken]'
// )?.value
// }
// });
// }
// this.deleteConfirm = false;
// this.deleteTarget = null;
// }
// }">
//
// <div class="datatable glass" id="products-table">
//
// <!-- Toolbar -->
// <div class="datatable-toolbar">
// <div class="flex flex-wrap gap-2 w-full items-center">
//
// <!-- Search -->
// <label class="input input-sm datatable-search
// order-1 w-full sm:w-auto sm:flex-1">
// <svg><!-- search icon --></svg>
// <input type="search" name="q"
// hx-get="{% url 'app:products' %}"
// hx-target="#datatable-body"
// hx-include="#products-table"
// hx-trigger="input changed delay:300ms">
// </label>
//
// <!-- Filters -->
// <select name="status"
// class="select select-sm"
// hx-get="{% url 'app:products' %}"
// hx-target="#datatable-body"
// hx-include="#products-table"
// hx-trigger="change">
// <option value="">All Status</option>
// <option value="active">Active</option>
// </select>
//
// <!-- Add Button -->
// <button class="btn btn-sm color-primary"
// @click="openPanel(
// '{% url 'app:product_add' %}'
// )">
// Add Product
// </button>
//
// <!-- View Toggle -->
// <div class="datatable-view-toggle">
// <button class="datatable-view-btn"
// :class="{ 'datatable-view-btn-active':
// view === 'table' }"
// @click="view = 'table'"
// hx-get="{% url 'app:products' %}"
// hx-target="#datatable-body"
// hx-include="#products-table"
// hx-vals='{"view": "table"}'>
// Table
// </button>
// <button class="datatable-view-btn"
// :class="{ 'datatable-view-btn-active':
// view === 'cards' }"
// @click="view = 'cards'"
// hx-get="{% url 'app:products' %}"
// hx-target="#datatable-body"
// hx-include="#products-table"
// hx-vals='{"view": "cards"}'>
// Cards
// </button>
// </div>
// </div>
// </div>
//
// <!-- Bulk Actions -->
// <div class="datatable-bulk"
// x-show="selectedIds.length > 0" x-cloak>
// <div class="datatable-bulk-info">
// <span class="datatable-bulk-count"
// x-text="selectedIds.length"></span>
// <span>selected</span>
// </div>
// <div class="datatable-bulk-actions">
// <button class="datatable-bulk-btn"
// hx-post="{% url 'app:bulk_action' %}"
// hx-target="#datatable-body"
// :hx-vals="JSON.stringify({
// ids: selectedIds.join(','),
// action: 'activate'
// })"
// @htmx:after-request="clearSelection()">
// Activate
// </button>
// <button class="datatable-bulk-btn
// datatable-bulk-btn-danger"
// hx-post="{% url 'app:bulk_action' %}"
// hx-target="#datatable-body"
// :hx-vals="JSON.stringify({
// ids: selectedIds.join(','),
// action: 'delete'
// })"
// @htmx:after-request="clearSelection()">
// Delete
// </button>
// </div>
// </div>
//
// {% csrf_token %}
// <input type="hidden" name="sort"
// value="{{ sort_field|default:'name' }}">
// <input type="hidden" name="dir"
// value="{{ sort_dir|default:'asc' }}">
// <input type="hidden" name="view" :value="view">
//
// <!-- HTMX swap target -->
// <div id="datatable-body">
// {% include "app/partials/list.html" %}
// </div>
// </div>
//
// <!-- SIDE SHEET (UX Component) -->
// <div class="sheet-backdrop"
// :class="{ 'is-open': panelOpen }"
// @click.self="closePanel()">
// <div class="side-sheet side-sheet-right"
// id="panel-content">
// <!-- Loaded via htmx.ajax() -->
// </div>
// </div>
//
// <!-- DELETE MODAL (UX Component) -->
// <div class="modal-backdrop"
// :data-state="deleteConfirm ? 'open' : 'closed'"
// @click.self="deleteConfirm = false">
// <div class="modal modal-sm">
// <div class="modal-header">
// <h3 class="modal-title">Delete Product</h3>
// <button class="modal-close"
// @click="deleteConfirm = false">
// ×
// </button>
// </div>
// <div class="modal-body">
// <p class="text-sm text-base-content/70">
// Are you sure you want to delete
// <strong x-text="deleteTarget?.name">
// </strong>?
// </p>
// </div>
// <div class="modal-footer">
// <button class="btn btn-ghost btn-sm"
// @click="deleteConfirm = false">
// Cancel
// </button>
// <button class="btn btn-sm color-error"
// @click="confirmDelete()">
// Delete
// </button>
// </div>
// </div>
// </div>
//
// </div>
// =============================================
// 2. LIST PARTIAL (partials/list.html)
// Returned by ALL HTMX requests (search, filter,
// sort, paginate, delete, toggle status)
// =============================================
// {% load i18n %}
//
// {% if products %}
// <div class="datatable-body">
// <table class="datatable-table">
// <thead class="datatable-thead">
// <tr>
// <th class="datatable-th datatable-th-checkbox">
// <label class="checkbox checkbox-sm">
// <input type="checkbox"
// :checked="selectAll"
// @click="toggleAll([
// {% for p in products %}
// '{{ p.id }}'
// {% if not forloop.last %},{% endif %}
// {% endfor %}
// ])">
// <span class="checkbox-mark"></span>
// </label>
// </th>
// <th class="datatable-th
// datatable-th-sortable
// {% if sort == 'name' %}
// datatable-th-sorted
// {% endif %}"
// hx-get="{% url 'app:products' %}
// ?sort=name&dir=
// {% if sort == 'name' and
// dir == 'asc' %}
// desc{% else %}asc{% endif %}"
// hx-target="#datatable-body"
// hx-include="#products-table">
// Name
// <span class="datatable-sort-icon">
// <!-- chevron svg -->
// </span>
// </th>
// <!-- more <th> columns... -->
// </tr>
// </thead>
// <tbody class="datatable-tbody">
// {% for p in products %}
// <tr class="datatable-tr"
// :class="{ 'datatable-tr-selected':
// selectedIds.includes('{{ p.id }}') }">
// <td class="datatable-td
// datatable-td-checkbox">
// <label class="checkbox checkbox-sm">
// <input type="checkbox"
// :checked="selectedIds
// .includes('{{ p.id }}')"
// @click="toggleSelect('{{ p.id }}')">
// <span class="checkbox-mark"></span>
// </label>
// </td>
// <td class="datatable-td">
// <div class="flex items-center gap-3
// cursor-pointer"
// @click="openPanel(
// '{% url 'app:product_edit' p.id %}'
// )">
// <span class="font-medium">
// {{ p.name }}
// </span>
// </div>
// </td>
// <!-- more <td> columns... -->
// <td class="datatable-td
// datatable-td-actions">
// <div class="datatable-row-actions">
// <button class="datatable-row-action"
// @click="openPanel(
// '{% url 'app:product_edit' p.id %}'
// )">
// Edit
// </button>
// <button class="datatable-row-action
// datatable-row-action-danger"
// @click="deleteTarget = {
// id: '{{ p.id }}',
// name: '{{ p.name }}',
// url: '{% url 'app:product_delete'
// p.id %}'
// }; deleteConfirm = true">
// Delete
// </button>
// </div>
// </td>
// </tr>
// {% endfor %}
// </tbody>
// </table>
// </div>
// <!-- Pagination footer -->
// <div class="datatable-footer">
// <div class="datatable-per-page">
// Show
// <select name="per_page"
// hx-get="{% url 'app:products' %}"
// hx-target="#datatable-body"
// hx-include="#products-table"
// hx-trigger="change">
// <option value="10">10</option>
// <option value="25" selected>25</option>
// </select>
// per page
// </div>
// <span class="datatable-info">
// Showing {{ page.start_index }}-{{ page.end_index }}
// of {{ page.paginator.count }}
// </span>
// <nav class="pagination pagination-sm">
// <!-- pagination buttons with hx-get -->
// </nav>
// </div>
// {% else %}
// <div class="datatable-empty">
// <div class="datatable-empty-icon">...</div>
// <div class="datatable-empty-title">
// No products found
// </div>
// </div>
// {% endif %}
// =============================================
// 3. EDIT PARTIAL (partials/edit.html)
// Loaded via htmx.ajax() into side-sheet
// =============================================
// {% load i18n %}
//
// <div class="side-sheet-header">
// <h3 class="sheet-title">Edit Product</h3>
// <button class="sheet-close"
// @click="closePanel()">
// ×
// </button>
// </div>
//
// <div class="side-sheet-content">
// <form id="edit-form"
// hx-post="{% url 'app:product_edit' obj.id %}"
// hx-target="#datatable-body"
// hx-swap="innerHTML"
// @htmx:after-request="closePanel()"
// class="flex flex-col gap-4 p-6">
// {% csrf_token %}
// <div>
// <label class="text-sm font-medium mb-1 block">
// Name
// </label>
// <input type="text" name="name"
// class="input input-sm w-full"
// value="{{ obj.name }}" required>
// </div>
// <div class="grid grid-cols-2 gap-3">
// <div>
// <label class="text-sm font-medium mb-1 block">
// Price
// </label>
// <input type="number" name="price"
// class="input input-sm w-full"
// value="{{ obj.price }}" step="0.01">
// </div>
// <div>
// <label class="text-sm font-medium mb-1 block">
// Stock
// </label>
// <input type="number" name="stock"
// class="input input-sm w-full"
// value="{{ obj.stock }}">
// </div>
// </div>
// <div>
// <label class="text-sm font-medium mb-1 block">
// Category
// </label>
// <select name="category"
// class="select select-sm w-full">
// {% for cat in categories %}
// <option value="{{ cat.id }}"
// {% if obj.category_id == cat.id %}
// selected{% endif %}>
// {{ cat.name }}
// </option>
// {% endfor %}
// </select>
// </div>
// </form>
// </div>
//
// <div class="side-sheet-footer">
// <div class="flex justify-end gap-2">
// <button class="btn btn-ghost btn-sm"
// @click="closePanel()">
// Cancel
// </button>
// <button type="submit" form="edit-form"
// class="btn btn-sm color-primary">
// Save
// </button>
// </div>
// </div>
// =============================================
// KEY PATTERNS:
// =============================================
// - sheet-backdrop + .is-open (Alpine :class)
// - modal-backdrop + data-state (Alpine :data-state)
// - htmx.ajax() to load partials into side-sheet
// - All HTMX requests return #datatable-body content
// - hx-include="#table-id" sends all filter state
// - @htmx:after-request="closePanel()" auto-closes
// - {% csrf_token %} inside datatable for POST reqs Edit Product
Delete Product
Are you sure you want to delete this product? This action cannot be undone.
Import Products
Upload a CSV or Excel file with columns: Name, SKU, Price, Stock, Category, Status
Features Demonstrated
search Server-side search with HTMX, debounced 300ms
filters Status and category dropdowns with HTMX triggers
view toggle Switch between table rows and card grid layout
columns Show/hide columns via dropdown checkboxes
bulk select Select all / individual rows with checkbox
bulk actions Delete multiple or change status in batch
sorting Click column headers to sort asc/desc via HTMX
pagination Server-side pagination with per-page selector
edit Right side sheet with product edit form
delete Confirmation modal before deleting a product
row actions Edit and delete buttons per row
export Export to CSV, Excel, or Print
import Import products from CSV or Excel file via modal
glass Glass morphism effect on the datatable container
responsive Mobile-optimized toolbar with flex-wrap and order
Components Used
datatable Container, header, toolbar, body, footer, bulk bar, view toggle, column toggle
sheet Right side sheet for product edit form (sheet-backdrop, side-sheet-right)
modal Delete confirmation dialog (modal-backdrop, modal, modal-body, modal-footer)
pagination Page navigation with prev/next and page numbers
dropdown Column visibility and export menus (native details/summary)
badge Status indicators (Active, Draft, Archived) and category tags
checkbox Row selection and column visibility toggles
input / select / textarea Search field, filter selects, and edit form fields