Table
Styled parts for native HTML tables.
import { … } from '@inlaylab/ivorie/table'| Name | Role | |
|---|---|---|
| Ada Lovelace | ada@example.com | Admin |
| Alan Turing | alan@example.com | Editor |
| Grace Hopper | grace@example.com | Viewer |
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { IV_TABLE_IMPORTS } from '@inlaylab/ivorie/table';
@Component({
selector: 'demo-table',
imports: [IV_TABLE_IMPORTS],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<iv-table class="w-[28rem]">
<thead ivTableHeader>
<tr ivTableRow>
<th ivTableHead>Name</th>
<th ivTableHead>Email</th>
<th ivTableHead>Role</th>
</tr>
</thead>
<tbody ivTableBody>
@for (user of users; track user.email) {
<tr ivTableRow>
<td ivTableCell>{{ user.name }}</td>
<td ivTableCell>{{ user.email }}</td>
<td ivTableCell>{{ user.role }}</td>
</tr>
}
</tbody>
</iv-table>
`,
})
export default class TableDemo {
protected readonly users = [
{ name: 'Ada Lovelace', email: 'ada@example.com', role: 'Admin' },
{ name: 'Alan Turing', email: 'alan@example.com', role: 'Editor' },
{ name: 'Grace Hopper', email: 'grace@example.com', role: 'Viewer' },
];
}
Installation
npx ivorie add tableimport { … } from '@inlaylab/ivorie/table'Ports the shadcn/ui Table. iv-table wraps a native <table> in a horizontally scrollable container. The other parts are attribute directives on native table elements, so the HTML table semantics stay intact.
Usage
import { IV_TABLE_IMPORTS } from '@inlaylab/ivorie/table';<iv-table>
<thead ivTableHeader>
<tr ivTableRow>
<th ivTableHead>Name</th>
<th ivTableHead>Email</th>
</tr>
</thead>
<tbody ivTableBody>
@for (user of users; track user.id) {
<tr ivTableRow>
<td ivTableCell>{{ user.name }}</td>
<td ivTableCell>{{ user.email }}</td>
</tr>
}
</tbody>
</iv-table>Examples
Footer
| Item | Total |
|---|---|
| Subscription | $25.00 |
| Support | $10.00 |
| Total | $35.00 |
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { IV_TABLE_IMPORTS } from '@inlaylab/ivorie/table';
@Component({
selector: 'demo-table-footer',
imports: [IV_TABLE_IMPORTS],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<iv-table class="w-80">
<thead ivTableHeader>
<tr ivTableRow>
<th ivTableHead>Item</th>
<th ivTableHead class="text-right">Total</th>
</tr>
</thead>
<tbody ivTableBody>
<tr ivTableRow>
<td ivTableCell>Subscription</td>
<td ivTableCell class="text-right">$25.00</td>
</tr>
<tr ivTableRow>
<td ivTableCell>Support</td>
<td ivTableCell class="text-right">$10.00</td>
</tr>
</tbody>
<tfoot ivTableFooter>
<tr ivTableRow>
<td ivTableCell>Total</td>
<td ivTableCell class="text-right">$35.00</td>
</tr>
</tfoot>
</iv-table>
`,
})
export default class TableFooterDemo {}
Caption
| Invoice | Status |
|---|---|
| INV001 | Paid |
| INV002 | Pending |
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { IV_TABLE_IMPORTS } from '@inlaylab/ivorie/table';
@Component({
selector: 'demo-table-caption',
imports: [IV_TABLE_IMPORTS],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<iv-table class="w-80">
<caption ivTableCaption>A list of recent invoices.</caption>
<thead ivTableHeader>
<tr ivTableRow>
<th ivTableHead>Invoice</th>
<th ivTableHead>Status</th>
</tr>
</thead>
<tbody ivTableBody>
<tr ivTableRow>
<td ivTableCell>INV001</td>
<td ivTableCell>Paid</td>
</tr>
<tr ivTableRow>
<td ivTableCell>INV002</td>
<td ivTableCell>Pending</td>
</tr>
</tbody>
</iv-table>
`,
})
export default class TableCaptionDemo {}
Selected rows
| Name | Role |
|---|---|
| Ada Lovelace | Admin |
| Alan Turing | Editor |
| Grace Hopper | Viewer |
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { IV_TABLE_IMPORTS } from '@inlaylab/ivorie/table';
@Component({
selector: 'demo-table-selected-rows',
imports: [IV_TABLE_IMPORTS],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<iv-table class="w-80">
<thead ivTableHeader>
<tr ivTableRow>
<th ivTableHead>Name</th>
<th ivTableHead>Role</th>
</tr>
</thead>
<tbody ivTableBody>
@for (user of users; track user.name) {
<tr
ivTableRow
class="cursor-pointer"
[attr.data-state]="selected() === user.name ? 'selected' : null"
(click)="selected.set(user.name)"
>
<td ivTableCell>{{ user.name }}</td>
<td ivTableCell>{{ user.role }}</td>
</tr>
}
</tbody>
</iv-table>
`,
})
export default class TableSelectedRowsDemo {
protected readonly selected = signal('Alan Turing');
protected readonly users = [
{ name: 'Ada Lovelace', role: 'Admin' },
{ name: 'Alan Turing', role: 'Editor' },
{ name: 'Grace Hopper', role: 'Viewer' },
];
}
In a card
| Ada Lovelace |
| Alan Turing |
| Grace Hopper |
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { IV_CARD_IMPORTS } from '@inlaylab/ivorie/card';
import { IV_TABLE_IMPORTS } from '@inlaylab/ivorie/table';
@Component({
selector: 'demo-table-in-a-card',
imports: [IV_CARD_IMPORTS, IV_TABLE_IMPORTS],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<iv-card class="w-96">
<iv-card-header class="border-b">
<iv-card-title>Users</iv-card-title>
</iv-card-header>
<iv-card-content class="px-0">
<iv-table>
<tbody ivTableBody>
@for (user of users; track user) {
<tr ivTableRow>
<td ivTableCell>{{ user }}</td>
</tr>
}
</tbody>
</iv-table>
</iv-card-content>
</iv-card>
`,
})
export default class TableInACardDemo {
protected readonly users = ['Ada Lovelace', 'Alan Turing', 'Grace Hopper'];
}
API
Every part also accepts class.
iv-table
The host is the scroll container. class and layout style the inner <table>.
| Name | Type | Default | Description |
|---|---|---|---|
layout |
'auto' | 'fixed' |
'auto' |
fixed sets table-layout: fixed. |
Directives
Each directive goes on the matching native element. They have no inputs besides class.
| Selector | Element |
|---|---|
thead[ivTableHeader] |
<thead> |
tbody[ivTableBody] |
<tbody> |
tfoot[ivTableFooter] |
<tfoot> |
tr[ivTableRow] |
<tr> |
th[ivTableHead] |
<th> |
td[ivTableCell] |
<td> |
caption[ivTableCaption] |
<caption> |
Accessibility
- Parts render native
table,thead,tbody,tfoot,tr,th,tdandcaptionelements, so roles,colspan,rowspanandscopework as in plain HTML. - Add a
<caption ivTableCaption>to name the table. - Selection by
data-state="selected"is visual only. Also expose it witharia-selectedor a checkbox.
Notes
- A row gets a muted background on hover, when
data-state="selected", and when it contains an element witharia-expanded="true". - The last row in
tbodyhas no bottom border. - For sorting, filtering, pagination and selection, see Data Table. There is no virtualization.