Table
Virtualized data grid with declarative columns, sorting, selection, pinning and resizing.
c2-table
Attributes
| Name | Type | Default | Description |
|---|---|---|---|
rows | TableRow[] | [] | The rows to display. An array in the property, JSON in the attribute. |
columns | TableColumnConfig[] | undefined | — | Column definitions, as an alternative to `c2-table-column` children. Children win when both are present. |
row-key | string | — | Field used as the identity of a row, for selection and DOM reuse. Falls back to the row index. |
selection | TableSelectionMode | none | `single` selects one row at a time, `multiple` supports ⌘/ctrl-click and shift-click ranges. |
checkbox-selection | boolean | false | Adds a leading checkbox column, pinned to the start. |
value | string[] | [] | Keys of the selected rows: an array in the property, `;`-separated in the attribute. |
sort | SortModel[] | [] | The sort, in priority order: `SortModel[]` in the property, `field:asc;other:desc` in the `sort` attribute. |
multi-sort | boolean | false | Lets shift-click add a column to the sort instead of replacing it. |
sortable | boolean | false | Makes every column sortable; a column's own `sortable` still wins. |
resizable | boolean | false | Makes every column resizable; a column's own `resizable` still wins. |
stripe | boolean | false | Tints odd rows with `--c2-table__row__odd--background`. |
virtual | TableVirtualMode | auto | `auto` virtualizes past `virtual-threshold` rows; `always` and `never` force it. |
row-height | number | 36 | Row height in pixels used before the first row has been measured. |
overscan | number | 6 | Rows rendered above and below the viewport while virtualizing. |
virtual-threshold | number | 100 | Row count past which `virtual="auto"` starts windowing. |
block-size | number | 100 | Number of rows the table asks a `dataSource` for at a time. |
loading | boolean | false | Shows the loading state; implied while a `dataSource` resolves its first block. |
error | string | — | Shows the error state with this message. |
empty-message | string | No rows | Message shown when there are no rows. |
page | number | 1 | The page on show, 1-based. Only meaningful while the table is `paginated`; clamped to `pageCount`. |
page-size | number | 0 | Rows per page. `0` leaves paging off; a `c2-pagination` slotted into the `footer` sets it from its own `page-size` when the table has none, which is what makes the nested form work with no configuration. With `rows` the page is sliced in place; with a `dataSource` each page is one request. |
Events
| Name | Type | Description |
|---|---|---|
page-change | CustomEvent<TablePageChangeEventDetail> | Fired after the shown page changes, while `paginated`. `detail.start` and `detail.count` are the slice of the whole dataset now shown — with a `dataSource`, the `getRows` request that follows. A pager slotted in the footer does not fire its own: the table speaks for it. |
sort-change | CustomEvent<TableSortChangeEventDetail> | Fired after the user clicks a sortable header. `detail.sort` is the new sort model, in priority order. |
row-click | CustomEvent<TableRowEventDetail> | Fired when a row is clicked, before the selection is applied. |
cell-click | CustomEvent<TableCellEventDetail> | Fired when a cell is clicked; adds `detail.column` and `detail.value`. |
selection-change | CustomEvent<TableSelectionChangeEventDetail> | Fired after the user changes the selection. `detail.value` is the array of selected row keys, `detail.rows` the matching rows. |
column-resize | CustomEvent<TableColumnResizeEventDetail> | Fired when the user releases a column's resize handle. |
CSS custom properties
Grouped by the element and state they target. Click a group to collapse it; click a name to copy it.
| Name | Type | Default | Description |
|---|---|---|---|
--c2-table--background | color | #ffffff | |
--c2-table--color | color | #18181b | |
--c2-table--font-size | font-size | 14px | |
--c2-table--max-height | pixel | none | Caps the height when the host is not sized itself; the body scrolls. |
--c2-table--border-top | border | 1px solid #e4e4e7 | |
--c2-table--border-right | border | 1px solid #e4e4e7 | |
--c2-table--border-bottom | border | 1px solid #e4e4e7 | |
--c2-table--border-left | border | 1px solid #e4e4e7 | |
--c2-table--border-top-left-radius | border-radius | 8px | |
--c2-table--border-top-right-radius | border-radius | 8px | |
--c2-table--border-bottom-left-radius | border-radius | 8px | |
--c2-table--border-bottom-right-radius | border-radius | 8px | |
--c2-table--box-shadow | box-shadow | none | |
--c2-table__header--background | color | #fafafa | |
--c2-table__header--color | color | #71717a | |
--c2-table__header--height | pixel | 36px | |
--c2-table__header--font-size | font-size | 12px | |
--c2-table__header--font-weight | font-weight | 600 | |
--c2-table__header--letter-spacing | letter-spacing | 0.02em | |
--c2-table__header--border-bottom | border | 1px solid #e4e4e7 | |
--c2-table__header-cell--padding | padding | 0 12px | |
--c2-table__header-cell--gap | pixel | 6px | |
--c2-table__header-cell__hover--background | color | #f4f4f5 | |
--c2-table__header-cell__sorted--color | color | #18181b | |
--c2-table__sort-icon--width | pixel | 14px | |
--c2-table__sort-icon--height | pixel | 14px | |
--c2-table__sort-icon--color | color | #a1a1aa | |
--c2-table__sort-icon__active--color | color | rgb(2, 101, 220) | |
--c2-table__resizer--width | pixel | 9px | Width of the grab area, not of the visible line. |
--c2-table__resizer--height | pixel | 56% | Height of the resting divider, as a share of the header. |
--c2-table__resizer--line-width | pixel | 1px | |
--c2-table__resizer--color | color | #e4e4e7 | The divider drawn at a resizable column's edge. |
--c2-table__resizer__hover--height | pixel | 100% | |
--c2-table__resizer__hover--line-width | pixel | 2px | |
--c2-table__resizer__hover--color | color | rgb(2, 101, 220) | |
--c2-table__row--height | pixel | 36px | Row height; virtualization measures it, so keep it uniform. |
--c2-table__row--border-bottom | border | 1px solid #e4e4e7 | |
--c2-table__row__hover--background | color | #fafafa | |
--c2-table__row__odd--background | color | #fafafa | Applies with the `stripe` attribute. |
--c2-table__row__selected--background | color | #edf1fe | |
--c2-table__row__selected--color | color | #18181b | |
--c2-table__cell--padding | padding | 0 12px | |
--c2-table__cell--gap | pixel | 8px | |
--c2-table__cell--font-size | font-size | 14px | |
--c2-table__cell--color | color | #18181b | |
--c2-table__cell--border-right | border | none | Vertical grid lines; applies to header cells too. |
--c2-table__cell__focus--outline | outline | 2px solid rgba(2, 101, 220, 0.4) | |
--c2-table__cell__focus--outline-offset | pixel | -2px | |
--c2-table__pinned-start--box-shadow | box-shadow | 1px 0 0 0 #e4e4e7 | Separator on the last column pinned to the start. |
--c2-table__pinned-end--box-shadow | box-shadow | -1px 0 0 0 #e4e4e7 | Separator on the first column pinned to the end. |
--c2-table__selection-cell--width | pixel | 44px | Width of the `checkbox-selection` column. |
--c2-table__skeleton--background | color | #f4f4f5 | Placeholder shown in cells whose `dataSource` block is still loading. |
--c2-table__skeleton--border-radius | border-radius | 4px | |
--c2-table__state--color | color | #71717a | Colour of the empty and loading messages. |
--c2-table__state--padding | padding | 32px 12px | |
--c2-table__state--font-size | font-size | 14px | |
--c2-table__state__error--color | color | rgb(211, 21, 16) | |
--c2-table__toolbar--background | color | transparent | |
--c2-table__toolbar--padding | padding | 8px 12px | |
--c2-table__toolbar--border-bottom | border | 1px solid #e4e4e7 | |
--c2-table__footer--background | color | transparent | |
--c2-table__footer--padding | padding | 8px 12px | |
--c2-table__footer--border-top | border | 1px solid #e4e4e7 | |
c2-table-column
Attributes
| Name | Type | Default | Description |
|---|---|---|---|
field | string | — | Key of the value in the row object; may be a dotted path such as `user.name`. |
header | string | undefined | — | Header label. Falls back to the field name. |
width | string | 1fr | Grid track for the column: `1fr`, `160px`, `minmax(120px, 1fr)`… |
min-width | number | 64 | Lower bound in pixels while the column is resized. |
align | ColumnAlign | start | Horizontal alignment of the header and the cells. |
sortable | boolean | undefined | — | Lets the user sort by this column. Defaults to the table's `sortable`. |
resizable | boolean | undefined | — | Lets the user drag the column's trailing edge. Defaults to the table's `resizable`. |
pinned | ColumnPin | undefined | — | Freezes the column against the leading or trailing edge while scrolling horizontally. |
hidden | boolean | false | Leaves the column out of the table without removing the definition. |
format | ColumnFormat | text | Built-in `Intl` formatting applied when no `renderCell` is set. |
format-options | Record<string, unknown> | undefined | — | Options handed to the `Intl` formatter of `format`, as JSON in the attribute. |
currency | string | undefined | — | Currency code used by `format="currency"`. |
locale | string | undefined | — | BCP 47 locale used by `format`. Defaults to the browser locale. |
cell-class | string | undefined | — | Class set on every cell of this column, so light-DOM CSS can target it through `::part(cell)`. |
Events
| Name | Type | Description |
|---|---|---|
COLUMN_CHANGE_EVENT | CustomEvent |