--- title: Table description: Table provides a composable data grid with keyboard navigation, row selection, and sortable columns. category: Data display --- import { meta as TableMeta, Default, MultipleSelection, Sorting } from '@/content/stories/table'; import Canvas from '@/components/component-docs/canvas'; import Demo from '@/components/component-docs/demo'; export const config = { dependencies: { components: [Canvas, Demo], scripts: [ '../../components/component-docs/demo.script.tsx', '../../components/component-docs/canvas.script.tsx', '../../components/component-docs/controls.script.tsx', ], }, }; # Table

Table provides a composable data grid with keyboard navigation, row selection, and sortable columns.

## Try it ## Usage Compose header, body, columns, and rows as light-DOM children. The table owns keyboard focus, selection, and sort state; your app owns the collection and updates it after `rui-sort-change`. Selected row ids live on `value` as `string[]`; the HTML attribute is comma-separated. JSX may pass a string or an array. `rui-change` emits `{ value: string[] }`. ```tsx import { RuiTable, RuiTableBody, RuiTableCell, RuiTableColumn, RuiTableHeader, RuiTableRow, } from '@ecopages/radiant-ui/table'; Plant Cycle Aloe Perennial ``` ## Custom markup `` coordinates any light-DOM tree that matches its query contract. The `Rui*` helpers stamp these targets; they are not required. ```tsx import '@ecopages/radiant-ui/table';
Cycle
Aloe
Perennial
; ``` Add `data-table-actionable` on a row for Enter / double-click activation. Use `rui-checkbox[data-table-select-row]` for per-row checkboxes. ## Row selection Set `selectionMode="multiple"` (or `"single"`) and compose `RuiTableSelectionCell` in the header and each row. Click-to-select is not a separate prop: when selection is enabled, clicking a data cell selects that row. Nested controls (`button`, `a`, `input`, `rui-checkbox`) do not select. Space toggles selection. Enter and double-click fire `rui-row-action` on rows with `data-table-actionable`. ## Sorting `allowsSorting` on `RuiTableColumn` adds the sort button and indicator. Clicking it updates `sort-column`, `sort-direction`, and `aria-sort`, then emits `rui-sort-change`. The table does not reorder rows. Listen for the event and render the collection in that order: ```tsx table.addEventListener('rui-sort-change', (event) => { const { column, direction } = event.detail; plants = sortPlants(plants, column, direction); }); ``` ## Table vs grid vs treegrid

Use `RuiGrid` for simple read-only grids with a `rows` prop. Use `RuiTreegrid` when rows expand into hierarchies. Use `RuiTable` when you need sortable columns, row selection, and actionable row activation.

## Theming | Part | CSS roles | | --- | --- | | Surface (`rui-table`) | `border`, `background`, `on-background`, `rounded-container`, `shadow-sm` | | Header columns | `surface`, `on-background` | | Selected row | `primary-container`, `on-primary-container` | | Sort / focus | `focus-ring` | ## API `RuiTable` is a custom element (``) with composable JSX helpers for header, body, columns, cells, and selection. ### Attributes (``) | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `label` | `string` | `''` | Accessible name for the grid. | | `aria-busy` | `'true'` · `'false'` | `'false'` | Whether the collection is updating. | | `selection-mode` | `none` · `single` · `multiple` | `none` | Row selection mode. `single` and `multiple` select a row when a non-interactive cell is clicked. | | `value` | `string` | omitted | Comma-separated selected row ids. Empty selection removes the attribute. The JS property is `string[]`. | | `sort-column` | `string` | `''` | Active sortable column id. | | `sort-direction` | `ascending` · `descending` | `ascending` | Active sort direction. | ### Light-DOM contract | Target | Required | Host writes | Author owns | | --- | --- | --- | --- | | `[data-table-row]` | yes | `aria-selected` when selecting | `data-table-row` id, `data-table-actionable`, `hidden`, `aria-disabled` | | `[data-table-cell]` | yes | roving `tabIndex` | cell content; direct child of a row | | `[data-table-column]` | per header | `aria-sort` when active | column id | | `[data-table-sort]` | per sortable column | — | sort button | | `rui-checkbox[data-table-select-row]` | no | `checked`, `disabled` | nested row checkbox | | `rui-checkbox[data-table-select-all]` | no | `checked`, `indeterminate`, `disabled` | nested header checkbox | Do not set `aria-selected`, `aria-sort`, or checkbox `checked` — the host owns those. Nested hosts: `rui-checkbox` with `data-table-select-row` or `data-table-select-all`. ### Events | Event | Detail | Description | | --- | --- | --- | | `rui-change` | `{ value: string[] }` | Selected row ids changed. | | `rui-sort-change` | `{ column, direction }` | Sort column or direction changed. The table does not reorder rows. | | `rui-row-action` | `{ rowId: string }` | Actionable row activated (Enter / double-click). | ### View helpers | Component | Target stamped | Notes | | --- | --- | --- | | `RuiTable` | `.rui-table[role="grid"]` wrapper | Not queried; hosts `label` / `aria-busy` on the grid. | | `RuiTableColumn` | `[data-table-column]`, optional `[data-table-sort]` | `allowsSorting` adds the sort button. | | `RuiTableRow` | `[data-table-row]`, optional `data-table-actionable` | Row id via `id` prop. | | `RuiTableCell` | `[data-table-cell]` | `isRowHeader` sets `role="rowheader"`. | | `RuiTableSelectionCell` | `[data-table-cell]`, `rui-checkbox[data-table-select-*]` | `scope="all"` or `"row"`. | ### CSS classes | Class | Description | | --- | --- | | `.rui-table` | Table surface (`role="grid"`). | | `.rui-table__column` | Column header cell. | | `.rui-table__cell` | Data cell. | | `.rui-table__row` | Data row. |