Skip to content
Ndop UI

Table

Displays tabular data with responsive styles.

  • v1.0.0
  • SSR
  • Zoneless
  • 0.8 kB gzip
A list of your recent invoices.
InvoiceStatusMethodAmount
INV001PaidCredit card$250.00
INV002PendingPayPal$150.00
INV003UnpaidBank transfer$350.00
INV004PaidCredit card$450.00
INV005PaidPayPal$550.00
Total$1,750.00
import { Component } from '@angular/core';
import {
  Table,
  TableBody,
  TableCaption,
  TableCell,
  TableContainer,
  TableFooter,
  TableHead,
  TableHeader,
  TableRow,
} from '@/ui/table/table';

/** Recent invoices, named by a caption, with a total in the footer. */
@Component({
  selector: 'example-table-demo',
  imports: [
    Table,
    TableBody,
    TableCaption,
    TableCell,
    TableContainer,
    TableFooter,
    TableHead,
    TableHeader,
    TableRow,
  ],
  template: `
    <div ndTableContainer>
      <table ndTable>
        <caption ndTableCaption>
          A list of your recent invoices.
        </caption>
        <thead ndTableHeader>
          <tr ndTableRow>
            <th ndTableHead scope="col" class="w-24">Invoice</th>
            <th ndTableHead scope="col">Status</th>
            <th ndTableHead scope="col">Method</th>
            <th ndTableHead scope="col" class="text-end">Amount</th>
          </tr>
        </thead>
        <tbody ndTableBody>
          @for (invoice of invoices; track invoice.id) {
            <tr ndTableRow>
              <th ndTableHead scope="row">{{ invoice.id }}</th>
              <td ndTableCell>{{ invoice.status }}</td>
              <td ndTableCell>{{ invoice.method }}</td>
              <td ndTableCell class="text-end">{{ invoice.amount }}</td>
            </tr>
          }
        </tbody>
        <tfoot ndTableFooter>
          <tr ndTableRow>
            <th ndTableHead scope="row" colspan="3">Total</th>
            <td ndTableCell class="text-end">$1,750.00</td>
          </tr>
        </tfoot>
      </table>
    </div>
  `,
})
export class TableDemoExample {
  protected readonly invoices = [
    { id: 'INV001', status: 'Paid', method: 'Credit card', amount: '$250.00' },
    { id: 'INV002', status: 'Pending', method: 'PayPal', amount: '$150.00' },
    {
      id: 'INV003',
      status: 'Unpaid',
      method: 'Bank transfer',
      amount: '$350.00',
    },
    { id: 'INV004', status: 'Paid', method: 'Credit card', amount: '$450.00' },
    { id: 'INV005', status: 'Paid', method: 'PayPal', amount: '$550.00' },
  ];
}

Table styles a native HTML table: its caption, header, body, footer, rows and cells. Because each part is the table's own element, screen readers announce rows, columns and headers as they should. Wrap it in ndTableContainer so a wide table scrolls sideways instead of the page.

Installation

npx @ndop/cli add table

Usage

import {
  Table,
  TableBody,
  TableCaption,
  TableCell,
  TableContainer,
  TableFooter,
  TableHead,
  TableHeader,
  TableRow,
} from '@/ui/table/table';
<div ndTableContainer>
  <table ndTable>
    <caption ndTableCaption>
      A list of your recent invoices.
    </caption>
    <thead ndTableHeader>
      <tr ndTableRow>
        <th ndTableHead scope="col" class="w-24">Invoice</th>
        <th ndTableHead scope="col">Status</th>
        <th ndTableHead scope="col">Method</th>
        <th ndTableHead scope="col" class="text-end">Amount</th>
      </tr>
    </thead>
    <tbody ndTableBody>
      @for (invoice of invoices; track invoice.id) {
        <tr ndTableRow>
          <th ndTableHead scope="row">{{ invoice.id }}</th>
          <td ndTableCell>{{ invoice.status }}</td>
          <td ndTableCell>{{ invoice.method }}</td>
          <td ndTableCell class="text-end">{{ invoice.amount }}</td>
        </tr>
      }
    </tbody>
    <tfoot ndTableFooter>
      <tr ndTableRow>
        <th ndTableHead scope="row" colspan="3">Total</th>
        <td ndTableCell class="text-end">$1,750.00</td>
      </tr>
    </tfoot>
  </table>
</div>

Examples

Table status

Orders with a status and an action on each row.

OrderCustomerStatusActions
#3210Olivia MartinDelivered
#3209Jackson LeeShipped
#3204Isabella NguyenCancelled
import { Component } from '@angular/core';
import { Badge, type BadgeVariant } from '@/ui/badge/badge';
import { Button } from '@/ui/button/button';
import {
  Table,
  TableBody,
  TableCell,
  TableContainer,
  TableHead,
  TableHeader,
  TableRow,
} from '@/ui/table/table';

/** One order's status. */
type Status = 'Delivered' | 'Shipped' | 'Cancelled';

/**
 * Orders with a status badge and an action on each row; each button's
 * name says which order it acts on.
 */
@Component({
  selector: 'example-table-status',
  imports: [
    Badge,
    Button,
    Table,
    TableBody,
    TableCell,
    TableContainer,
    TableHead,
    TableHeader,
    TableRow,
  ],
  template: `
    <div ndTableContainer class="rounded-md border">
      <table ndTable aria-label="Orders">
        <thead ndTableHeader>
          <tr ndTableRow>
            <th ndTableHead scope="col">Order</th>
            <th ndTableHead scope="col">Customer</th>
            <th ndTableHead scope="col">Status</th>
            <th ndTableHead scope="col">
              <span class="sr-only">Actions</span>
            </th>
          </tr>
        </thead>
        <tbody ndTableBody>
          @for (order of orders; track order.id) {
            <tr ndTableRow>
              <th ndTableHead scope="row">{{ order.id }}</th>
              <td ndTableCell>{{ order.customer }}</td>
              <td ndTableCell>
                <span ndBadge [variant]="variants[order.status]">{{
                  order.status
                }}</span>
              </td>
              <td ndTableCell class="text-end">
                <button
                  ndButton
                  variant="ghost"
                  size="sm"
                  [attr.aria-label]="'View order ' + order.id"
                >
                  View
                </button>
              </td>
            </tr>
          }
        </tbody>
      </table>
    </div>
  `,
})
export class TableStatusExample {
  protected readonly variants: Record<Status, BadgeVariant> = {
    Delivered: 'secondary',
    Shipped: 'outline',
    Cancelled: 'destructive',
  };

  protected readonly orders: {
    id: string;
    customer: string;
    status: Status;
  }[] = [
    { id: '#3210', customer: 'Olivia Martin', status: 'Delivered' },
    { id: '#3209', customer: 'Jackson Lee', status: 'Shipped' },
    { id: '#3204', customer: 'Isabella Nguyen', status: 'Cancelled' },
  ];
}

Accessibility

Table has no keyboard interaction or focus handling of its own; it keeps the semantics of the element it's on.

API reference

TableContainer

[ndTableContainer]

Scrolls a wide table sideways instead of the page. Wrap the table[ndTable] in it.

TableContainer binding Type Default
[class]
Extra classes, merged last so they win.
string''
Data attribute Values
data-slottable-container

Table

table[ndTable]

A styled native table. Its parts style the table's own elements, so the table keeps all of its semantics.

Table binding Type Default
[class]
Extra classes, merged last so they win.
string''
Data attribute Values
data-slottable

TableHeader

thead[ndTableHeader]

A table's header rows.

TableHeader binding Type Default
[class]
Extra classes, merged last so they win.
string''
Data attribute Values
data-slottable-header

TableBody

tbody[ndTableBody]

A table's body rows.

TableBody binding Type Default
[class]
Extra classes, merged last so they win.
string''
Data attribute Values
data-slottable-body

TableFooter

tfoot[ndTableFooter]

A table's footer rows, such as totals.

TableFooter binding Type Default
[class]
Extra classes, merged last so they win.
string''
Data attribute Values
data-slottable-footer

TableRow

tr[ndTableRow]

A table row; data-state="selected" highlights it.

TableRow binding Type Default
[class]
Extra classes, merged last so they win.
string''
Data attribute Values
data-slottable-row

TableHead

th[ndTableHead]

A header cell. Add scope="col" or scope="row" as fits.

TableHead binding Type Default
[class]
Extra classes, merged last so they win.
string''
Data attribute Values
data-slottable-head

TableCell

td[ndTableCell]

A data cell.

TableCell binding Type Default
[class]
Extra classes, merged last so they win.
string''
Data attribute Values
data-slottable-cell

TableCaption

caption[ndTableCaption]

A table's caption, which names it; shown under the table.

TableCaption binding Type Default
[class]
Extra classes, merged last so they win.
string''
Data attribute Values
data-slottable-caption

Agent notes

Do

  • Name the table with caption[ndTableCaption], or aria-label when a visible caption doesn't fit the design.
  • Give header cells a scope: col for column headers, row for row headers.
  • Use Data Table for sorting, filtering, selection and pagination.

Don't

  • Don't use a table for layout; use flex or grid.
  • Don't put interactive controls in every cell without labels that say which row they act on.