Data & tables
Data Table
Sortable table with filters and expandable rows
detent-data-table
Port status
- Catalog status
- implemented
- Origin
- upstream
- Phase
- 3
- Wave
- phase-3-data
- Group
- Data & tables
14.5 KB gzip for the class module
Variants
A local sample dataset: cards appear automatically below 640px; use the view button to switch between cards and the semantic table. Sort the Name or Score column, search, filter, select rows, expand a detail, hide a column, or change the page size. Bulk Delete only reports selected ids — nothing is removed, and nothing is fetched.
No bulk action has run yet.
Recorded states
loadingrecorded state; no setteremptyrecorded state; no setterexpandedrecorded state; no setterindeterminaterecorded state; no settercardsrecorded state; no setter
Usage
Import
import '/components/data-table/define.js';
// or register it yourself
import { DetentDataTable } from '/components/data-table/data-table.js';
Attributes
default-page-sizedefault-view-modeempty-descriptionempty-titleerroris-loadingsearch-placeholdertitle
Properties
columnsdatagetRowIdpageSizeOptionsrenderSubComponentonBulkDeleteonBulkExport
Slots
No slots are recorded for this component.
Events
detent-bulk-delete— {}detent-bulk-export— {}
Example
Runnable example: site/examples/data-table.ts. The snippet below is generated from the catalogued attributes.
<detent-data-table
default-page-size="10"
default-view-mode="auto"
empty-description
empty-title
error
is-loading
search-placeholder="Search records..."
title
></detent-data-table>
Property-only values
const element = document.querySelector('detent-data-table');
if (element !== null) {
// data: T[]
element.data = [];
// columns: ColumnDef<T>[]
element.columns = [];
// getRowId: (row: T, index: number) => string
element.getRowId = () => {};
// renderSubComponent: (row: T) => ReactNode
element.renderSubComponent = () => {};
// onBulkDelete: (selectedIds: string[]) => void
element.onBulkDelete = () => {};
// onBulkExport: (selectedIds: string[]) => void
element.onBulkExport = () => {};
}
Source
The pinned upstream source for this row:
src/components/ui/AdvancedDataTable.tsxsha256 54ce9725f5f7…
Native files
The native module and its artifacts:
src/components/data-table/data-table.tssrc/components/data-table/styles.csssrc/components/data-table/define.tsdocs/components/data-table.mdsite/examples/data-table.tstests/browser/data-table.spec.tsevidence/components/data-table.md
API
Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.
Attributes
| Attribute | Property | Type | Default | Notes |
|---|---|---|---|---|
default-page-size | defaultPageSize | number | 10 | Kebab-case attribute; camelCase property. |
default-view-mode | defaultViewMode | DataTableViewMode | 'auto' | Kebab-case attribute; camelCase property. |
empty-description | — | — | — | Recorded in the catalog as an attribute. |
empty-title | — | — | — | Recorded in the catalog as an attribute. |
error | error | string | null | null | Kebab-case attribute; camelCase property. |
is-loading | isLoading | boolean | false | Presence semantics: present means true, absent means false. |
search-placeholder | searchPlaceholder | string | 'Search records...' | Kebab-case attribute; camelCase property. |
title | title | string | undefined | Kebab-case attribute; camelCase property. |
Properties
| Property | Type | Default | Notes |
|---|---|---|---|
columns | ColumnDef<T>[] | [] | Property-only: assign from script; not accepted as an attribute. |
data | T[] | [] | Property-only: assign from script; not accepted as an attribute. |
getRowId | (row: T, index: number) => string | (row, index) => row.id || `row-${index}` | Property-only: assign from script; not accepted as an attribute. |
pageSizeOptions | — | — | Recorded in the catalog without a top-level type; pinned from source when the row is ported. |
renderSubComponent | (row: T) => ReactNode | undefined | Property-only: assign from script; not accepted as an attribute. |
onBulkDelete | (selectedIds: string[]) => void | undefined | Property-only: assign from script; not accepted as an attribute. |
onBulkExport | (selectedIds: string[]) => void | undefined | Property-only: assign from script; not accepted as an attribute. |
Events
| Event | Upstream prop | Payload type | Notes |
|---|---|---|---|
detent-bulk-delete | onBulkDelete | (selectedIds: string[]) => void | Composed, bubbles; fires only for a user action. |
detent-bulk-export | onBulkExport | (selectedIds: string[]) => void | Composed, bubbles; fires only for a user action. |
Slots
No slots are recorded for this component.
CSS customisation
- React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
- `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
- React callback props become typed composed CustomEvents; programmatic state assignment emits none.
- Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
- React state/hooks replaced by element-owned state with pre-upgrade property preservation.
- The compound exports (DataTable, useDataTable, DataTableToolbar, DataTableFilters, DataTableContent, DataTablePagination) ship as parts of the single detent-data-table tag, because PC-2.1 is one-to-one and the catalog row records one exportName/tag.
- data, columns, pageSizeOptions and renderSubComponent are properties, not attributes; nothing is parsed from JSON. onBulkDelete/onBulkExport become the composed events detent-bulk-delete/detent-bulk-export, and programmatic writes emit none (PC-6.5).
- renderSubComponent/columns[].cell return a Node or a string rendered as text. Upstream returned ReactNode; a custom element never parses a consumer string as markup (R-1.1, R-1.3).
- defaultViewMode reflects as default-view-mode; auto selects cards below 640px and a semantic table at 640px or wider, and viewport resize reapplies auto. A manual choice persists through resize for explicit modes, but auto reapplies its breakpoint choice.
- getRowId is a property-only callback. The native fallback uses row.id or the original data index; duplicate or invalid IDs warn once and fall back to unique source-index IDs.
- Stable source-index fallback IDs intentionally improve on upstream's page-local getRowId index, which could change identity when a row moved between pages.
- A search term now returns to page 1. Upstream kept the current page, which could show an empty page even though rows matched.
- The page-size <select> always offers the configured defaultPageSize. Upstream rendered a blank selection when that value was not in pageSizeOptions.
- The bulk bar renders both Export and Delete. Upstream rendered each only when the matching callback prop existed; a custom element cannot detect listeners reliably, so the consumer decides what to do with the event.
- Rows are never mutated: Delete reports selectedIds and leaves data alone (the row's features describe bulk actions, not local deletion).
- lucide-react icons become local text glyphs marked aria-hidden. SPRING_PHYSICS (300/32/0.6) drives the bulk bar's enter/exit height and opacity and an expanded drawer's entry on the shared spring helper; collapsing a drawer stays instant, as upstream (no AnimatePresence). The table row's chevron turns 90 degrees on SPRING_PHYSICS through the shared spring helper (the card view's text Expand/Collapse button no longer rotates, which a shared CSS rule had done); the motion.button press feedback remains a CSS transition. Reduced motion jumps the reveals with no frame and removes the pulse.
- Upstream's semi-transparent palette (bg-surface/50 + border/70) is replaced by the shared theme tokens so both colour schemes meet AA.
What it does
A data table with three-state column sorting, global search, faceted filter chips, per-page selection with a bulk-action bar, expandable rows, pagination with a page-size dropdown and first/last jumps, a column-visibility menu, and skeleton/error/empty states.
Upstream behaviour
- Multi-state column sorting with clean directional indicator icons
- Responsive automatic table/cards views with a user-controlled toggle
- Global live search with instant filtering across all data fields
- Faceted filter chips with multi-selection and reset counters
- Indeterminate select-all checkbox, row selection, and animated bulk actions drawer
- Smooth Framer Motion expandable rows for nested data and telemetry inspection
- Configurable pagination with page size dropdown and first/last page jump buttons
- Dynamic column visibility toggle menu to hide or show columns on demand
- Polished skeleton loading and informative empty state with clear filters trigger
Differences from upstream
- React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
- `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
- React callback props become typed composed CustomEvents; programmatic state assignment emits none.
- Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
- React state/hooks replaced by element-owned state with pre-upgrade property preservation.
- The compound exports (DataTable, useDataTable, DataTableToolbar, DataTableFilters, DataTableContent, DataTablePagination) ship as parts of the single detent-data-table tag, because PC-2.1 is one-to-one and the catalog row records one exportName/tag.
- data, columns, pageSizeOptions and renderSubComponent are properties, not attributes; nothing is parsed from JSON. onBulkDelete/onBulkExport become the composed events detent-bulk-delete/detent-bulk-export, and programmatic writes emit none (PC-6.5).
- renderSubComponent/columns[].cell return a Node or a string rendered as text. Upstream returned ReactNode; a custom element never parses a consumer string as markup (R-1.1, R-1.3).
- defaultViewMode reflects as default-view-mode; auto selects cards below 640px and a semantic table at 640px or wider, and viewport resize reapplies auto. A manual choice persists through resize for explicit modes, but auto reapplies its breakpoint choice.
- getRowId is a property-only callback. The native fallback uses row.id or the original data index; duplicate or invalid IDs warn once and fall back to unique source-index IDs.
- Stable source-index fallback IDs intentionally improve on upstream's page-local getRowId index, which could change identity when a row moved between pages.
- A search term now returns to page 1. Upstream kept the current page, which could show an empty page even though rows matched.
- The page-size <select> always offers the configured defaultPageSize. Upstream rendered a blank selection when that value was not in pageSizeOptions.
- The bulk bar renders both Export and Delete. Upstream rendered each only when the matching callback prop existed; a custom element cannot detect listeners reliably, so the consumer decides what to do with the event.
- Rows are never mutated: Delete reports selectedIds and leaves data alone (the row's features describe bulk actions, not local deletion).
- lucide-react icons become local text glyphs marked aria-hidden. SPRING_PHYSICS (300/32/0.6) drives the bulk bar's enter/exit height and opacity and an expanded drawer's entry on the shared spring helper; collapsing a drawer stays instant, as upstream (no AnimatePresence). The table row's chevron turns 90 degrees on SPRING_PHYSICS through the shared spring helper (the card view's text Expand/Collapse button no longer rotates, which a shared CSS rule had done); the motion.button press feedback remains a CSS transition. Reduced motion jumps the reveals with no frame and removes the pulse.
- Upstream's semi-transparent palette (bg-surface/50 + border/70) is replaced by the shared theme tokens so both colour schemes meet AA.