Table

Data table for displaying structured information with sortable columns, row selection, resizable columns, and sticky headers.

Destination Departure Duration Price
Mallorca15 Aug 20257 nights€499
Tenerife22 Aug 202514 nights€799
Lanzarote01 Sep 202510 nights€649

Anatomy

1 2 3
Column AColumn BColumn C
CellCellCell
CellCellCell
  1. 1 Header row — sticky column labels, optionally sortable or resizable
  2. 2 Body row — data cells, optionally selectable
  3. 3 Table wrapper — horizontal scroll container

Behaviors

DestinationPrice
Mallorca€499
Tenerife€799

Sortable columns

Clicking or keyboard-activating a th[aria-sort] header cycles through none → ascending → descending → none. Only one column is sorted at a time; activating a new header resets the previous sort.

DestinationPrice
Mallorca€499
Tenerife€799

Row selection

data-selection="single" allows one row at a time; multi allows multiple rows. The selected state is tracked via aria-selected="true" on the <tr>. Multi-selection supports a select-all checkbox in the header.

DestinationDeparturePrice
Mallorca15 Aug 2025€499

Resizable columns

Adding data-resizable to a <th> injects a drag handle. Dragging resizes the column inline; a minimum width of 96 px is enforced. Column widths are not persisted by default.

DestinationPrice
Mallorca€499
Tenerife€799
Lanzarote€649
Rhodes€589
Gran Canaria€529
Ibiza€459
Fuerteventura€699

Sticky header

The <thead> row sticks to the top of the scroll container via position: sticky so column labels remain visible as the user scrolls through long tables.

Options

Density

NamePrice
Mallorca€499
Tenerife€799
Lanzarote€649
Rhodes€589
Compact
NamePrice
Mallorca€499
Tenerife€799
Lanzarote€649
Rhodes€589
Default
NamePrice
Mallorca€499
Tenerife€799
Lanzarote€649
Rhodes€589
Spacious

Options reference

AttributeValuesDefault
densitycompact / comfortable / spacious
data-selectionsingle / multi
aria-sort on thnone / ascending / descending
data-resizable on thboolean attribute

Keyboard interactions

KeyContextAction
Enter / SpaceSortable headerCycles sort direction: none → ascending → descending → none
Enter / SpaceSelectable rowToggles row selection
TabTableMoves focus through interactive cells and controls

Usage guidelines

Use selection only when the action is clear

DestinationPrice
Mallorca€499
Tenerife€799

Do

Use selection when the user needs to act on a row — e.g. delete, compare, or bulk-update.

DestinationPrice
Mallorca€499
Tenerife€799

Don't

Don't add row selection to display-only tables — it implies an action that doesn't exist.

Match density to context

NameStatusValue
Item AActive100
Item BDraft200
Item CActive150

Do

Use compact density for data-heavy dashboards where many rows must be visible at once.

NameStatusValue
Item AActive100
Item BDraft200

Don't

Don't use spacious density in compact UIs — it wastes vertical space and forces unnecessary scrolling.

Accessibility

  • Use a <caption> element or aria-label on the <table> to give it an accessible name.
  • Sortable <th> elements use aria-sort (none / ascending / descending) to communicate sort state to screen readers.
  • Selected rows use aria-selected="true" on <tr>; the table must have role="grid" or the selection pattern must be documented for AT users.
  • Column resize handles are visually-only; ensure column widths can also be controlled by other means.
  • Focus ring uses box-shadow inset to avoid layout impact and remains visible in high-contrast modes.
  • Do not rely on color alone to communicate sort direction — the / / icons provide a redundant visual cue.

Content standards

Column headers

Keep column headers short — 1–3 words. Use sentence case, not title case. Avoid abbreviations unless they are universally understood (e.g. "ID", "SKU").

DestinationDeparture datePrice
Mallorca15 Aug 2025€499

Do

Use clear, concise labels that describe the column's data.

Dest.Dep. Dt.Pr. (€)
Mallorca15/08/25499

Don't

Don't use unclear abbreviations that force the user to guess what the column means.

Empty and loading states

Provide a meaningful message for empty tables. Use <td colspan="…"> with the .uif-table-empty class. For loading state, add .uif-table--loading to the <table> element.

Theming

Table adapts automatically across brands and color modes through component tokens (--uif-table-*). Background, text, border, hover, and selection colors all respond to brand and mode changes. Use the hero preview switches above to see it in action.

For the full theming architecture — brands, modes, and how tokens cascade — see Foundations: Design Tokens.

Design checklist

All brand/mode contextsToken-driven colors and spacing adapt across brands and light/dark modes.
Keyboard interactionsSort, selection, and focus are fully keyboard accessible.
Design tokensComponent-scoped tokens (--uif-table-*) defined.
Density variantsCompact, comfortable, and spacious densities available.
Progressive enhancementBase HTML table is fully usable without JavaScript; JS adds sort/select/resize.
Code ConnectFigma node-id needed — schemas/web-table.figma.ts has a placeholder.

Usage

HTML

<div class="uif-table-wrapper">
  <table class="uif-table" data-selection="single">
    <thead>
      <tr>
        <th aria-sort="none">Destination</th>
        <th aria-sort="none">Departure</th>
        <th>Price</th>
      </tr>
    </thead>
    <tbody>
      <tr><td>Mallorca</td><td>15 Aug 2025</td><td>€499</td></tr>
    </tbody>
  </table>
</div>
<script type="module" src="/vendor/ui-foundations/components/table.js"></script>

JavaScript

import { enhanceTable } from "ui-foundations/ui/components/table.js";
// Enhance all tables on the page:
enhanceTable();
// Or enhance within a specific container:
enhanceTable(document.querySelector("#my-container"));