table

A responsive table component for displaying structured data.

PreviousNext
A list of your recent invoices.
InvoiceStatusMethodAmount
INV001
Paid Credit Card
$250.00
INV002
Pending PayPal
$150.00
INV003
Unpaid Bank Transfer
$350.00
INV004
Paid Credit Card
$450.00
INV005
Paid PayPal
$550.00
INV006
Pending Bank Transfer
$200.00
INV007
Unpaid Credit Card
$300.00
Total$2,250.00
import { ChangeDetectionStrategy, Component, computed, signal } from '@angular/core';

import { ZardTableImports } from '@/shared/components/table/table.imports';

export interface Invoice {
  id: string;
  status: string;
  method: string;
  amount: number;
}

@Component({
  selector: 'z-demo-table-preview',
  imports: [ZardTableImports],
  template: `
    <table z-table>
      <caption z-table-caption>A list of your recent invoices.</caption>
      <thead z-table-header>
        <tr z-table-row>
          <th z-table-head scope="col">Invoice</th>
          <th z-table-head scope="col">Status</th>
          <th z-table-head scope="col">Method</th>
          <th z-table-head scope="col" class="text-end">Amount</th>
        </tr>
      </thead>
      <tbody z-table-body>
        @for (invoice of invoices(); track invoice.id) {
          <tr z-table-row>
            <td z-table-cell>
              <div>{{ invoice.id }}</div>
            </td>
            <td z-table-cell>
              {{ invoice.status }}
            </td>
            <td z-table-cell>
              {{ invoice.method }}
            </td>
            <td z-table-cell>
              <div class="text-end font-medium">{{ formatCurrency(invoice.amount) }}</div>
            </td>
          </tr>
        } @empty {
          <tr z-table-row>
            <td z-table-cell [attr.colspan]="4" class="h-24 text-center">No results.</td>
          </tr>
        }
      </tbody>
      <tfoot z-table-footer>
        <tr z-table-row>
          <td z-table-cell colspan="3">Total</td>
          <td z-table-cell class="text-end">{{ formatCurrency(total()) }}</td>
        </tr>
      </tfoot>
    </table>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  host: {
    class: 'block w-full overflow-x-auto',
  },
})
export class ZardDemoTablePreviewComponent {
  readonly invoices = signal<Invoice[]>([
    {
      id: 'INV001',
      status: 'Paid',
      method: 'Credit Card',
      amount: 250,
    },
    {
      id: 'INV002',
      status: 'Pending',
      method: 'PayPal',
      amount: 150,
    },
    {
      id: 'INV003',
      status: 'Unpaid',
      method: 'Bank Transfer',
      amount: 350,
    },
    {
      id: 'INV004',
      status: 'Paid',
      method: 'Credit Card',
      amount: 450,
    },
    {
      id: 'INV005',
      status: 'Paid',
      method: 'PayPal',
      amount: 550,
    },
    {
      id: 'INV006',
      status: 'Pending',
      method: 'Bank Transfer',
      amount: 200,
    },
    {
      id: 'INV007',
      status: 'Unpaid',
      method: 'Credit Card',
      amount: 300,
    },
  ]);

  readonly total = computed(() =>
    this.invoices().reduce((sum: number, invoice: { amount: number }) => sum + invoice.amount, 0),
  );

  formatCurrency(amount: number): string {
    return new Intl.NumberFormat('en-US', {
      style: 'currency',
      currency: 'USD',
    }).format(amount);
  }
}

Installation

Copy
npx zard-cli@latest add table

Usage

import { ZardTableImports } from '@/shared/components/table/table.imports';
Copy
<table z-table>
  <thead z-table-header>
    <tr z-table-row>
      <th z-table-head>Name</th>
      <th z-table-head>Status</th>
    </tr>
  </thead>
  <tbody z-table-body>
    <tr z-table-row>
      <td z-table-cell>Item 1</td>
      <td z-table-cell>Active</td>
    </tr>
  </tbody>
</table>
Copy

Composition

Use the following composition to build a table:

z-table
├── caption[z-table-caption]
├── thead[z-table-header]
│   └── tr[z-table-row]
│       ├── th[z-table-head]
│       ├── th[z-table-head]
│       └── th[z-table-head]
├── tbody[z-table-body]
│   ├── tr[z-table-row]
│   │   ├── td[z-table-cell]
│   │   ├── td[z-table-cell]
│   │   └── td[z-table-cell]
│   └── tr[z-table-row]
│       ├── td[z-table-cell]
│       ├── td[z-table-cell]
│       └── td[z-table-cell]
└── tfoot[z-table-footer]
    └── tr[z-table-row]
        ├── td[z-table-cell]
        └── td[z-table-cell]
Copy

Examples

footer

Add a tfoot[z-table-footer] to render a summary row below the table body.
A list of your recent invoices.
InvoiceStatusMethodAmount
INV001
Paid
Credit Card
$250.00
INV002
Pending
PayPal
$150.00
INV003
Unpaid
Bank Transfer
$350.00
Total$750.00
import { ChangeDetectionStrategy, Component, computed, signal } from '@angular/core';

import { ZardTableImports } from '@/shared/components/table/table.imports';

export interface Invoice {
  id: string;
  status: string;
  method: string;
  amount: number;
}

@Component({
  selector: 'z-demo-table-footer',
  imports: [ZardTableImports],
  template: `
    <table z-table>
      <caption z-table-caption>A list of your recent invoices.</caption>
      <thead z-table-header>
        <tr z-table-row>
          <th z-table-head scope="col">Invoice</th>
          <th z-table-head scope="col">Status</th>
          <th z-table-head scope="col">Method</th>
          <th z-table-head scope="col" class="text-end">Amount</th>
        </tr>
      </thead>
      <tbody z-table-body>
        @for (invoice of invoices(); track invoice.id) {
          <tr z-table-row>
            <td z-table-cell>{{ invoice.id }}</td>
            <td z-table-cell>
              <div>{{ invoice.status }}</div>
            </td>

            <td z-table-cell>
              {{ invoice.method }}
            </td>
            <td z-table-cell>
              <div class="text-end font-medium">{{ formatCurrency(invoice.amount) }}</div>
            </td>
          </tr>
        } @empty {
          <tr z-table-row>
            <td z-table-cell [attr.colspan]="4" class="h-24 text-center">No results.</td>
          </tr>
        }
      </tbody>
      <tfoot z-table-footer>
        <tr z-table-row>
          <td z-table-cell colspan="3">Total</td>
          <td z-table-cell class="text-end">{{ formatCurrency(total()) }}</td>
        </tr>
      </tfoot>
    </table>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  host: {
    class: 'block w-full overflow-x-auto',
  },
})
export class ZardDemoTableFooterComponent {
  readonly invoices = signal<Invoice[]>([
    {
      id: 'INV001',
      status: 'Paid',
      method: 'Credit Card',
      amount: 250,
    },
    {
      id: 'INV002',
      status: 'Pending',
      method: 'PayPal',
      amount: 150,
    },
    {
      id: 'INV003',
      status: 'Unpaid',
      method: 'Bank Transfer',
      amount: 350,
    },
  ]);

  readonly total = computed(() =>
    this.invoices().reduce((sum: number, invoice: { amount: number }) => sum + invoice.amount, 0),
  );

  formatCurrency(amount: number): string {
    return new Intl.NumberFormat('en-US', {
      style: 'currency',
      currency: 'USD',
    }).format(amount);
  }
}

actions

A table showing actions for each row using a z-dropdown menu.
ProductPriceActions
Wireless Mouse29.99
Mechanical Keyboard129.99
USB-C Hub49.99
import { ChangeDetectionStrategy, Component } from '@angular/core';

import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucideEllipsis } from '@ng-icons/lucide';

import { ZardDropdownImports } from '@/shared/components/dropdown';
import { ZardTableImports } from '@/shared/components/table/table.imports';

interface Product {
  id: number;
  name: string;
  price: number;
}

@Component({
  selector: 'z-demo-table-actions',
  imports: [ZardTableImports, ZardDropdownImports, NgIcon],
  template: `
    <table z-table aria-label="Products and their actions">
      <thead z-table-header>
        <tr z-table-row>
          <th z-table-head scope="col">Product</th>
          <th z-table-head scope="col">Price</th>
          <th z-table-head scope="col" class="text-end">Actions</th>
        </tr>
      </thead>
      <tbody z-table-body>
        @for (product of products; track product.id) {
          <tr z-table-row>
            <td z-table-cell class="font-medium">{{ product.name }}</td>
            <td z-table-cell>{{ product.price }}</td>
            <td z-table-cell class="text-end">
              <button
                z-button
                z-dropdown
                zType="ghost"
                zSize="icon"
                zAlign="end"
                type="button"
                aria-label="Open actions"
                [zDropdownMenu]="menu"
              >
                <ng-icon name="lucideEllipsis" aria-hidden="true" />
              </button>
              <z-dropdown-menu-content #menu="zDropdownMenuContent" class="w-30 -translate-x-[calc(100%-1.5rem)]">
                <z-dropdown-menu-item>Edit</z-dropdown-menu-item>
                <z-dropdown-menu-item>Duplicate</z-dropdown-menu-item>
                <z-dropdown-menu-separator class="block" />
                <z-dropdown-menu-item
                  variant="destructive"
                  class="hover:text-destructive focus:text-destructive focus-visible:text-destructive data-highlighted:text-destructive"
                >
                  Delete
                </z-dropdown-menu-item>
              </z-dropdown-menu-content>
            </td>
          </tr>
        }
      </tbody>
    </table>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  viewProviders: [provideIcons({ lucideEllipsis })],
  host: {
    class: 'block w-full overflow-x-auto',
  },
})
export class ZardDemoTableActionsComponent {
  products: Product[] = [
    {
      id: 1,
      name: 'Wireless Mouse',
      price: 29.99,
    },
    {
      id: 2,
      name: 'Mechanical Keyboard',
      price: 129.99,
    },
    {
      id: 3,
      name: 'USB-C Hub',
      price: 49.99,
    },
  ];
}

simple

A minimal table with only a header and body — no footer or row actions.
A list of registered users.
NameAgeAddress
John Brown32New York No. 1 Lake Park
Jim Green42London No. 1 Lake Park
Joe Black32Sidney No. 1 Lake Park
import { ChangeDetectionStrategy, Component } from '@angular/core';

import { ZardTableImports } from '@/shared/components/table/table.imports';

interface Person {
  key: string;
  name: string;
  age: number;
  address: string;
}

@Component({
  selector: 'z-demo-table-simple',
  imports: [ZardTableImports],
  template: `
    <table z-table>
      <caption z-table-caption>A list of registered users.</caption>
      <thead z-table-header>
        <tr z-table-row>
          <th z-table-head scope="col">Name</th>
          <th z-table-head scope="col">Age</th>
          <th z-table-head scope="col">Address</th>
        </tr>
      </thead>
      <tbody z-table-body>
        @for (data of listOfData; track data.key) {
          <tr z-table-row>
            <td z-table-cell class="font-medium">{{ data.name }}</td>
            <td z-table-cell>{{ data.age }}</td>
            <td z-table-cell>{{ data.address }}</td>
          </tr>
        }
      </tbody>
    </table>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  host: {
    class: 'block w-full overflow-x-auto',
  },
})
export class ZardDemoTableSimpleComponent {
  listOfData: Person[] = [
    {
      key: '1',
      name: 'John Brown',
      age: 32,
      address: 'New York No. 1 Lake Park',
    },
    {
      key: '2',
      name: 'Jim Green',
      age: 42,
      address: 'London No. 1 Lake Park',
    },
    {
      key: '3',
      name: 'Joe Black',
      age: 32,
      address: 'Sidney No. 1 Lake Park',
    },
  ];
}

rtl

To enable RTL support in zard/ui, see the RTL configuration guide.
قائمة بأحدث الفواتير.
الفاتورةالحالةالمبلغ
INV001مدفوعة$250.00
INV002قيد الانتظار$150.00
INV003مدفوعة$350.00
// You will notice this example is rendered inside a wrapper that sets the dir and data-lang attributes, and
// reads its text from a translations helper. This is because this site is not RTL by default.
// In your application you won't need either: set dir="rtl" on <html> (or use the CDK Dir directive on a subtree)
// and write the text of your own language. See the RTL guide: https://zardui.com/docs/rtl

import { ChangeDetectionStrategy, Component } from '@angular/core';

import { ZardTableImports } from '@/shared/components/table/table.imports';
import { injectRtlTranslation, type ZardRtlTranslations } from '@/shared/demo-utils/rtl-translation';

const translations: ZardRtlTranslations<{
  caption: string;
  name: string;
  status: string;
  amount: string;
  paid: string;
  pending: string;
}> = {
  en: {
    dir: 'ltr',
    values: {
      caption: 'A list of recent invoices.',
      name: 'Invoice',
      status: 'Status',
      amount: 'Amount',
      paid: 'Paid',
      pending: 'Pending',
    },
  },
  ar: {
    dir: 'rtl',
    values: {
      caption: 'قائمة بأحدث الفواتير.',
      name: 'الفاتورة',
      status: 'الحالة',
      amount: 'المبلغ',
      paid: 'مدفوعة',
      pending: 'قيد الانتظار',
    },
  },
  he: {
    dir: 'rtl',
    values: {
      caption: 'רשימת חשבוניות אחרונות.',
      name: 'חשבונית',
      status: 'סטטוס',
      amount: 'סכום',
      paid: 'שולם',
      pending: 'ממתין',
    },
  },
};

@Component({
  selector: 'z-demo-table-rtl',
  imports: [ZardTableImports],
  template: `
    <table z-table>
      <caption z-table-caption>{{ i18n.t().caption }}</caption>
      <thead z-table-header>
        <tr z-table-row>
          <th z-table-head scope="col">{{ i18n.t().name }}</th>
          <th z-table-head scope="col">{{ i18n.t().status }}</th>
          <th z-table-head scope="col" class="text-end">{{ i18n.t().amount }}</th>
        </tr>
      </thead>
      <tbody z-table-body>
        @for (invoice of invoices; track invoice.id) {
          <tr z-table-row>
            <td z-table-cell class="font-medium">{{ invoice.id }}</td>
            <td z-table-cell>{{ invoice.paid ? i18n.t().paid : i18n.t().pending }}</td>
            <td z-table-cell class="text-end">{{ invoice.amount }}</td>
          </tr>
        }
      </tbody>
    </table>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoTableRtlComponent {
  protected readonly i18n = injectRtlTranslation(translations);

  protected readonly invoices = [
    { id: 'INV001', paid: true, amount: '$250.00' },
    { id: 'INV002', paid: false, amount: '$150.00' },
    { id: 'INV003', paid: true, amount: '$350.00' },
  ];
}

API Reference

table[z-table]Component

The table root. Apply the `z-table` attribute to a native `<table>` — it styles nested cells and rows through descendant selectors, and picks up the full set of per-slot styles once you also apply the matching attribute to each native tag: `z-table-header`, `z-table-body`, `z-table-row`, `z-table-head`, `z-table-cell`, `z-table-caption`, `z-table-footer`.

PropertyDescriptionTypeDefault
[class] Custom CSS classes ClassValue ''
[zType] Visual style of the table 'default' | 'striped' | 'bordered' 'default'
[zSize] Row and cell padding density 'default' | 'compact' | 'comfortable' 'default'

thead[z-table-header]Component

The table header section. Apply the attribute to a native `<thead>`.

PropertyDescriptionTypeDefault
[class] Custom CSS classes ClassValue ''

tbody[z-table-body]Component

The table body section. Apply the attribute to a native `<tbody>`; its last row renders without a bottom border.

PropertyDescriptionTypeDefault
[class] Custom CSS classes ClassValue ''

tr[z-table-row]Component

A table row. Apply the attribute to a native `<tr>`; it highlights on hover and when `[data-state="selected"]` is set on the element.

PropertyDescriptionTypeDefault
[class] Custom CSS classes ClassValue ''

th[z-table-head]Component

A header cell. Apply the attribute to a native `<th>`; set `scope="col"` (or `"row"`) on the element yourself so the table stays accessible.

PropertyDescriptionTypeDefault
[class] Custom CSS classes ClassValue ''

td[z-table-cell]Component

A data cell. Apply the attribute to a native `<td>`.

PropertyDescriptionTypeDefault
[class] Custom CSS classes ClassValue ''

caption[z-table-caption]Component

The table caption, giving the table an accessible name for screen readers. Apply the attribute to a native `<caption>`.

PropertyDescriptionTypeDefault
[class] Custom CSS classes ClassValue ''

tfoot[z-table-footer]Component

The table footer section, typically a totals row. Apply the attribute to a native `<tfoot>`.

PropertyDescriptionTypeDefault
[class] Custom CSS classes ClassValue ''
github iconwhatsapp icondiscord iconX icon

Made with ❤ in Brazil. Open source and available on GitHub .