0.1.0

Table

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

Try it

Plant
Sunlight
Watering
Aloe
Full sun
Minimum
Maidenhair fern
Part shade
Frequent
Ivy
Part sun
Average

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[] }.

import {
  RuiTable,
  RuiTableBody,
  RuiTableCell,
  RuiTableColumn,
  RuiTableHeader,
  RuiTableRow,
} from '@ecopages/radiant-ui/table';
 
<RuiTable label="Plants" sortColumn="name" sortDirection="ascending">
  <RuiTableHeader>
    <RuiTableColumn id="name" allowsSorting isRowHeader>
      Plant
    </RuiTableColumn>
    <RuiTableColumn id="cycle" allowsSorting>
      Cycle
    </RuiTableColumn>
  </RuiTableHeader>
  <RuiTableBody>
    <RuiTableRow id="aloe">
      <RuiTableCell isRowHeader>Aloe</RuiTableCell>
      <RuiTableCell>Perennial</RuiTableCell>
    </RuiTableRow>
  </RuiTableBody>
</RuiTable>

Custom markup

<rui-table> coordinates any light-DOM tree that matches its query contract. The Rui* helpers stamp these targets; they are not required.

import '@ecopages/radiant-ui/table';
 
<rui-table label="Plants" sort-column="name" sort-direction="ascending">
  <div class="rui-table" role="grid" aria-label="Plants">
    <div role="rowgroup" class="rui-table__header">
      <div role="row" class="rui-table__row">
        <div role="columnheader" data-table-column="name">
          <button type="button" data-table-sort="name">Plant</button>
        </div>
        <div role="columnheader" data-table-column="cycle">Cycle</div>
      </div>
    </div>
    <div role="rowgroup" class="rui-table__body">
      <div role="row" data-table-row="aloe" class="rui-table__row">
        <div role="rowheader" data-table-cell class="rui-table__cell" tabindex={-1}>Aloe</div>
        <div role="gridcell" data-table-cell class="rui-table__cell" tabindex={-1}>Perennial</div>
      </div>
    </div>
  </div>
</rui-table>;

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.

Plant
Sunlight
Watering
Aloe
Full sun
Minimum
Maidenhair fern
Part shade
Frequent
Ivy
Part sun
Average

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:

table.addEventListener('rui-sort-change', (event) => {
  const { column, direction } = event.detail;
  plants = sortPlants(plants, column, direction);
});
Watering
Aloe
Full sun
Minimum
Ivy
Part sun
Average
Maidenhair fern
Part shade
Frequent

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

PartCSS roles
Surface (rui-table)border, background, on-background, rounded-container, shadow-sm
Header columnssurface, on-background
Selected rowprimary-container, on-primary-container
Sort / focusfocus-ring

API

RuiTable is a custom element (<rui-table>) with composable JSX helpers for header, body, columns, cells, and selection.

Attributes (<rui-table>)

AttributeTypeDefaultDescription
labelstring''Accessible name for the grid.
aria-busy'true' · 'false''false'Whether the collection is updating.
selection-modenone · single · multiplenoneRow selection mode. single and multiple select a row when a non-interactive cell is clicked.
valuestringomittedComma-separated selected row ids. Empty selection removes the attribute. The JS property is string[].
sort-columnstring''Active sortable column id.
sort-directionascending · descendingascendingActive sort direction.

Light-DOM contract

TargetRequiredHost writesAuthor owns
[data-table-row]yesaria-selected when selectingdata-table-row id, data-table-actionable, hidden, aria-disabled
[data-table-cell]yesroving tabIndexcell content; direct child of a row
[data-table-column]per headeraria-sort when activecolumn id
[data-table-sort]per sortable column—sort button
rui-checkbox[data-table-select-row]nochecked, disablednested row checkbox
rui-checkbox[data-table-select-all]nochecked, indeterminate, disablednested 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

EventDetailDescription
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

ComponentTarget stampedNotes
RuiTable.rui-table[role="grid"] wrapperNot 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-actionableRow 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

ClassDescription
.rui-tableTable surface (role="grid").
.rui-table__columnColumn header cell.
.rui-table__cellData cell.
.rui-table__rowData row.