Table
A styled <table>. Write the <thead> and <tbody> yourself; the component sets the classes and the scroll wrapper.
<twig:Tabler:Table> <thead> <tr><th>Name</th><th>Email</th><th>Role</th></tr> </thead> <tbody> <tr><td>Jane Cooper</td><td>jane@example.com</td><td>Admin</td></tr> <tr><td>Wade Warren</td><td>wade@example.com</td><td>Editor</td></tr> <tr><td>Esther Howard</td><td>esther@example.com</td><td>Viewer</td></tr> </tbody></twig:Tabler:Table><div class="table-responsive"> <table class="table"> <thead> <tr> <th>Name</th> <th>Email</th> <th>Role</th> </tr> </thead> <tbody> <tr> <td>Jane Cooper</td> <td>jane@example.com</td> <td>Admin</td> </tr> <tr> <td>Wade Warren</td> <td>wade@example.com</td> <td>Editor</td> </tr> <tr> <td>Esther Howard</td> <td>esther@example.com</td> <td>Viewer</td> </tr> </tbody> </table></div>Styles
Section titled “Styles”striped adds zebra rows, hover highlights the row under the pointer, bordered draws a border around every cell.
Combine them freely:
<twig:Tabler:Table striped hover> <thead> <tr><th>Invoice</th><th>Customer</th><th class="text-end">Amount</th></tr> </thead> <tbody> <tr><td>INV-0142</td><td>Acme Corp</td><td class="text-end">€1,250.00</td></tr> <tr><td>INV-0143</td><td>Globex</td><td class="text-end">€480.00</td></tr> <tr><td>INV-0144</td><td>Initech</td><td class="text-end">€3,100.00</td></tr> </tbody></twig:Tabler:Table><twig:Tabler:Table bordered> <thead> <tr><th>Invoice</th><th>Customer</th><th class="text-end">Amount</th></tr> </thead> <tbody> <tr><td>INV-0142</td><td>Acme Corp</td><td class="text-end">€1,250.00</td></tr> <tr><td>INV-0143</td><td>Globex</td><td class="text-end">€480.00</td></tr> </tbody></twig:Tabler:Table><div class="table-responsive"> <table class="table table-striped table-hover"> <thead> <tr> <th>Invoice</th> <th>Customer</th> <th class="text-end">Amount</th> </tr> </thead> <tbody> <tr> <td>INV-0142</td> <td>Acme Corp</td> <td class="text-end">€1,250.00</td> </tr> <tr> <td>INV-0143</td> <td>Globex</td> <td class="text-end">€480.00</td> </tr> <tr> <td>INV-0144</td> <td>Initech</td> <td class="text-end">€3,100.00</td> </tr> </tbody> </table></div><div class="table-responsive"> <table class="table table-bordered"> <thead> <tr> <th>Invoice</th> <th>Customer</th> <th class="text-end">Amount</th> </tr> </thead> <tbody> <tr> <td>INV-0142</td> <td>Acme Corp</td> <td class="text-end">€1,250.00</td> </tr> <tr> <td>INV-0143</td> <td>Globex</td> <td class="text-end">€480.00</td> </tr> </tbody> </table></div>size="sm" tightens the cell padding, borderless removes the borders, and nowrap keeps the cell text on one line.
transparent removes the background of the header cells, and center centers the cells horizontally:
<twig:Tabler:Table size="sm" borderless="{{ true }}" nowrap="{{ true }}"> <thead> <tr><th>Date</th><th>Event</th><th>User</th></tr> </thead> <tbody> <tr><td>2026-10-12 09:42</td><td>Invoice INV-0144 sent to Initech</td><td>jane@example.com</td></tr> <tr><td>2026-10-12 09:15</td><td>Customer Globex updated</td><td>wade@example.com</td></tr> <tr><td>2026-10-11 17:03</td><td>Payment of €480.00 received</td><td>system</td></tr> </tbody></twig:Tabler:Table><div class="table-responsive"> <table class="table table-borderless table-nowrap table-sm"> <thead> <tr> <th>Date</th> <th>Event</th> <th>User</th> </tr> </thead> <tbody> <tr> <td>2026-10-12 09:42</td> <td>Invoice INV-0144 sent to Initech</td> <td>jane@example.com</td> </tr> <tr> <td>2026-10-12 09:15</td> <td>Customer Globex updated</td> <td>wade@example.com</td> </tr> <tr> <td>2026-10-11 17:03</td> <td>Payment of €480.00 received</td> <td>system</td> </tr> </tbody> </table></div>Vertical centering
Section titled “Vertical centering”vcenter centers the cells vertically, for rows with avatars or buttons:
<twig:Tabler:Table vcenter> <thead> <tr><th>User</th><th>Role</th><th></th></tr> </thead> <tbody> {% for user in [ {name: 'Jane Cooper', email: 'jane@example.com', role: 'Admin', avatar: '/examples/avatar-1.jpg'}, {name: 'Wade Warren', email: 'wade@example.com', role: 'Editor', avatar: '/examples/avatar-2.jpg'}, ] %} <tr> <td> <div class="d-flex align-items-center"> <twig:Tabler:Avatar image="{{ user.avatar }}" class="me-2" /> <div> <div>{{ user.name }}</div> <div class="text-secondary">{{ user.email }}</div> </div> </div> </td> <td>{{ user.role }}</td> <td class="text-end"><a href="#">Edit</a></td> </tr> {% endfor %} </tbody></twig:Tabler:Table><div class="table-responsive"> <table class="table table-vcenter"> <thead> <tr> <th>User</th> <th>Role</th> <th></th> </tr> </thead> <tbody> <tr> <td> <div class="d-flex align-items-center"> <span class="avatar me-2" style="background-image: url(/examples/avatar-1.jpg)"></span> <div> <div>Jane Cooper</div> <div class="text-secondary">jane@example.com</div> </div> </div> </td> <td>Admin</td> <td class="text-end"><a href="#">Edit</a></td> </tr> <tr> <td> <div class="d-flex align-items-center"> <span class="avatar me-2" style="background-image: url(/examples/avatar-2.jpg)"></span> <div> <div>Wade Warren</div> <div class="text-secondary">wade@example.com</div> </div> </div> </td> <td>Editor</td> <td class="text-end"><a href="#">Edit</a></td> </tr> </tbody> </table></div>Stacked rows on phones
Section titled “Stacked rows on phones”mobile stacks each row into a block below a breakpoint (table-mobile-{breakpoint}): sm, md, lg, xl or
xxl. The header row is hidden, and each cell is labelled by its data-label attribute: write the cells with
Table:Cell and its label prop. Narrow the window to see it:
<twig:Tabler:Table mobile="md"> <thead> <tr><th>Invoice</th><th>Customer</th><th>Status</th><th class="text-end">Amount</th></tr> </thead> <tbody> <tr> <twig:Tabler:Table:Cell label="Invoice">INV-0142</twig:Tabler:Table:Cell> <twig:Tabler:Table:Cell label="Customer">Acme Corp</twig:Tabler:Table:Cell> <twig:Tabler:Table:Cell label="Status"><twig:Tabler:Badge variant="success" light="{{ true }}">Paid</twig:Tabler:Badge></twig:Tabler:Table:Cell> <twig:Tabler:Table:Cell label="Amount" class="text-end">€1,250.00</twig:Tabler:Table:Cell> </tr> <tr> <twig:Tabler:Table:Cell label="Invoice">INV-0143</twig:Tabler:Table:Cell> <twig:Tabler:Table:Cell label="Customer">Globex</twig:Tabler:Table:Cell> <twig:Tabler:Table:Cell label="Status"><twig:Tabler:Badge variant="warning" light="{{ true }}">Pending</twig:Tabler:Badge></twig:Tabler:Table:Cell> <twig:Tabler:Table:Cell label="Amount" class="text-end">€480.00</twig:Tabler:Table:Cell> </tr> </tbody></twig:Tabler:Table><div class="table-responsive"> <table class="table table-mobile-md"> <thead> <tr> <th>Invoice</th> <th>Customer</th> <th>Status</th> <th class="text-end">Amount</th> </tr> </thead> <tbody> <tr> <td data-label="Invoice">INV-0142</td> <td data-label="Customer">Acme Corp</td> <td data-label="Status"><span class="badge bg-success-lt">Paid</span></td> <td data-label="Amount" class="text-end">€1,250.00</td> </tr> <tr> <td data-label="Invoice">INV-0143</td> <td data-label="Customer">Globex</td> <td data-label="Status"><span class="badge bg-warning-lt">Pending</span></td> <td data-label="Amount" class="text-end">€480.00</td> </tr> </tbody> </table></div>Truncated cells
Section titled “Truncated cells”Table:Cell with truncate cuts a long text with an ellipsis instead of stretching the table: the cell takes the
remaining width (td-truncate) and wraps its content in a <div class="text-truncate">. Here nowrap keeps the
other cells on one line:
<twig:Tabler:Table nowrap="{{ true }}"> <thead> <tr><th>Invoice</th><th>Note</th><th class="text-end">Amount</th></tr> </thead> <tbody> <tr> <td>INV-0142</td> <twig:Tabler:Table:Cell truncate="{{ true }}">Paid in two parts: the first by bank transfer on October 9, the second by card after the customer asked for a corrected invoice with the new billing address.</twig:Tabler:Table:Cell> <td class="text-end">€1,250.00</td> </tr> <tr> <td>INV-0143</td> <twig:Tabler:Table:Cell truncate="{{ true }}">Reminder sent.</twig:Tabler:Table:Cell> <td class="text-end">€480.00</td> </tr> </tbody></twig:Tabler:Table><div class="table-responsive"> <table class="table table-nowrap"> <thead> <tr> <th>Invoice</th> <th>Note</th> <th class="text-end">Amount</th> </tr> </thead> <tbody> <tr> <td>INV-0142</td> <td class="td-truncate"> <div class="text-truncate"> Paid in two parts: the first by bank transfer on October 9, the second by card after the customer asked for a corrected invoice with the new billing address. </div> </td> <td class="text-end">€1,250.00</td> </tr> <tr> <td>INV-0143</td> <td class="td-truncate"><div class="text-truncate">Reminder sent.</div></td> <td class="text-end">€480.00</td> </tr> </tbody> </table></div>Sortable headers and selectable rows
Section titled “Sortable headers and selectable rows”Table:Sort renders a header button with a sort arrow (table-sort), to put in a <th>. field is rendered as
data-sort, and direction (asc or desc) marks the current sort. The classes only style the header: sort the rows
yourself, e.g. with List.js, which reads data-sort, or with a link that reloads the page sorted.
selectable highlights the rows whose Table:Check is checked (table-selectable), with CSS only. Table:Check
renders the row checkbox; give each one an aria-label naming its row. A “select all” checkbox in the header needs
your own JavaScript:
<twig:Tabler:Table selectable="{{ true }}" vcenter="{{ true }}"> <thead> <tr> <th class="w-1"><twig:Tabler:Table:Check aria-label="Select all invoices" /></th> <th><twig:Tabler:Table:Sort field="invoice" direction="asc">Invoice</twig:Tabler:Table:Sort></th> <th><twig:Tabler:Table:Sort field="customer">Customer</twig:Tabler:Table:Sort></th> <th class="text-end"><twig:Tabler:Table:Sort field="amount">Amount</twig:Tabler:Table:Sort></th> </tr> </thead> <tbody> {% for invoice in [ {number: 'INV-0142', customer: 'Acme Corp', amount: '€1,250.00', checked: true}, {number: 'INV-0143', customer: 'Globex', amount: '€480.00', checked: false}, {number: 'INV-0144', customer: 'Initech', amount: '€3,100.00', checked: false}, ] %} <tr> <td> <twig:Tabler:Table:Check name="invoices[]" value="{{ invoice.number }}" checked="{{ invoice.checked }}" aria-label="Select {{ invoice.number }}" /> </td> <td>{{ invoice.number }}</td> <td>{{ invoice.customer }}</td> <td class="text-end">{{ invoice.amount }}</td> </tr> {% endfor %} </tbody></twig:Tabler:Table><div class="table-responsive"> <table class="table table-vcenter table-selectable"> <thead> <tr> <th class="w-1"> <input type="checkbox" class="form-check-input table-selectable-check" aria-label="Select all invoices" /> </th> <th><button type="button" class="table-sort asc" data-sort="invoice">Invoice</button></th> <th><button type="button" class="table-sort" data-sort="customer">Customer</button></th> <th class="text-end"><button type="button" class="table-sort" data-sort="amount">Amount</button></th> </tr> </thead> <tbody> <tr> <td> <input type="checkbox" class="form-check-input table-selectable-check" name="invoices[]" value="INV-0142" checked="" aria-label="Select INV-0142" /> </td> <td>INV-0142</td> <td>Acme Corp</td> <td class="text-end">€1,250.00</td> </tr> <tr> <td> <input type="checkbox" class="form-check-input table-selectable-check" name="invoices[]" value="INV-0143" aria-label="Select INV-0143" /> </td> <td>INV-0143</td> <td>Globex</td> <td class="text-end">€480.00</td> </tr> <tr> <td> <input type="checkbox" class="form-check-input table-selectable-check" name="invoices[]" value="INV-0144" aria-label="Select INV-0144" /> </td> <td>INV-0144</td> <td>Initech</td> <td class="text-end">€3,100.00</td> </tr> </tbody> </table></div>In a card
Section titled “In a card”card runs the table edge to edge in a card, aligned with the card padding and without a bottom margin
(card-table). Classes and other attributes go to the <table>, not to the wrapper:
<twig:Tabler:Card> <twig:Tabler:Card:Header> <twig:Tabler:Card:Title>Recent deployments</twig:Tabler:Card:Title> </twig:Tabler:Card:Header> <twig:Tabler:Table card="{{ true }}"> <thead> <tr><th>Environment</th><th>Commit</th><th>Deployed</th></tr> </thead> <tbody> <tr><td>Production</td><td><code>a3f9c21</code></td><td>2 hours ago</td></tr> <tr><td>Staging</td><td><code>e81b0d4</code></td><td>Yesterday</td></tr> </tbody> </twig:Tabler:Table></twig:Tabler:Card><div class="card"> <div class="card-header"><h3 class="card-title">Recent deployments</h3></div> <div class="table-responsive"> <table class="table card-table"> <thead> <tr> <th>Environment</th> <th>Commit</th> <th>Deployed</th> </tr> </thead> <tbody> <tr> <td>Production</td> <td><code>a3f9c21</code></td> <td>2 hours ago</td> </tr> <tr> <td>Staging</td> <td><code>e81b0d4</code></td> <td>Yesterday</td> </tr> </tbody> </table> </div></div>Without the scroll wrapper
Section titled “Without the scroll wrapper”The table is wrapped in a <div class="table-responsive"> by default. Pass responsive="{{ false }}" to render the
bare <table> (see boolean props):
<twig:Tabler:Table responsive="{{ false }}"> <thead> <tr><th>Key</th><th>Value</th></tr> </thead> <tbody> <tr><td>APP_ENV</td><td>prod</td></tr> <tr><td>APP_DEBUG</td><td>0</td></tr> </tbody></twig:Tabler:Table><table class="table"> <thead> <tr> <th>Key</th> <th>Value</th> </tr> </thead> <tbody> <tr> <td>APP_ENV</td> <td>prod</td> </tr> <tr> <td>APP_DEBUG</td> <td>0</td> </tr> </tbody></table>| Prop | Type | Default | Description |
|---|---|---|---|
responsive |
bool | true |
Wraps the table in <div class="table-responsive"> |
vcenter |
bool | false |
Centers cells vertically (table-vcenter) |
center |
bool | false |
Centers cells horizontally (table-center) |
striped |
bool | false |
Zebra rows (table-striped) |
hover |
bool | false |
Highlights the hovered row (table-hover) |
bordered |
bool | false |
Borders on every cell (table-bordered) |
borderless |
bool | false |
No borders (table-borderless) |
transparent |
bool | false |
No background on the header cells (table-transparent) |
nowrap |
bool | false |
Cell text does not wrap (table-nowrap) |
selectable |
bool | false |
Highlights the rows whose Table:Check is checked (table-selectable) |
size |
string | 'md' |
sm md: sm tightens the cell padding (table-sm) |
mobile |
string? | null |
sm md lg xl xxl: stacks the rows below this breakpoint (table-mobile-*) |
card |
bool | false |
Edge to edge inside a card, no bottom margin (card-table) |
Other attributes go to the <table>.
Table:Sort:
| Prop | Type | Default | Description |
|---|---|---|---|
field |
string? | null |
Sort key, rendered as data-sort |
direction |
string? | null |
asc desc: marks the current sort direction |
Other attributes go to the <button class="table-sort">.
Table:Cell:
| Prop | Type | Default | Description |
|---|---|---|---|
label |
string? | null |
Column name shown when mobile stacks the row (data-label) |
truncate |
bool | false |
Cuts the content with an ellipsis (td-truncate, content in a .text-truncate div) |
Other attributes go to the <td>. Table:Check has no props: its attributes (name, value,
checked, aria-label…) go to the <input type="checkbox">.
Sub-components
Section titled “Sub-components”| Component | Renders |
|---|---|
Table:Sort |
<button type="button" class="table-sort">: a sortable header |
Table:Check |
<input type="checkbox" class="form-check-input table-selectable-check">: a row checkbox |
Table:Cell |
<td>: a cell with a mobile label or a truncated content |
Blocks
Section titled “Blocks”| Block | Default |
|---|---|
content |
Empty: the children, the <thead> and <tbody>, go here |
Table:Sort and Table:Cell have an empty content block too, for the header label and the cell content.
