DataTable
<caos-data-table>
the TABLE body of a list surface — a row per record, ordered by its column headings
May be placed on a record page, an app’s home page and a page inside an app.
Inputs
Section titled “Inputs”| Name | Attribute | Description | Type | Default | Required |
|---|---|---|---|---|---|
columns |
property only | The columns, in order: each one a cell key, a header label, and optionally sortable, an alignment (numeric columns read end) and a fixed width. Set as a property — an attribute cannot hold a list. | json | none declared | Required |
rows |
property only | The records. Each row carries an id, its cells keyed by column key, and optionally selected, disabled or total. A row may also carry children — the rows beneath it — which draw as ordinary rows at their own depth, with the same cells, checkbox and actions as any other. Nothing is fetched here: the access-filtered, formatted set is assembled by the app layer and handed in. An empty list is a real state and draws the empty content rather than an empty frame. | json | empty list | Optional |
sort |
property only | Which column is sorted and which way, as { key, direction }. Setting it moves the header marker and aria-sort; it does NOT reorder the rows. The host reorders and hands back a new rows list. | json | null | Optional |
selectable |
selectable, also a property |
Add the checkbox column and the tri-state select-all in the header. A row flagged total is never selectable and gets no checkbox. | boolean | false | Optional |
expandable |
expandable, also a property |
Lets a reader open and close the rows under a parent row. Every parent still starts OPEN, so nothing a table was given is hidden on arrival. Without this, children still draw at their depth and simply cannot be put away. This is a MODE of this table, not a second hierarchical table: a nested row is an ordinary row, so selection and row actions reach it unchanged. It is the Tree input collapsible under the name a table calls it. | boolean | false | Optional |
loading |
loading, markup only |
The rows have not arrived. Draws placeholder rows in the real column shape instead of the body, so the table does not jump when the data lands. | boolean | false | Optional |
loadingRows |
loading-rows, markup only |
How many placeholder rows to draw while loading. | number | 3 | Optional |
emptyLabel |
empty-label, markup only |
The heading of the built-in empty content shown when there are no rows. | text | No records | Optional |
label |
label, markup only |
The accessible name of the grid — what a screen reader calls this table. Follows the attribute whenever it changes; without it the grid has no name. | text | none declared | Optional |
Events
Section titled “Events”| Name | When it fires | What it carries |
|---|---|---|
caos-sort |
A sortable column header is pressed. The header marker moves immediately; the rows do not. | { column, direction } — the column key, and asc or desc. |
caos-row-select |
One row’s checkbox is ticked or cleared. | { id, selected, selectedIds } — the row, its new state, and every selected row id. |
caos-select-all |
The header checkbox is ticked or cleared. Disabled rows and total rows are left alone. | { selected, selectedIds } — the new state, and every selected row id afterwards. Rows inside a closed parent are included: a reader who ticks select-all means every row, not every row they happen to be looking at. |
caos-row-expand |
A parent row is opened or closed. Only an expandable table raises it. | { id, expanded } — the parent row, and whether its children are now showing. |
| Name | What goes in it |
|---|---|
footer |
The strip under the table, kept outside the horizontal scroller so it stays put while a wide table scrolls. The source describes it as carrying the pagination affordance — and the library has no pagination piece, so there is nothing to put in it yet. Whatever is placed here is laid out as a row with its ends pushed apart, above a top rule. |
empty |
What to show instead of rows when the list is empty. Left alone it holds a default EmptyState headed by empty-label; filling it replaces that, which is how a list says something more useful than “no records”. |
(named by a cell) |
A cell whose value is the descriptor { slot: “name” } draws a slot of that name in place, so a real piece — a StatusPill in a stage column, a Tag, a ProvenanceMarker — is projected into the cell from the light DOM. The names are chosen by the data rather than by this component, so they cannot be listed here. |
Styling hooks
Section titled “Styling hooks”| Design token | What it controls |
|---|---|
--caos-color-border-strong |
The border around the table card. |
--caos-radius-lg |
The corners of the table card. |
--caos-shadow-card |
The elevation of the table card. |
--caos-color-surface |
The ground the table sits on. |
--caos-color-surface-2 |
The hover tint of a row. |
--caos-font |
The typeface of the cells. |
--caos-text-row |
The text size of a row. |
--caos-color-text |
The cell ink. |
--caos-font-mono |
The header face, and the figures in an end-aligned column. |
--caos-font-weight-label |
The weight of the column headers. |
--caos-color-text-ghost |
The header ink at rest. |
--caos-color-border |
The rule under the header and between the rows. |
--caos-color-accent |
The rule above a total row, the links inside cells, and the selected-row tint. |
--caos-color-accent-strong |
The header ink of the column currently sorted. |
--caos-color-text-muted |
The em dash standing in for an empty cell. |
--caos-font-display |
The figure on a total row. |
--caos-focus-ring |
The focus ring on a sort header and on a cell link. |
--caos-radius-sm |
The corners of that focus ring. |
--caos-space-1 |
The gap between a header label and its sort arrow. |
--caos-space-2 |
The gap between things inside a cell, and the checkbox column inset. |
--caos-space-3 |
The footer padding down, and the gap between footer items. |
--caos-space-4 |
The footer padding across. |
Methods
Section titled “Methods”Nothing else drives this piece by calling it.
Readable state
Section titled “Readable state”| Name | Type | What it tells you |
|---|---|---|
selectedIds |
string[] | The ids of the rows currently ticked. Selection is driven by the checkboxes and reported by the events; nothing sets it. |
Styling parts
Section titled “Styling parts”| Part | Which piece of it | When it is there |
|---|---|---|
::part(scroll) |
the box that scrolls when the table is wider than its space | Always |
::part(table) |
the table element | Always |
::part(head) |
the header section | Always |
::part(header-row) |
the row of column headers | Always |
::part(header-cell) |
one column header, the selection column included | Always |
::part(sort-button) |
the control inside a sortable column header | Always |
::part(select-all) |
the checkbox in the header of the selection column | Always |
::part(body) |
the section holding the rows | Always |
::part(row) |
one row | Always |
::part(cell) |
one cell, the selection cell included | Always |
::part(row-select) |
the checkbox in a row selection cell | Always |
::part(footer) |
the strip under the table holding slotted footer content | Always |
When to use it
Section titled “When to use it”Several records that are being COMPARED, where the same field across records is the thing being read and the columns line those values up. It is also the only one of the four list bodies that can select rows or carry a totals line.
What to use instead
Section titled “What to use instead”- CardGrid — The records are being browsed rather than compared: a name, a sub-line and a couple of rollups each, in a grid that reflows on a narrow screen instead of scrolling sideways.
- KanbanBoard — The question is WHERE THE WORK IS rather than what is in the list — the same records stood up in columns by the field that says what stage each one is at.
- SplitView — Somebody works down the list reading one record at a time, and reading one should not cost a trip to its page and back.
Accessibility
Section titled “Accessibility”The table is a real role=“grid”: each row is a row, each cell a gridcell, a sortable header is a real button carrying aria-sort, a selected row carries aria-selected, and the select-all checkbox goes indeterminate when only some rows are ticked. Focus roams in two dimensions — Arrow Up/Down between rows, Left/Right within a row, Home/End to the ends — and the stops are enumerated through the shadow-piercing focus util, so a control living inside a slotted piece’s own shadow root is reached in order. Left to the author: set label, because a grid with no accessible name is announced as a grid and nothing else; give any piece slotted into a cell its own name; and actually perform the sort when caos-sort fires, because the header marker says the list is sorted whether or not anybody reordered it.
Examples
Section titled “Examples”The orders list
Section titled “The orders list”Three records as rows and columns: two sortable headers, a status column holding real status pills, and the figures aligned to the right. The same three records the other three list bodies draw.
{ "id": "example", "section": "The orders list", "columns": 1, "items": [ { "id": "data_table_1", "type": "component", "key": "data_table", "inputs": { "label": "Orders", "columns": [ { "key": "reference", "label": "Order", "sortable": true }, { "key": "account", "label": "Account" }, { "key": "status", "label": "Status" }, { "key": "total", "label": "Total", "align": "end", "sortable": true } ], "rows": [ { "id": "o1", "cells": { "reference": "ORD-4471", "account": "Meridian Supply", "status": { "slot": "status-o1" }, "total": "12,400" } }, { "id": "o2", "cells": { "reference": "ORD-4472", "account": "Calder & Finch", "status": { "slot": "status-o2" }, "total": "3,150" } }, { "id": "o3", "cells": { "reference": "ORD-4473", "account": "Brightwater Group", "status": { "slot": "status-o3" }, "total": "86,900" } } ] }, "children": [ { "id": "status_pill_2", "type": "component", "key": "status_pill", "inputs": { "intent": "muted" }, "slot": "status-o1", "children": [ "Draft" ] }, { "id": "status_pill_3", "type": "component", "key": "status_pill", "inputs": { "intent": "success" }, "slot": "status-o2", "children": [ "Approved" ] }, { "id": "status_pill_4", "type": "component", "key": "status_pill", "inputs": { "intent": "info" }, "slot": "status-o3", "children": [ "In review" ] } ] } ]}<caos-data-table id="dataTable1" label="Orders"> <caos-status-pill slot="status-o1" intent="muted">Draft</caos-status-pill> <caos-status-pill slot="status-o2" intent="success">Approved</caos-status-pill> <caos-status-pill slot="status-o3" intent="info">In review</caos-status-pill></caos-data-table>// What an attribute cannot hold, set as a property — the same values the preview is drawn with.const dataTable1 = document.getElementById('dataTable1');dataTable1.columns = [ { "key": "reference", "label": "Order", "sortable": true }, { "key": "account", "label": "Account" }, { "key": "status", "label": "Status" }, { "key": "total", "label": "Total", "align": "end", "sortable": true }];dataTable1.rows = [ { "id": "o1", "cells": { "reference": "ORD-4471", "account": "Meridian Supply", "status": { "slot": "status-o1" }, "total": "12,400" } }, { "id": "o2", "cells": { "reference": "ORD-4472", "account": "Calder & Finch", "status": { "slot": "status-o2" }, "total": "3,150" } }, { "id": "o3", "cells": { "reference": "ORD-4473", "account": "Brightwater Group", "status": { "slot": "status-o3" }, "total": "86,900" } }];Selecting rows, and a total
Section titled “Selecting rows, and a total”The same list with the checkbox column on, one row already ticked, and a summary row under it — which carries no checkbox and is left out of select-all.
{ "id": "example", "section": "Selecting rows, and a total", "columns": 1, "items": [ { "id": "data_table_1", "type": "component", "key": "data_table", "inputs": { "label": "Orders", "selectable": true, "columns": [ { "key": "reference", "label": "Order" }, { "key": "account", "label": "Account" }, { "key": "total", "label": "Total", "align": "end" } ], "rows": [ { "id": "o1", "cells": { "reference": "ORD-4471", "account": "Meridian Supply", "total": "12,400" }, "selected": true }, { "id": "o2", "cells": { "reference": "ORD-4472", "account": "Calder & Finch", "total": "3,150" } }, { "id": "o3", "cells": { "reference": "ORD-4473", "account": "Brightwater Group", "total": "86,900" } }, { "id": "sum", "cells": { "reference": "Total", "account": "", "total": "102,450" }, "total": true } ] } } ]}<caos-data-table id="dataTable1" label="Orders" selectable></caos-data-table>// What an attribute cannot hold, set as a property — the same values the preview is drawn with.const dataTable1 = document.getElementById('dataTable1');dataTable1.columns = [ { "key": "reference", "label": "Order" }, { "key": "account", "label": "Account" }, { "key": "total", "label": "Total", "align": "end" }];dataTable1.rows = [ { "id": "o1", "cells": { "reference": "ORD-4471", "account": "Meridian Supply", "total": "12,400" }, "selected": true }, { "id": "o2", "cells": { "reference": "ORD-4472", "account": "Calder & Finch", "total": "3,150" } }, { "id": "o3", "cells": { "reference": "ORD-4473", "account": "Brightwater Group", "total": "86,900" } }, { "id": "sum", "cells": { "reference": "Total", "account": "", "total": "102,450" }, "total": true }];Waiting for the rows
Section titled “Waiting for the rows”The rows have not arrived: placeholder lines are drawn in the real column shape, so the table is the same size before and after the data lands.
{ "id": "example", "section": "Waiting for the rows", "columns": 1, "items": [ { "id": "data_table_1", "type": "component", "key": "data_table", "inputs": { "label": "Orders", "loading": true, "loading_rows": 4, "columns": [ { "key": "reference", "label": "Order" }, { "key": "account", "label": "Account" }, { "key": "total", "label": "Total", "align": "end" } ] } } ]}<caos-data-table id="dataTable1" label="Orders" loading loading-rows="4"></caos-data-table>// What an attribute cannot hold, set as a property — the same values the preview is drawn with.const dataTable1 = document.getElementById('dataTable1');dataTable1.columns = [ { "key": "reference", "label": "Order" }, { "key": "account", "label": "Account" }, { "key": "total", "label": "Total", "align": "end" }];Nothing in the list
Section titled “Nothing in the list”No rows at all: the headers stay and the body holds an EmptyState headed by empty-label, rather than a blank strip under the column names.
{ "id": "example", "section": "Nothing in the list", "columns": 1, "items": [ { "id": "data_table_1", "type": "component", "key": "data_table", "inputs": { "label": "Orders", "empty_label": "No orders yet", "columns": [ { "key": "reference", "label": "Order" }, { "key": "account", "label": "Account" }, { "key": "total", "label": "Total", "align": "end" } ], "rows": [] } } ]}<caos-data-table id="dataTable1" label="Orders" empty-label="No orders yet"></caos-data-table>// What an attribute cannot hold, set as a property — the same values the preview is drawn with.const dataTable1 = document.getElementById('dataTable1');dataTable1.columns = [ { "key": "reference", "label": "Order" }, { "key": "account", "label": "Account" }, { "key": "total", "label": "Total", "align": "end" }];dataTable1.rows = [];