Overview
Data-driven accessible table that renders rows and columns into a real <table> with <caption>, scoped column and row headers, optional sortable columns, and a horizontal-scroll responsive container.
Usage
Guidance
Set virtualized with a fixed rowHeight when the table is rendering thousands
of same-height append-only rows. The virtualized path keeps the <table>,
<thead>, and <tbody> semantics intact, sets aria-rowcount to the full
logical row count, and annotates mounted body rows with full-table
aria-rowindex values. Variable or measured body row heights are out of scope
for this mode.
Use When
- Rendering a roster, cohort, or assignment view from row/column data with real table semantics.
- Needing sortable columns with screen-reader-announced sort state out of the box.
Avoid When
- Composing a bespoke table layout with custom cell markup — use the compositional Table family directly.
- Visualizing dense numeric magnitude across two dimensions — use matrix-chart instead.
When to use
- Rendering a structured dataset where columns and rows are known at runtime (e.g. API responses, config-driven dashboards).
- You want correct scope=col / scope=row semantics and aria-sort wiring without writing Table.Header / Table.Body manually.
- You need custom cell rendering, interactive cells, nested components, or column spanning — use the compositional Table family directly.
- You need fully custom cell or row composition — use Table directly.
Examples
Class roster
A class roster with a sortable grade column. The Name column is the row header (scope="row"). Bind sort state and reorder rows in a $derived expression.
Sortable data table
Multiple sortable columns with a scrollable wrapper. Sort state is bound and rows are reordered via $derived.
Virtualized data table
A semantic table that windows 10,000 fixed-height body rows while preserving full row counts and row indexes.
Props
| Name | Type | Default | Required | Bindable | Description |
|---|---|---|---|---|---|
columns | DataTableColumn<Row>[] | req | Column descriptors defining the headers and cell rendering for each column. | ||
rows | Row[] | req | Row data. Each entry is read via column.key for each column. | ||
caption | text | Visual caption rendered as a <caption> element above the table. | |||
sort | TableSort | undefined | bind | Bound sort state. When the user activates a sortable header cell, this prop
is updated with the new { column, direction }. The consumer is responsible
for reordering rows in response — DataTable does not sort internally.
Pass undefined initially when no column is sorted; the component will never
write back undefined itself (sort always toggles to a column). | ||
selectable | 'none' | 'single' | 'multiple' | 'none' | Enables checkbox-based row selection. "none" renders no selection controls,
"single" allows one selected row id, and "multiple" allows any number.
Selection state is exposed through row checkbox controls; native table rows
do not emit aria-selected.
Defaults to "none". | ||
selectedRowIds | string[] | Set<string> | [] | bind | Bound selected row ids. Arrays stay arrays on update; Sets stay Sets. When omitted, DataTable starts with an empty array. | |
getRowId | (row: Row, index: number) => string | Resolves the stable row id used for selection. Defaults to row.id when it
is a string or number, otherwise the row's current positional index. | |||
isRowSelectionDisabled | (row: Row, index: number) => boolean | Returns true when a row should render a disabled selection checkbox. | |||
selectAllLabel | text | 'Select all rows' | Accessible label for the multiple-selection header checkbox. | ||
rowSelectionLabel | (row: Row, index: number) => string | Accessible label for an individual row selection checkbox. | |||
stickyHeader | boolean | false | When true, the header sticks to the top of the scrolling container. | ||
density | 'comfortable' | 'condensed' | 'spacious' | 'comfortable' | Vertical padding density for header and body cells.
Defaults to 'comfortable'. | ||
scrollable | boolean | false | When true, wraps the table in a .cinder-table-scroll container that
enables horizontal overflow scrolling on small viewports. | ||
virtualized | boolean | false | When true, renders only the visible <tbody> row window plus spacer rows.
Requires a fixed row height. This is intended for large, append-only tables
such as live logs or event streams. | ||
rowHeight | number | 44 | Fixed body row height in pixels for virtualized mode. This must match the actual rendered body row height, including density padding and any wrapping introduced by the table content. Defaults to 44. | ||
overscan | number | 5 | Extra body rows rendered before and after the visible virtualized window. Defaults to 5. | ||
height | text | '24rem' | CSS block-size for the virtualized native scroll container.
Defaults to "24rem" when virtualized is true. | ||
stickToBottom | boolean | false | When true in virtualized mode, appending rows while scrolled to the bottom keeps the newest row pinned in view. Appending while scrolled up does not change the viewport. | ||
onscroll | unknown | req |