Skip to main content

AdminFilterableTable

Implemented

Search-filtered admin data table — Wave-3 L5-M3 composition of the canonical DataTable organism with the TableToolbar molecule (search input + clear affordance). The wrapper owns the filtering semantics: filterPredicate(row, query) narrows rows client-side, a live region in the toolbar announces 'N / total matching', aria-controls wires the search input to the table wrapper, and emptyMessage (no data) vs noMatchMessage (filter miss) distinguish the two empty states. Filter chips (filters prop, forwarded to TableToolbar) are independent from the search predicate — chips handle categorical facets, the predicate handles the free-text query. Exported from the barrel along with AdminFilterableTableProps and AdminFilterableTableColumn.

Categoryorganisms
Sourcecomponents/organisms/AdminFilterableTable/AdminFilterableTable.tsx
AI HintComposes the canonical DataTable organism + TableToolbar molecule. Use when rows are filtered CLIENT-SIDE by a search query — filterPredicate is called with the TRIMMED query and is skipped entirely on empty/whitespace. Filter chips (filters) are independent from the search predicate (categorical facets vs free-text). For server-driven search, compose TableToolbar + DataTable (or ListPanel) directly with your own debounced fetch — this wrapper does not own data fetching. EN-default i18n across searchPlaceholder/searchLabel/emptyMessage/noMatchMessage/ariaLabel/matchCountFormat/rowCountFormat/clearSearchLabel/filtersLabel — FR consumers pass their own per the JSDoc recipes. Exported from the barrel along with AdminFilterableTableProps and AdminFilterableTableColumn.

Search-filtered admin data table — a Wave-3 L5-M3 composition of the canonical DataTable organism with the TableToolbar molecule. The wrapper owns filterPredicate, the live 'N / total matching' counter and the aria-controls wiring; distinct emptyMessage (no data) vs noMatchMessage (filter miss).

AdminFilterableTable

Organism — Admin family

Composes DataTable (table chrome, accessible empty state) with TableToolbar (search input + clear affordance). filterPredicate(row, query) narrows rows client-side, the match counter is an aria-live region, the search input carries aria-controls pointing at the table wrapper, and emptyMessage vs noMatchMessage distinguish no-data from filter-miss.

Events — results
EventVenueCity
Festival Jazz MTLThéâtre MaisonneuveMontréal
OSM Symphonie n° 5Maison SymphoniqueMontréal
Lion d'Or GalaLion d'OrMontréal
Yelle en spectacleImpériel BellQuébec
Nuit du PianoPalais MontcalmQuébec

This component supports light and dark mode.

Usage

Pass filterPredicate matching every searchable field (name, venue, city) — the wrapper trims the query and skips filtering entirely when it is empty. Localize both empty states: emptyMessage fires when there is no data, noMatchMessage when the filter excludes everything.

import { AdminFilterableTable } from '@epasslive/design-system'

<AdminFilterableTable
  columns={columns}
  rows={rows}
  rowKey={(r) => r.id}
  filterPredicate={(row, query) =>
    `${row.name} ${row.venue} ${row.city}`.toLowerCase().includes(query.toLowerCase())
  }
  ariaLabel="Events"
  noMatchMessage="Aucun événement ne correspond à votre recherche"
/>

Conventions

Filtering is client-side only — for server-driven search, compose TableToolbar + DataTable (or ListPanel) directly with your own debounced fetch. The aria-controls wiring (input → table wrapper) is derived from id; pass a stable id when you need to target the table from elsewhere.

Accessibility

The search input carries aria-controls pointing at the table wrapper and an accessible label (WCAG 4.1.3 — Status Messages): the match counter lives in an aria-live="polite" region in the toolbar, so results-count changes are announced without moving focus. The results table is named via its caption; the empty state renders in DataTable's role="status" cell.

Props

Prop
Type
Default
Description
columns*
AdminFilterableTableColumn<T>[]
—

Column definitions — see the column table below.

rows*
T[]
—

Row data — one item per row, typed by the generic.

rowKey*
(row: T, index: number) => string
—

Stable React key per row (index is the pre-filter position).

filterPredicate*
(row: T, query: string) => boolean
—

Keep-row test — receives the trimmed query string (skip-filter on empty is handled by the wrapper).

searchPlaceholder
string
"Search…"

Placeholder for the toolbar search input.

searchLabel
string
"Filter rows"

Accessible label for the search input.

emptyMessage
string
"No data to display"

Empty state when rows is empty.

noMatchMessage
string
"No rows match your search"

Empty state when the filter excludes every row.

ariaLabel
string
"Filterable table"

Accessible name — also feeds the results table caption.

className
string
—

Class on the outer section wrapper.

id
string
—

Stable id (auto-generated aflt-<useId()> fallback).

matchCountFormat
(filtered: number, total: number) => string
(f, t) => `${f} / ${t} matching`

Format the trailing live-region count when a filter is active. EN default — FR: (f, t) => `${f} / ${t} correspondance(s)`.

rowCountFormat
(total: number) => string
(t) => `${t} rows`

Format the trailing live-region count when no filter is active. EN default — FR: (t) => `${t} ligne(s)`.

clearSearchLabel
string
"Clear search"

Accessible label for the clear-search button (forwarded to TableToolbar). EN default — FR: 'Effacer la recherche'.

filters
TableToolbarFilter[]
—

Optional filter chip group for column-level faceting (forwarded to TableToolbar). Independent from filterPredicate — chips handle categorical facets, the predicate handles the free-text query.

filtersLabel
string
"Filters"

Accessible label for the filter chip group (forwarded to TableToolbar). EN default — FR: 'Filtres'.

Column definition

Prop
Type
Default
Description
id*
string
—

Column identifier.

header*
string
—

Header label.

cell*
(row: T) => React.ReactNode
—

Cell renderer — receives the row object.

width
string
—

CSS width for the column.

align
"left" | "right" | "center"
—

Cell + header alignment.

Props

Preview

AdminFilterableTable

Prop
Type
Required
Default
Description
columns
AdminFilterableTableColumn<T>[]
Required
—

Column definitions — id / header / cell / width / align. See AdminFilterableTableColumn below.

rows
T[]
Required
—

Row data — one item per row, typed by the generic.

rowKey
(row: T, index: number) => string
Required
—

Stable React key per row (index is the PRE-FILTER position).

filterPredicate
(row: T, query: string) => boolean
Required
—

Keep-row test — receives the TRIMMED query string (whitespace-only queries are skipped before the predicate is called; lowercase both sides yourself).

searchPlaceholder
string
Optional
'Search…'

Placeholder for the toolbar search input. EN default — FR: 'Rechercher…'.

searchLabel
string
Optional
'Filter rows'

Accessible label for the search input. EN default — FR: 'Filtrer les lignes'.

emptyMessage
string
Optional
'No data to display'

Empty state when rows is empty (no data at all). EN default — FR: 'Aucune donnée à afficher'.

noMatchMessage
string
Optional
'No rows match your search'

Empty state when the filter predicate excludes every row. EN default — FR: 'Aucune ligne ne correspond à votre recherche'.

ariaLabel
string
Optional
'Filterable table'

Accessible name for the wrapper section and the results table caption. EN default — FR: 'Tableau filtrable'.

className
string
Optional
—

Class on the outer section wrapper.

id
string
Optional
aflt-<useId()>

Stable id for the wrapper section (auto-generated aflt-<useId()> fallback). Pass a stable id when you need to target the table from elsewhere; aria-controls derives from it.

matchCountFormat
(filtered: number, total: number) => string
Optional
(f, t) => `${f} / ${t} matching`

Format the live-region count WHEN a filter is active. EN default — FR: (f, t) => `${f} / ${t} correspondance(s)`.

rowCountFormat
(total: number) => string
Optional
(t) => `${t} rows`

Format the live-region count when no filter is active. EN default — FR: (t) => `${t} ligne(s)`.

clearSearchLabel
string
Optional
'Clear search'

Accessible label for the clear-search button (forwarded to TableToolbar). EN default — FR: 'Effacer la recherche'.

filters
TableToolbarFilter[]
Optional
—

Optional filter chip group for column-level faceting (forwarded to TableToolbar). Independent from filterPredicate — chips handle categorical facets, the predicate handles the free-text query.

filtersLabel
string
Optional
'Filters'

Accessible label for the filter chip group (forwarded to TableToolbar). EN default — FR: 'Filtres'.

Quick use:<AdminFilterableTable />

Accessibility

ARIA roletable

ARIA

2 rules

WCAG 4.1.3

Search input carries aria-controls pointing at the table wrapper id (derived from id prop); match counter (N / total matching when filtering, N rows otherwise) lives in an aria-live='polite' region in the toolbar — announced without focus steal (WCAG 4.1.3)

WCAG 4.1.2

Two distinct empty states — emptyMessage for 'no data' and noMatchMessage for 'filter excludes all rows' — so screen-reader users hear the right diagnosis; results table named via its caption (WCAG 4.1.2)

Usage

Import

tsxSnippet
import { AdminFilterableTable } from "@epasslive/design-system"

Example 1

tsxSnippet
<AdminFilterableTable columns={cols} rows={rows} rowKey={(r) => r.id} filterPredicate={(row, q) => `${row.name} ${row.venue} ${row.city}`.toLowerCase().includes(q.toLowerCase())} ariaLabel="Events" />

Example 2

tsxSnippet
<AdminFilterableTable columns={cols} rows={orders} rowKey={(r) => r.id} filterPredicate={orderSearch} ariaLabel="Commandes" emptyMessage="Aucune donnée à afficher" noMatchMessage="Aucune commande ne correspond à votre recherche" searchPlaceholder="Rechercher…" searchLabel="Filtrer les commandes" clearSearchLabel="Effacer la recherche" matchCountFormat={(f, t) => `${f} / ${t} correspondance(s)`} rowCountFormat={(t) => `${t} commande(s)`} filters={statusChips} filtersLabel="Filtres" />

Anti-patterns

— Do not fetch data directly in organisms

— Avoid tight coupling to specific data sources

— Do not filter inside cell renderers — use filterPredicate against the rows array (cells render the post-filter view)

— Do not use this organism for server-driven search — compose TableToolbar + DataTable (or ListPanel) directly with your own debounced fetch

— Do not conflate emptyMessage and noMatchMessage — localizing both is mandatory; they diagnose two different states for screen-reader users

— Do not hide the clear-search affordance (it is part of the TableToolbar contract — the clearSearchLabel prop exists so FR consumers can localize the aria-label)

Design System v4.1.0 | ePassLive