Liking cljdoc? Tell your friends :D

x-table

A stateless Web Component that acts as the grid container for x-table-row and x-table-cell children. It defines the CSS Grid column tracks that x-table-row subgrids inherit, provides correct ARIA table/grid semantics, and coordinates sorting and row selection.

<x-table columns="2fr 1fr 120px" selectable="single" bordered caption="Team Members">

  <x-table-row>
    <x-table-cell type="header" scope="col" sortable>Name</x-table-cell>
    <x-table-cell type="header" scope="col">Role</x-table-cell>
    <x-table-cell type="header" scope="col" align="end">Joined</x-table-cell>
  </x-table-row>

  <x-table-row interactive row-index="1">
    <x-table-cell truncate>Alice Wonderland</x-table-cell>
    <x-table-cell>Admin</x-table-cell>
    <x-table-cell align="end">2024-01-15</x-table-cell>
  </x-table-row>

</x-table>

Attributes

AttributeTypeDefaultDescription
columnsstringabsentCSS grid-template-columns value. A positive integer (e.g. "4") expands to repeat(4,1fr). Any other string is used as-is. Absent → no explicit column template.
captionstringabsentVisible caption text rendered before the first row. Also sets aria-label on the host.
selectable"none"\|"single"\|"multi""none"Row selection mode. "single" and "multi" switch role to "grid" and enable automatic selection management via x-table-row-click events. "multi" also adds aria-multiselectable="true".
stripedbooleanabsentApplies an alternating background to even-indexed x-table-row children.
borderedbooleanabsentRenders an outer border and border-radius around the table.
full-widthbooleanabsentSets width:100% on the host element.
compactbooleanabsentReduces cell padding by overriding --x-table-cell-padding for all descendant cells.
row-countnumberabsentExplicit aria-rowcount for windowed/virtual tables where only a subset of rows is in the DOM.

Properties

PropertyTypeDefaultReflects Attribute
columnsstring""columns
captionstring""caption
selectablestring"none"selectable
stripedbooleanfalsestriped
borderedbooleanfalsebordered
fullWidthbooleanfalsefull-width
compactbooleanfalsecompact
rowCountnumber\|nullnullrow-count
const table = document.querySelector('x-table');
table.columns    = '2fr 1fr 120px';
table.selectable = 'single';
table.bordered   = true;
table.fullWidth  = true;
table.rowCount   = 500; // for virtual scrolling

Events

x-table-sort

Fires when a sortable column header (x-table-cell with sortable) is clicked. x-table intercepts the lower-level x-table-cell-sort event, adds column index information, and re-fires as x-table-sort.

PropertyValue
bubblestrue
composedtrue
cancelabletrue

Detail:

KeyTypeDescription
colIndexnumberZero-based column index of the clicked header cell
directionstringNew sort direction ("asc", "desc", or "none")
previousDirectionstringPrevious sort direction
table.addEventListener('x-table-sort', e => {
  // Update sort-direction on the header cell
  const headers = table.querySelectorAll('x-table-row:first-child x-table-cell');
  // Clear all, apply to clicked column
  headers.forEach((h, i) => {
    h.setAttribute('sort-direction', i === e.detail.colIndex ? e.detail.direction : 'none');
  });
});

x-table-row-select

Fires when a row is clicked and selectable is not "none". Fires after x-table-row-click but before x-table updates the row's selected state. Canceling this event prevents x-table from updating the DOM.

PropertyValue
bubblestrue
composedtrue
cancelabletrue

Detail:

KeyTypeDescription
rowIndexnumberRow index (from row-index attribute, or 0)
selectedbooleanWhether the row will be selected after this action
selectionModestring"single" or "multi"
table.addEventListener('x-table-row-select', e => {
  if (someGuard) e.preventDefault(); // prevents selection DOM update
  console.log(`Row ${e.detail.rowIndex} → selected=${e.detail.selected}`);
});

Slots

NameDescription
(default)x-table-row elements that form the table body and header

Parts

PartElementDescription
captiondivThe visible caption text above the first row. Hidden when caption attribute is absent.

CSS Custom Properties

All custom properties are set on :host with automatic light/dark defaults.

PropertyLight DefaultDark DefaultDescription
--x-table-border-colorrgba(0,0,0,0.1)rgba(255,255,255,0.1)Outer border color (when bordered)
--x-table-border-radius8px8pxBorder radius (when bordered)
--x-table-stripe-bgrgba(0,0,0,0.025)rgba(255,255,255,0.03)Even-row background (when striped)
--x-table-caption-colorinheritinheritCaption text color
--x-table-caption-font-size0.875rem0.875remCaption font size
--x-table-caption-font-weight600600Caption font weight
--x-table-caption-padding0 0 0.5rem0 0 0.5remCaption padding
--x-table-compact-padding0.25rem 0.5rem0.25rem 0.5remCell padding override when compact
x-table {
  --x-table-border-color: #e5e7eb;
  --x-table-stripe-bg: rgba(249, 250, 251, 1);
}

Accessibility

ARIA Attributes

ConditionAttributeValue
Alwaysrole"table" (or "grid" when selectable)
selectable="single"role"grid"
selectable="multi"role"grid", aria-multiselectable = "true"
caption presentaria-labelcaption text
row-count is validaria-rowcountthe integer as string

Keyboard Interaction

x-table itself is not focusable. Keyboard focus is managed by x-table-row (when interactive). Tab moves focus between interactive rows; Enter/Space fires row click.


Layout / Subgrid

x-table is display:grid. Its grid-template-columns value comes from the columns attribute:

/* Integer shorthand */
<x-table columns="4">          →  grid-template-columns: repeat(4,1fr)

/* CSS value */
<x-table columns="2fr 1fr 120px">  →  grid-template-columns: 2fr 1fr 120px

/* Absent */
<x-table>                      →  grid-template-columns: (not set, auto)

Each x-table-row child has grid-template-columns:subgrid; grid-column:1/-1 — it inherits the column tracks from x-table and its x-table-cell children align perfectly across rows.

The slot inside x-table's shadow DOM has display:contents, making x-table-row elements direct grid items of the host grid.


Selection Management

When selectable is set, x-table listens to x-table-row-click events and automatically manages the selected attribute on rows:

  • selectable="single" — clicking a row removes selected from all other rows and sets it on the clicked row. Clicking the already-selected row deselects it.
  • selectable="multi" — clicking a row toggles its selected attribute. Other rows are not affected.
  • selectable="none" (default)x-table does not manage selection. The app handles x-table-row-click events directly.

In all modes, x-table-row must have the interactive attribute set to receive clicks.


Sort Coordination

x-table intercepts x-table-cell-sort events that bubble up through the DOM, computes the zero-based column index of the firing cell, stops the original event from propagating further, and fires x-table-sort instead. This means consumers should listen to x-table-sort rather than x-table-cell-sort when using the full x-tablex-table-rowx-table-cell composition.

// Recommended: listen at x-table level
table.addEventListener('x-table-sort', e => {
  // e.detail.colIndex is the column that was clicked
});

// Discouraged when using x-table (intercepted and stopped):
// document.addEventListener('x-table-cell-sort', ...)

Usage Examples

Basic read-only table

<x-table columns="3" bordered>
  <x-table-row>
    <x-table-cell type="header" scope="col">Name</x-table-cell>
    <x-table-cell type="header" scope="col">Role</x-table-cell>
    <x-table-cell type="header" scope="col" align="end">Date</x-table-cell>
  </x-table-row>
  <x-table-row>
    <x-table-cell>Alice</x-table-cell>
    <x-table-cell>Admin</x-table-cell>
    <x-table-cell align="end">2024-01-15</x-table-cell>
  </x-table-row>
</x-table>

Selectable table with sorting

<x-table id="users-table" columns="2fr 1fr 120px"
         selectable="single" bordered striped caption="Users">
  <x-table-row>
    <x-table-cell type="header" scope="col" sortable sort-direction="asc">Name</x-table-cell>
    <x-table-cell type="header" scope="col" sortable>Role</x-table-cell>
    <x-table-cell type="header" scope="col" align="end">Joined</x-table-cell>
  </x-table-row>
  <x-table-row interactive row-index="1">
    <x-table-cell truncate>Alice Wonderland</x-table-cell>
    <x-table-cell>Admin</x-table-cell>
    <x-table-cell align="end">2024-01-15</x-table-cell>
  </x-table-row>
  <x-table-row interactive row-index="2">
    <x-table-cell truncate>Bob Builder</x-table-cell>
    <x-table-cell>Member</x-table-cell>
    <x-table-cell align="end">2024-03-02</x-table-cell>
  </x-table-row>
</x-table>

<script>
  const table = document.getElementById('users-table');

  table.addEventListener('x-table-sort', e => {
    const { colIndex, direction } = e.detail;
    // Update sort directions in the header row
    const headerCells = table.querySelectorAll('x-table-row:first-child x-table-cell');
    headerCells.forEach((cell, i) => {
      cell.setAttribute('sort-direction', i === colIndex ? direction : 'none');
    });
    // Fetch sorted data and update rows...
  });

  table.addEventListener('x-table-row-select', e => {
    console.log(`Row ${e.detail.rowIndex} selected=${e.detail.selected}`);
  });
</script>

Virtual / windowed table

<x-table columns="2fr 1fr" row-count="10000" bordered full-width>
  <!-- only visible rows in DOM -->
</x-table>

JavaScript property API

const table = document.querySelector('x-table');
table.columns    = '200px 1fr 1fr 120px';
table.selectable = 'multi';
table.striped    = true;
table.bordered   = true;
table.compact    = true;
table.rowCount   = 500;

ClojureScript / hiccup

[:x-table {:columns "2fr 1fr 120px" :selectable "single" :bordered true}
  [:x-table-row
    [:x-table-cell {:type "header"} "Name"]
    [:x-table-cell {:type "header"} "Role"]
    [:x-table-cell {:type "header" :align "end"} "Date"]]
  [:x-table-row {:interactive true :row-index 1}
    [:x-table-cell "Alice"]
    [:x-table-cell "Admin"]
    [:x-table-cell {:align "end"} "2024-01-15"]]]

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close