ComponentsData & statusTable
On this page
Data & status

Table released

A table you write yourself, in the platform's own markup, with the library's look, sortable headers and a frame that scrolls a table wider than its space.

  • Import@pacit/components/table
  • Selectorstable[pctTable], caption[pctCaption], th[pctHeader], td[pctCell], pct-table-frame

Semantics come from the native <table> — no role and no keyboard handling of ours

Devices
DeviceProtocolPower (kW)
RecuperatorModbus TCP0.2
InverterModbus RTU9.9
Heat pumpModbus TCP2.4

table.demo.ts — the same file runs here and is highlighted under Code

Start here

Usage

One import per component, and the shortest possible use — the package itself is installed once, on the start page.

Import this component
import { PctTable, PctTableCaption, PctTableHeader, PctTableCell, PctCellTemplate } from '@pacit/components/table';
ExportedWhat it is
PctTableSortinterfaceunreleasedThe column the rows stand sorted by, named by the key its header carries, and which way.
PctTablecomponentunreleasedtable[pctTable]A table the consumer writes, dressed by the library: the platform's <table>, its caption, header cells and cells, each element wearing a component of its own.
PctTableCaptioncomponentunreleasedcaption[pctCaption]The table's caption: its name, which a reader announces with the table, and the name of the frame around it when the frame scrolls.
PctTableHeadercomponentunreleasedth[pctHeader]A header cell. With a sortKey its content is a button that proposes sorting the rows by that column, and the header of the sorted column carries aria-sort.
PctTableCellcomponentunreleasedtd[pctCell]A cell.
PctTableFramecomponentunreleasedpct-table-frameThe frame a table scrolls in when it is wider than the space it is given, or taller than a height it is given. While the table overflows, the frame is a focusable region named by the table's caption, so a keyboard can reach it and scroll it and a reader can say what it holds. While the table fits, the frame is nothing: a stop with nothing to scroll is a stop for nothing.
PctTableVisibleinterfaceunreleasedThe lines in the view, [start, end) among the lines drawn, and the ids of the rows among them, in order.
PctScrollAligntypeunreleased'start' | 'center' | 'end' | 'nearest'Where a row the table scrolls to stands in the view: at its top, its centre, its bottom, or wherever is nearest from where the view is — the default.
PctScrollToOptionsinterfaceunreleasedHow scrollTo scrolls.
PctDataTablecomponentunreleasedpct-data-tableA table built from data: typed columns, sorting by one key or several, a quick filter, and the empty and loading states, rendered as the first layer's <table pctTable> so the two look the same (0097).
PctCellContextinterfaceunreleasedWhat a cell template is handed.
PctCellTemplatedirectiveunreleasedng-template[pctCell]A cell the consumer draws for one column, or for several that share a look.
PctHeaderCellContextinterfaceunreleasedWhat a header template is handed.
PctHeaderCellTemplatedirectiveunreleasedng-template[pctHeaderCell]What a header shows in place of the column's header text.
PctEmptyContextinterfaceunreleasedWhat the empty-state template is handed.
PctEmptyTemplatedirectiveunreleasedng-template[pctEmpty]What the table shows beside itself when it has no row to show, in place of its sentence.
<table pctTable>
  <caption pctCaption>
    Devices
  </caption>
  <thead>
    <tr>
      <th pctHeader scope="col">Device</th>
      <th pctHeader scope="col" numeric>Power</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td pctCell>Heater</td>
      <td pctCell numeric>2.4</td>
    </tr>
  </tbody>
</table>
Examples

Every state, running

Each example is the component itself, not a screenshot. Open the code under any of them.

Sorted by the headers, ordered by you

The headers with a sortKey propose an order into one sort model, and the component that owns the rows puts them in it. The frame scrolls the table when the page is too narrow for it, and only then is it a region with a tab stop.

Rooms
Kitchen21.548
Bedroom19.052
Office22.541
Bathroom23.067
API

What it reads, and what it writes back

Read from the source at build time — the types, the defaults and the lines of documentation are the library's own.

unreleased marks 17 of them: on main, and not in @pacit/components@0.2.0 — the next release names the version.

A table the consumer writes, dressed by the library: the platform's <table>, its caption, header cells and cells, each element wearing a component of its own.

Inputs

NameTypeWhat it does
sorttwo-wayunreleaseddefault nullPctTableSort | nullThe column the rows stand sorted by, and which way, or null while they stand in the consumer's own order. A header with a sortKey writes it when pressed: a column not yet sorted starts ascending, and the sorted one turns the other way. Bind it two-way and order the rows from it.

Outputs

None of its own. The native events — (click), (focus), the form's submit — are the platform's, and two-way models above carry the rest.

What it writes on the element

The hooks a stylesheet or a test can hold. Stable by contract — the parts inventory gate fails the build when one disappears.

AttributeBound toNote
classpct-table
Styling

Parts to select, tokens to override

Two ways in, both stable. No class names to depend on.

Parts

In a test, PctTableHarness, PctTableCaptionHarness, PctTableHeaderHarness, PctTableCellHarness, PctTableFrameHarness from @pacit/components/testing read a part by the name below and a state by its attribute — the harness gate holds the names to the package.

PartSelectorWhat it is
mark[data-pct-part="mark"]the data table's word the quick filter found
message[data-pct-part="message"]the data table's sentence beside an empty table — see the data table's card
place[data-pct-part="place"]the data table's place of a sort key after the first
placeholder[data-pct-part="placeholder"]the data table's row standing for rows still coming
icon[data-pct-part="icon"]the chevron beside a sortable header's label
toggle[data-pct-part="toggle"]the button that fills a sortable header cell

Tokens

Every value the component reads, with the light and the dark default. Override a token at any scope and it cascades like any custom property.

TokenStyles
--pct-table-bg-changed{pct.warning-200}#fde68a · #b45309Background a data table's cell flashes in when its value changes.
--pct-table-bg-danger{pct.danger-100}#fee2e2 · #991b1bBackground of a data table's row or cell in the danger tone.
--pct-table-bg-hover{pct.primary-200}#bfdbfe · #1d4ed8Background of a data table's row under the pointer.
--pct-table-bg-info{pct.info-100}#dbeafe · #1e40afBackground of a data table's row or cell in the info tone.
--pct-table-bg-striped{pct.surface-100}#f1f5f9 · #1e293bBackground of every other row of a striped data table.
--pct-table-bg-success{pct.success-100}#dcfce7 · #166534Background of a data table's row or cell in the success tone.
--pct-table-bg-warning{pct.warning-100}#fef3c7 · #92400eBackground of a data table's row or cell in the warning tone.
--pct-table-border{pct.border}#e2e8f0 · #334155Line under a row.
--pct-table-caption-fg{pct.text}#0f172a · #ffffffText colour of the caption.
--pct-table-caption-font-weight{pct.font.weight.medium}500Font weight of the caption.
--pct-table-caption-gap{pct.space.3}8pxSpace between the caption and the table.
--pct-table-column-max-width20remMaximum width of a data table's column whose cells are cut to one line.
--pct-table-fg{pct.text}#0f172a · #ffffffText colour of the cells.
--pct-table-fg-danger{pct.on-danger-100}#991b1b · #fee2e2Text colour of a data table's row or cell in the danger tone.
--pct-table-fg-info{pct.on-info-100}#1d4ed8 · #bfdbfeText colour of a data table's row or cell in the info tone.
--pct-table-fg-muted{pct.text-muted}#334155 · #e2e8f0Text colour of the numbers of a data table's rows.
--pct-table-fg-success{pct.on-success-100}#166534 · #dcfce7Text colour of a data table's row or cell in the success tone.
--pct-table-fg-warning{pct.on-warning-100}#92400e · #fef3c7Text colour of a data table's row or cell in the warning tone.
--pct-table-font-size{pct.font.size.md}14pxFont size of the table.
--pct-table-heading-bg{pct.surface-100}#f1f5f9 · #1e293bBackground of a header cell.
--pct-table-heading-border{pct.border-strong}#64748bLine under the header row.
--pct-table-heading-fg{pct.text}#0f172a · #ffffffText colour of a header cell.
--pct-table-heading-font-weight{pct.font.weight.medium}500Font weight of a header cell.
--pct-table-icon-fg-muted{pct.text-muted}#334155 · #e2e8f0Colour of the chevron a sortable header shows as a hint.
--pct-table-mark-bg{pct.warning-200}#fde68a · #b45309Background of a word the quick filter found.
--pct-table-mark-fg{pct.on-warning-100}#92400e · #fef3c7Text colour of a word the quick filter found.
--pct-table-padding-x{pct.space.4}12pxHorizontal padding of a cell and a header cell.
--pct-table-padding-y{pct.space.3}8pxVertical padding of a cell and a header cell.
--pct-table-toggle-gap{pct.space.2}4pxGap between a sortable header's label and its chevron.
--pct-table-toggle-target-min{pct.target.min}24pxTouch-target floor of a sortable header.

Theming

A brand override in a few declarations. Scope it to a theme attribute, a page or one container — the component never notices.

[data-theme='brand'] {
  --pct-table-heading-bg: #ccfbf1;
  --pct-table-heading-border: #0f766e;
  --pct-table-border: #99f6e4;
}
Devices
DeviceProtocolPower (kW)
RecuperatorModbus TCP0.2
InverterModbus RTU9.9
Heat pumpModbus TCP2.4
Accessibility

Measured, not assumed

Pattern: a native <table> — sorting as the APG's sortable table draws it: a button in each sortable header, and aria-sort on the sorted column's header alone. Named in the class JSDoc.

Keyboard

keyeffecttest
Tab / Shift+Tabmoves between the sort buttons, and onto a scrolling frameapps/sandbox-e2e/src/table.spec.ts — "an overflowing frame is a region named by the caption, and the keyboard scrolls it"
Enter / Spacesorts by the column, or turns its orderapps/sandbox-e2e/src/table.spec.ts — "the keyboard sorts: Enter sorts ascending, Space turns it"
← / →scrolls a focused frame along the inline axisapps/sandbox-e2e/src/table.spec.ts — the same case as Tab; the scrolling is the platform's

There are no handlers to map: the sort buttons are buttons and the frame is a scrolling box, and both answer the keyboard on their own.

What is measured, and what is not yet

Every line is a test in three engines or a gate on every commit. The gaps are listed the same way, each with the requirement that will close it.

MeasuredARIA pattern named in the class JSDoclibs/components/table/src/table.ts
MeasuredKeyboard map tested key by keythe table above — every row measured in apps/sandbox-e2e/src/table.spec.ts
Measuredaxe audit on the component's own sandbox viewapps/sandbox-e2e/src/a11y.spec.ts — /table in SBX_ROUTES, the overflowing frame included
MeasuredVisual screenshotapps/sandbox-e2e/src/visual.spec.ts — table-pattern and table-pattern-rtl
Measuredforced-colors: active — no state carried by colour alonelibs/components/table/src/header.scss, cell.scss, frame.scss — the lines CanvasText, the focus Highlight; the sorted column keeps its chevron, a drawing. The reading: apps/sandbox-e2e/src/forced-colors.spec.ts
n/aprefers-reduced-motion — duration from a tokennot applicable — no motion at all
MeasuredTouch target ≥ 24×24 px outrightlibs/components/table/src/header.scss — min-block-size: var(--pct-table-toggle-target-min) on the sort button, which fills its cell; measured in apps/sandbox-e2e/src/target-min.spec.ts
MeasuredSize axis aligned to --pct-control-height-*deliberately not: a cell is not a control, and a table is sized by its type and the spacing scale, as the badge is by its own
MeasuredDensity axislibs/tokens/src/component.table.json — the padding stands on space.3 and space.4, which the compact scope re-points; the sort button in apps/sandbox-e2e/src/density.spec.ts
MeasuredRTL — no physical properties + a dir="rtl" screenshotlogical properties only, held by check-styles; the screenshot table-pattern-rtl, where the caption and the text move to the right and the numbers to the left
MeasuredSSR + hydration with no NG05xxapps/sandbox-e2e/src/hydration.spec.ts — /table in SBX_ROUTES; the frame measures after render, so the server writes no region
n/aFormsnot applicable — a table holds no value a form owns
MeasuredParts registered in the inventorylibs/components/parts.snapshot.md — toggle, icon
MeasuredTokens registered + an entry in contrast.policy.jsonlibs/tokens/src/contrast.policy.json — seven entries: the text of a cell, the caption and a header cell (AA error), the sort hint's chevron (UI error), the header surface and both lines (UI warn); libs/tokens/tokens.snapshot.md
n/aStrings through PCT_TEXTSnot applicable — the component writes no word of its own
MeasuredEntrypoint size budgetlibs/components/size.snapshot.md — the ./table row
MeasuredScreen-reader logRead 2026-10-29 from docs/acr/at/ — three readers, whole sandbox. Each reader says part of a sort header: Orca both the header and the button, entering the table by its shape, table with 5 rows 3 columns · column header. · row 1. · column 1 · Name · button., then column header. · column 2 · Address · button.; NVDA the header alone, Address, column header, row 1, column 2, so under NVDA the sort is not announced as a control; VoiceOver the button alone, Address button. The overflowing frame is a named stop: NVDA Registers, region, VoiceOver main Registers region, Orca Registers. · clickable. — the name under all three, the role word under two. The press itself is not walked: aria-sort after a press is read by the e2e suite, not by a reader
MeasuredA docs page with live examplesapps/sandbox/src/app/views/table/ (the sandbox view). The published site: /components/table, its demo's source in the code tab
MeasuredUnit + mutationlibs/components/table/src/table.spec.ts; libs/components/mutation.snapshot.md — table.ts, measured by a narrow run
Evidence

What the repository holds for this component

Read at build time from the tracked snapshots, not typed here.

100%mutation score60 of 60 mutants killed
9 × 3e2e cases × enginesplus the shared audits: axe, forced colors, preferences, size, hydration
18colour pairs measuredlight and dark, at every token build
2visual baselinesChromium, the sandbox's own pictures
20elements in the preview0 listeners · 1 render to settle · 6268 µs in jsdom, held exactly but for the clock

Known limitations

  • Every dressed cell carries an attribute. A <td> without pctCell keeps the browser's look. The cost is the one Angular Material's mat-cell asks, and it is what keeps every rule inside the component that owns it.
  • No zebra, hover or selected row. Each would need a component on every <tr>, which this layer does not dress, and a selected row is a fact about data, which is the data table's.
  • A row header needs scope="row". The header cell keys its row look on the attribute, so a <th pctHeader> in the body without it wears the column header's surface. Write it anyway: it states the row header outright rather than leaving it to each engine's guess.
  • The frame keeps a static aria-labelledby only. It reads the consumer's attribute once and falls back to it where no caption names the region; a role or a tabindex written on the frame is dropped at the first change detection, and the frame writes its own only while the table overflows.
  • The rows are not sorted for you. The headers propose and the consumer orders; the data table orders its own rows.
  • NVDA does not say the sort is a control. In the walk of 2026-10-29 NVDA with Firefox read a focused sort button as its column header, Address, column header, row 1, column 2, with no role word, where Orca and VoiceOver say button. The element is a <button> and Enter and Space press it; whether the header owes NVDA a description is for the next walk to measure.
  • No sticky header. A header that sticks to the frame's top would stick to a box that does not scroll vertically, which is no help; the page's own scroll is the consumer's.