DataTable
ImplementedCanonical table organism for the entire design system — generic over its row type. Column config (key/header/format/sortable/numeric/hideBelow/sortValue/sortLabel/headerSrOnly), three-state sort cycle (null→asc→desc→null with aria-sort on <th>), accessible empty state (role=status / aria-live=polite), optional row selection (leading checkbox column with aria-selected + highlight), whole-row anchor navigation (rowHref stretches the first cell's <a>::after over the row — keyboard focus, accessible name, middle-click and open-in-new-tab all come from the anchor), interactive rows (onRowClick — focusable <tr>, Enter/Space activation, the <tr> KEEPS its row role so cell semantics survive), and grouped mode (rowGroups — full-width <th scope='rowgroup'> per group, sorting bypassed). Wave-3 L5-M3: the five Admin*Table organisms compose it instead of re-implementing table chrome; Wave-3 L8c: the five domain tables (BookingsTable, TierTable, UserTable, PromoTable, EventComparisonTable) compose it for the storefront. Exported from the barrel along with DataTableProps, DataTableColumn, DataTableRowGroup, DataTableSelection and SortDirection.
components/organisms/datatable/DataTable.tsxTableau générique typé (TypeScript generic) utilisé pour les listes de tableaux de bord. FR-CA : en-têtes triables, alignement à droite pour les nombres, badge de statut sémantique.
DataTable
Organism — Admin family (Family 9)
Composant générique <T> qui sérialise n'importe quel tableau d'objets. Chaque colonne peut spécifier son alignement, sa largeur CSS et une fonction `format` pour personnaliser le rendu des cellules. Table canonique du design system : les cinq Admin*Table et les cinq tables de domaine (BookingsTable, TierTable, UserTable, PromoTable, EventComparisonTable) la composent.
| Événement | ||
|---|---|---|
| Marie Tremblay | Festival Jazz MTL | 96,00 $ |
| Jean-Pierre Gauthier | OSM Symphonie n° 5 | 288,00 $ |
| Sophie Lavoie | Lion d'Or Gala | 54,00 $ |
This component supports light and dark mode.
Props
rows*T[]Lignes du tableau.
columns*DataTableColumn<T>[]Définition des colonnes : key, header, align, width, format, sortable, numeric, hideBelow, sortValue, sortLabel, headerSrOnly.
caption*stringLégende accessible (sr-only par défaut) — sert aussi de nom au conteneur défilant.
emptyMessagestringAucune donnée.Message si rows est vide (rangée role="status" aria-live="polite").
ariaLabelstringaria-label du conteneur et du <table>. Retombe sur la légende.
classNamestringClasses CSS additionnelles ajoutées au conteneur défilant.
idstringid du conteneur défilant (branchement aria-controls).
captionVisiblebooleanfalseAffiche la légende à l’écran (sinon sr-only).
rowGroupsDataTableRowGroup<T>[]Mode groupé : rangée d’en-tête th scope="rowgroup" pleine largeur avant chaque groupe (le tri par colonne est désactivé).
rowKey(row: T, index: number) => stringClé React stable par ligne (défaut : index).
rowClassName(row: T) => string | undefinedClasse par ligne (ex. teinte de mise en évidence).
defaultSort{ key: string; direction: "asc" | "desc" }État de tri initial (non contrôlé ; cycle null→asc→desc→null).
onRowClick(row: T) => voidRangées interactives : <tr> focalisable, activation Entrée/Espace — le rôle « row » est conservé.
rowHref(row: T) => string | undefinedNavigation par ligne : ancre réelle étirée sur toute la rangée (première cellule).
onRowActivate(row: T, event: MouseEvent) => voidCrochet SPA pour les clics simples ; les clics modifiés restent au navigateur.
selectionDataTableSelectionColonne de cases à cocher : selectedKeys, allSelected, someSelected, onToggleRow, onToggleAll, selectAllLabel (défaut « Tout sélectionner »), selectRowLabel.
Props
DataTable
rowsT[] (T extends Record<string, unknown>)—Row data — typed by the generic. Drives the empty-state check.
columnsDataTableColumn<T>[]—Column config — key, header, optional align/width/format/sortable/numeric/hideBelow/sortValue/sortLabel/headerSrOnly. UI Designer rule: columns are NON-sortable by default (set sortable: true to opt in); action / status / pass 'Used By' columns must opt out.
captionstring—Accessible caption. sr-only by default — pass captionVisible to render for sighted users. Doubles as the scroll-region aria-label fallback.
emptyMessagestring'Aucune donnée.'Empty-state cell copy (role=status / aria-live=polite). FR default — EN consumers pass 'No data to display'.
ariaLabelstring—aria-label for the scroll wrapper and the <table>. Falls back to the caption.
classNamestring—Additional CSS classes appended to the scroll wrapper.
idstring—id on the scroll wrapper (useful for aria-controls wiring from a parent filter/search).
captionVisiblebooleanfalseRender the caption for sighted users (instead of the sr-only default). Used by AdminDataTable / AdminBulkActionTable when a visible caption is wanted.
rowGroupsreadonly DataTableRowGroup<T>[]—Grouped mode — each group gets a full-width <th scope='rowgroup'> before its rows, in caller order. Per-column sorting is bypassed (group order is caller-defined); rows keeps serving as the flat fallback and drives the empty-state check.
rowKey(row: T, index: number) => stringrow indexStable React key per row. Defaults to the index — pass a domain id for selection / highlighting to survive re-sorts.
rowClassName(row: T) => string | undefined—Optional per-row className (e.g. highlight tint on featured rows, used by TierTable).
defaultSort{ key: string; direction: 'asc' | 'desc' }—Initial sort state (uncontrolled; the cycle starts from here and component-owns afterwards).
onRowClick(row: T) => void—Interactive rows — the <tr> becomes focusable (tabIndex=0) and fires on Enter / Space / click. The row KEEPS its row role (no role='button' swap) so cell semantics survive. Mutually independent from rowHref.
rowHref(row: T) => string | undefined—Destination for a row. When supplied, the FIRST data cell renders a real <a> whose ::after stretches over the whole row — so the entire row is clickable while the accessible name, keyboard focus, middle-click and open-in-new-tab come from a genuine link rather than a click handler on <tr>. Return undefined to leave an individual row non-navigable.
onRowActivate(row: T, event: React.MouseEvent) => void—Optional SPA hook — fires only for an unmodified primary click so a router can take over; rowHref still governs semantics and modified clicks (⌘/Ctrl/Shift/Alt, middle-click). DS is framework-agnostic (no next dep) — the consumer owns the actual navigation.
selectionDataTableSelection—Leading checkbox column for bulk selection — selectedKeys, allSelected, someSelected, onToggleRow, onToggleAll. selectAllLabel defaults to 'Tout sélectionner' (FR) and selectRowLabel to `(key) => 'Sélectionner la ligne ${key}'` — EN consumers pass their own templates. Per-row aria-selected + highlight are derived from the keys.
density'comfortable' | 'compact''comfortable'Row padding density — compact switches the per-cell padding to a tighter rhythm (AdminSortableTable density='compact' plumbs through here).
<DataTable />Accessibility
ARIA
3 rulesType
1 ruleUsage
Import
import { DataTable } from "@epasslive/design-system"Example 1
<DataTable rows={rows} columns={columns} caption="Réservations — 7 derniers jours" />Example 2
<DataTable rows={rows} columns={columns} caption="Bookings" captionVisible rowHref={(r) => `/bookings/${r.id}`} />Example 3
<DataTable rows={rows} columns={columns} caption="Bulk actions" selection={{ selectedKeys, allSelected, someSelected, onToggleRow, onToggleAll }} selectAllLabel="Select all rows" />Anti-patterns
— Do not fetch data directly in organisms
— Avoid tight coupling to specific data sources
— Do not swap <tr> role='row' for role='button' to make rows interactive — onRowClick keeps the row role so cell semantics survive
— Do not format rich nodes inside `format` without also providing sortValue when the column is sortable — the comparator cannot rank JSX
— Do not stack headerSrOnly on a sortable column — sort button needs a visible label