Table

Styled parts for native HTML tables.

import { … } from '@inlaylab/ivorie/table'
NameEmailRole
Ada Lovelaceada@example.comAdmin
Alan Turingalan@example.comEditor
Grace Hoppergrace@example.comViewer
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

CLInpm
npx ivorie add 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

ItemTotal
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

A list of recent invoices.
InvoiceStatus
INV001Paid
INV002Pending
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

NameRole
Ada LovelaceAdmin
Alan TuringEditor
Grace HopperViewer
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

Users
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, td and caption elements, so roles, colspan, rowspan and scope work as in plain HTML.
  • Add a <caption ivTableCaption> to name the table.
  • Selection by data-state="selected" is visual only. Also expose it with aria-selected or a checkbox.

Notes

  • A row gets a muted background on hover, when data-state="selected", and when it contains an element with aria-expanded="true".
  • The last row in tbody has no bottom border.
  • For sorting, filtering, pagination and selection, see Data Table. There is no virtualization.